# Testing Quality Review — Theseus

Generated from codebase analysis of `src/`, `tests/`, `e2e/`.

## Summary

| Criterion | Score | 
|-----------|-------|
| Coverage | C |
| Test Quality | B |
| Test Organization | B |
| Edge Case Testing | B- |
| Test Maintainability | B+ |
| CI Integration | F |

---

## 1. Coverage — C

**26 Rust test functions** across 11 modules. **10 E2E test files** in Python.

**Modules with tests (11/21 meaningful modules = 52%):**

| Module | Tests | Type |
|--------|-------|------|
| `src/types.rs` | 9 `#[test]` | Unit — serialization roundtrips, Display, custom type regex, NodeType ordering |
| `src/db.rs` | 6 `#[test]` | Unit — CRUD for all entities, cascade deletes, FK errors, analysis, timeline, search |
| `src/routes/projects.rs` | 1 `#[tokio::test]` | Integration — status codes, JSON envelope |
| `src/routes/nodes.rs` | 1 `#[tokio::test]` | Integration — CRUD, filters, errors |
| `src/routes/edges.rs` | 1 `#[tokio::test]` | Integration — CRUD, error statuses |
| `src/routes/credentials.rs` | 1 `#[tokio::test]` | Integration — CRUD |
| `src/routes/evidence.rs` | 1 `#[tokio::test]` | Integration — upload/list/delete |
| `src/routes/analysis.rs` | 1 `#[tokio::test]` | Integration — attack path analysis |
| `src/routes/report.rs` | 2 `#[tokio::test]` | Integration — report generation |
| `src/routes/search.rs` | 1 `#[tokio::test]` | Integration — global search |
| `src/routes/timeline.rs` | 1 `#[tokio::test]` | Integration — project timeline |

**Modules WITHOUT tests (10 modules):**

- `src/lib.rs` — middleware, CORS, auth gate, static serving (901 lines)
- `src/main.rs` — CLI parsing, all subcommands (2657 lines)
- `src/agent/` — llm.rs, loop.rs, memory.rs, prompts.rs, tools.rs (entire AI subsystem)
- `src/routes/check.rs` — graph health checks
- `src/routes/chat.rs` — chat endpoint
- `src/routes/daily_report.rs` — daily report generation
- `src/routes/import.rs` — data import
- `src/routes/ws.rs` — WebSocket broadcast
- `src/routes/agent.rs` — agent HTTP handler

**Recommendation:** The largest gaps are `main.rs` (2657 lines, zero tests), `lib.rs` (auth/CORS middleware untested), and the entire `agent/` module. The E2E tests partially cover import and daily_report at the HTTP level, but Rust-side unit tests for `main.rs` argument parsing and `lib.rs` middleware would catch regressions earlier.

---

## 2. Test Quality — B

**Strengths:**

- Tests verify actual behavior, not just "doesn't panic". Route integration tests assert specific status codes, JSON structure, and field values:
  ```rust
  // src/routes/projects.rs:179
  assert_eq!(resp.status(), StatusCode::CREATED);
  assert_eq!(body["success"], true);
  assert_eq!(body["data"]["name"], "Eng A");
  ```
- `db.rs` tests exercise complex multi-step scenarios: node update with content append + verdict + evidence, cascade edge deletion on node delete, foreign key constraint violations.
- `types.rs` uses a reusable `assert_roundtrip<T>()` helper for serde validation — clean pattern.

**Weaknesses:**

- Each route file has only **one** integration test function that covers everything. `nodes.rs:556` `node_crud_filters_and_errors()` is ~170 lines — if it fails at line 600, you don't know which scenario broke without reading the full test.
- No negative testing for malformed JSON bodies, missing required fields, or invalid UUIDs in most route tests.
- No concurrency or race-condition tests despite using `Mutex<Connection>`.

**Recommendation:** Split mega-test functions into focused `#[tokio::test]` cases (one per scenario). Add tests for malformed input and concurrent access to the database.

---

## 3. Test Organization — B

**Three clear layers exist:**

1. **Unit tests** (`#[test]`) — `types.rs`, `db.rs`. Use `:memory:` SQLite or pure logic. No external dependencies.
2. **Integration tests** (`#[tokio::test]`) — `routes/*.rs`. Spawn real axum server on random port with `TcpListener::bind("127.0.0.1:0")`, test via `reqwest`. Good isolation per test (separate temp DB via `uuid::Uuid::new_v4()`).
3. **E2E tests** (Python) — Two variants:
   - `tests/e2e_test.py` — standalone, stdlib-only, uses `TestContext` with temp DB + random port
   - `e2e/` — 10 files using `helper.py` + `run_all.sh`, requires `requests` library

**Issues:**

- No `tests/` Rust integration test directory (`cargo test --test` style). All route tests are inline `mod tests` blocks.
- The two Python E2E suites overlap significantly (`tests/e2e_test.py` covers much of what `e2e/` covers) but use different infrastructure (stdlib vs requests, different server management).
- No shared test fixtures or helpers across Rust test modules. Each route file reimplements `spawn_app()` and `json()` with minor variations.

**Recommendation:** Consolidate the two Python E2E suites or clearly document when to use which. Extract a shared `test_helpers` crate or module for the duplicated `spawn_app()` pattern across route tests.

---

## 4. Edge Case Testing — B-

**Good edge cases tested:**

- Delete already-deleted entity → 404 (`db.rs:1464`, `e2e/test_projects.py:35`)
- Foreign key violations → `ConstraintViolation` error (`db.rs:1732-1740`, `db.rs:1776-1785`)
- Force-delete connected node cascades edges (`e2e/test_nodes.py:67-77`)
- Empty project name → 400 (`e2e/test_projects.py:23`)
- Nonexistent UUID → 404 (`e2e/test_projects.py:27`)
- Search with no results → empty list (`e2e/test_nodes.py:62-64`)
- `NodeType::Custom` untagged serde ordering (`types.rs:762-798`)

**Missing edge cases:**

- No tests for very long strings, special characters, or Unicode in labels/content
- No tests for concurrent mutations (two requests updating the same node)
- No tests for malformed UUIDs or partial UUID prefix resolution (`resolve_*_id` helpers)
- No tests for WebSocket broadcast behavior
- No tests for auth middleware (API key required/optional paths)
- No tests for CORS configuration
- No tests for large payloads or batch operations

**Recommendation:** Add tests for input validation boundaries (max lengths, special chars), auth paths, and at least one concurrency test.

---

## 5. Test Maintainability — B+

**Strengths:**

- Route integration tests use `Box::leak` + random DB paths — fully isolated, no shared state between tests.
- E2E `helper.py` provides clean abstraction: `assert_ok()`, `assert_err()`, `unique_name()`, `create_project()`, `create_node()`.
- `TestContext` in `tests/e2e_test.py` handles server lifecycle, DB backup/restore, and port allocation cleanly.
- E2E tests use `RUN_ID` for name uniqueness — parallel-safe.

**Weaknesses:**

- Each `spawn_app()` implementation is 15-20 lines of boilerplate, duplicated across 9 route files with slight variations (some return `(String, Db)`, others `(String, Uuid, Uuid, Uuid)`).
- No `#[should_panic]` or `Result`-returning test patterns — all assertions use `assert!`/`assert_eq!` which give poor failure messages for complex types.
- Test data uses inline `serde_json::json!()` — readable but not reusable across tests.

**Recommendation:** Create a `test_support` module with a configurable `TestApp::builder()` to replace the 9 copy-pasted `spawn_app()` functions.

---

## 6. CI Integration — F

**No CI configuration found.** No `.github/workflows/`, no `Makefile`, no `justfile`, no `Dockerfile` for reproducible test runs.

The only automation is `e2e/run_all.sh` — a manual shell script.

**Impact:** Tests exist but rely entirely on developers running them manually. No guard against regressions landing on `main`.

**Recommendation:** Add a minimal GitHub Actions workflow:

```yaml
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
  rust:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: cargo test
  e2e:
    runs-on: ubuntu-latest
    needs: rust
    steps:
      - uses: actions/checkout@v4
      - run: cargo build
      - run: pip install requests
      - run: cd e2e && ./run_all.sh
```

---

## Test Inventory

### Rust (`cargo test`)

| File | `#[test]` | `#[tokio::test]` | Notes |
|------|-----------|-------------------|-------|
| `src/types.rs` | 9 | 0 | Serialization, Display, validation |
| `src/db.rs` | 6 | 0 | All CRUD paths, analysis, timeline |
| `src/routes/projects.rs` | 0 | 1 | Status codes, JSON envelope |
| `src/routes/nodes.rs` | 0 | 1 | CRUD + filters + errors |
| `src/routes/edges.rs` | 0 | 1 | CRUD + error statuses |
| `src/routes/credentials.rs` | 0 | 1 | CRUD |
| `src/routes/evidence.rs` | 0 | 1 | Upload/list/delete |
| `src/routes/analysis.rs` | 0 | 1 | Attack path analysis |
| `src/routes/report.rs` | 0 | 2 | Report generation |
| `src/routes/search.rs` | 0 | 1 | Global search |
| `src/routes/timeline.rs` | 0 | 1 | Timeline |
| **Total** | **15** | **11** | **26 test functions** |

### Python E2E

| File | Scope |
|------|-------|
| `tests/e2e_test.py` | Standalone (stdlib) — full CRUD + edge cases (827 lines) |
| `e2e/test_projects.py` | Project CRUD + validation |
| `e2e/test_nodes.py` | Node CRUD + search + filters + force delete |
| `e2e/test_edges.py` | Edge CRUD |
| `e2e/test_credentials.py` | Credential CRUD |
| `e2e/test_timeline.py` | Timeline queries |
| `e2e/test_search.py` | Global search |
| `e2e/test_evidence.py` | Evidence upload/list/delete |
| `e2e/test_import.py` | Data import |
| `e2e/test_daily_report.py` | Daily report generation |
| `e2e/test_analysis.py` | Attack analysis |
| `e2e/helper.py` | Shared HTTP/assertion helpers |
| `e2e/run_all.sh` | Orchestrator (pkill → build → serve → test → kill) |
