--- id: CUST-WP-0020 type: workplan title: "State Hub — pytest Test Suite" domain: custodian status: done owner: custodian topic_slug: custodian created: "2026-03-18" updated: "2026-03-18" state_hub_workstream_id: "56379f10-bad9-49b4-bbd0-e4a0de5c6a81" --- # State Hub — pytest Test Suite ## Summary The API, MCP server, seed script, and migration scripts currently have zero automated test coverage (TD-CUST-014). Regressions are caught at runtime. This workplan introduces a pytest-asyncio test suite using HTTPX's async test client against a real test database (no mocking — see ADR below). ## ADR Do NOT mock the database in these tests. The state-hub has had past incidents where mocked tests passed but the real DB diverged (see memory: feedback on not mocking the database). Use a real PostgreSQL test database via `TEST_DATABASE_URL` env var (docker-compose already provides postgres). --- ## Tasks ### T01 — Test infrastructure: pytest-asyncio + async HTTP client + fixtures ```task id: CUST-WP-0020-T01 status: done priority: high state_hub_task_id: "35a52abb-15b1-4c12-b1c6-5e321377ddfa" ``` - Add `pytest`, `pytest-asyncio`, `httpx` to `state-hub/pyproject.toml` dev deps - Create `state-hub/tests/conftest.py`: - `engine` fixture: creates a fresh async engine against `TEST_DATABASE_URL` - `db` fixture: runs `alembic upgrade head` then `alembic downgrade base` around each test session (or truncates tables between tests) - `client` fixture: `httpx.AsyncClient` pointed at the test app instance - Create `state-hub/tests/__init__.py` - Add `make test` target in `state-hub/Makefile` --- ### T02 — Core router tests: topics, workstreams, tasks, decisions ```task id: CUST-WP-0020-T02 status: done priority: high state_hub_task_id: "85b0b6e2-d66b-4619-8e1f-2056862a0d97" ``` Coverage targets (happy path + key error cases): - `POST /topics/` → 201, duplicate slug → 409 - `POST /workstreams/` + `GET /workstreams/?topic_id=` + status transitions - `POST /tasks/` + `GET /tasks/?workstream_id=` + `needs_human` flag - `POST /decisions/` + `PATCH /{id}/resolve/` - `GET /state/summary` → returns expected shape with counts --- ### T03 — TD, EP, contributions router tests ```task id: CUST-WP-0020-T03 status: done priority: medium state_hub_task_id: "41106482-5b1c-4ee9-8979-377595f704b9" ``` - TD CRUD + workflow status transitions + notes endpoints - EP CRUD + status transitions - Contribution lifecycle guard (invalid transitions → 422) - SBOM ingest + `/sbom/snapshots/` aggregation --- ### T04 — MCP server smoke tests ```task id: CUST-WP-0020-T04 status: done priority: medium state_hub_task_id: "6fa30dd1-065d-4158-8ef9-ed5ff7001083" ``` For each MCP tool, call the underlying HTTP helper against the test client and assert the result shape. No need to test the MCP stdio protocol itself — just the HTTP-level correctness. Focus on: `get_state_summary`, `create_task`, `update_task_status`, `add_progress_event`, `flag_for_human`. --- ### T05 — CI: add pytest to pre-commit or Makefile gate ```task id: CUST-WP-0020-T05 status: done priority: low state_hub_task_id: "5d206df8-c902-4c5b-8a0b-58b600480c0f" ``` Add `make test` as a step in any existing CI pipeline, or document the manual pre-push gate in CLAUDE.md. Ensure `TEST_DATABASE_URL` is set to the docker-compose postgres instance in the local dev workflow.