Skip to content

Adopt three-tier test layout with real-Postgres integration tier - #66

Merged
LukeMainwaring merged 2 commits into
mainfrom
refactor/backend-test-tiers
Jul 16, 2026
Merged

Adopt three-tier test layout with real-Postgres integration tier#66
LukeMainwaring merged 2 commits into
mainfrom
refactor/backend-test-tiers

Conversation

@LukeMainwaring

Copy link
Copy Markdown
Owner

Summary

Restructures backend/tests/ into three tiers and adds the infrastructure the previously-untested web/service/model layer needed:

  • unit/<area>/ — the existing fast, DB-free tests, now grouped by area (ml/, services/, agents/, scripts/, utils/). Default pytest run.
  • integration/ (new, opt-in via -m integration) — real Postgres + HTTP. Schema comes from the shipped Alembic migrations (so the pgvector extension and HNSW index are exercised), each test rolls back an outer transaction, and the httpx ASGITransport client overrides the app's session dependency. Covers sessions/thread/retrieval routes, model CRUD (incl. append-only message history and the Spotify token singleton), a pgvector cosine-ordering smoke test, and a migrations round-trip.
  • evals/ — unchanged real-LLM suite (-m eval).

Supporting changes:

  • Commit contract documented: the session dependency in dependencies/db.py owns commit/rollback; services and model classmethods only flush(). Removed two redundant commits in session-context callers (title_generator, seed_sessions) — behavior-preserving, since the context manager already commits on clean exit. This contract is what makes per-test rollback isolation sound.
  • anyio replaces raw asyncio.run() wrappers (no pytest-asyncio; anyio ships its own plugin). Unused main/additional markers removed.
  • tests/factories.py (make_* unpersisted / create_* persisted) and tests/fakes.py (agent-deps mocks).
  • CI: pgvector/pgvector:pg17 service container, POSTGRES_DB=cortexdj_test. Guards fail loudly in CI (and skip locally) if Postgres is unreachable or the DB name doesn't end in _test — the tier refuses non-test databases because it runs alembic downgrade.
  • Alembic env: fileConfig(disable_existing_loggers=False) so in-process migrations don't silence app loggers.
  • New unit tests for title_generator, message_serialization, emotion; TestModel-backed prepare_tools tests moved from evals/ to unit/agents/ (they were already running in the default tier).
  • backend/scripts/create-test-db.sh + DEVELOPMENT.md/rules docs for the new layout.

Coverage: 43% → 53.45% (floor stays at 40).

Test plan

  • uv run --directory backend pytest — unit tier, 175 passed
  • ./backend/scripts/create-test-db.sh && POSTGRES_DB=cortexdj_test uv run --directory backend pytest -m integration — 28 passed
  • CI-equivalent combined run with coverage: 203 passed, 53.45%
  • pre-commit run --all-files (ruff, ruff-format, mypy --strict src tests)
  • No API shape changes → no frontend client regen needed

🤖 Generated with Claude Code

LukeMainwaring and others added 2 commits July 16, 2026 15:24
- Restructure backend/tests into unit/<area>/, integration/, evals/ tiers;
  default pytest run is the DB-free unit tier, integration is opt-in via
  -m integration (CI runs both).
- Add tests/integration/conftest.py: schema from the shipped Alembic
  migrations, per-test outer-transaction rollback, httpx ASGITransport
  client with the session dependency overridden. Guards refuse non-test
  database names and skip when Postgres is down.
- Add tests/factories.py (make_* unpersisted / create_* persisted builders).
- New integration tests: sessions/thread/retrieval routes, models CRUD,
  pgvector cosine-ordering smoke, migrations round-trip, DB health. New
  unit tests: title_generator, message_serialization, emotion. Coverage
  43% -> 53%.
- Switch async tests to anyio's pytest plugin (drop raw asyncio.run
  wrappers); drop unused main/additional markers.
- Document the commit-owning session dependency contract in
  dependencies/db.py and remove redundant commits in session-context
  callers (title_generator, seed_sessions); services keep flush-only.
- Alembic env: fileConfig(disable_existing_loggers=False) so in-process
  migrations (integration tier) don't silence app loggers.
- CI: pgvector/pgvector:pg17 service container, POSTGRES_DB=cortexdj_test.
- backend/scripts/create-test-db.sh + DEVELOPMENT.md three-tier docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- DB-name guard is now endswith("_test"), not a substring check —
  "cortexdj_latest" must never pass a guard that precedes downgrade base.
- Guards fail loudly in CI (skip only locally) so a misconfigured tier
  can't silently deselect 28 tests while the job stays green.
- _migrated starts pristine (downgrade base -> upgrade head) so
  empty-state assertions hold across runs; tier markers are applied
  structurally by path in the integration conftest instead of per-file
  pytestmark.
- Move TestModel-backed prepare_tools tests from evals/ to unit/agents/
  (they run in the default tier); agent-deps fakes move to tests/fakes.py.
- Update the backend rules Testing section to describe the three tiers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@LukeMainwaring
LukeMainwaring merged commit 858cf6e into main Jul 16, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant