Testing
Testing
Tests exist so an agent can close its own loop honestly. A test suite that can’t fail meaningfully provides confidence without coverage, which is worse than no suite at all.
Levels
| Level | Tool | Runs against |
|---|---|---|
| Unit | Vitest | Pure logic. No I/O |
| Database | Vitest | Real Postgres — Docker locally, Neon branch in CI |
| API | Vitest + @cloudflare/vitest-pool-workers | The real Workers runtime |
| Web | Playwright | Built Astro output |
| Contract | tsc | Clients against packages/shared |
Non-negotiables
Real Postgres, never mocked. pg_trgm and FTS cannot be meaningfully faked, and a mocked query proves nothing about the SQL. Per ADR-0011: Docker locally, Neon branch in CI.
Real Workers runtime for the API. @cloudflare/vitest-pool-workers, not Node with mocked bindings. The runtimes differ in bindings, request lifecycle and available globals — the exact places this project’s traps live.
Write the test first. Step 3 of the loop commits the failing test. A test written after the implementation asserts what the code does; one written before asserts what it should do.
What must have a test
- Every Definition of Done line that is behavioural
- Every domain invariant from ADR-0006 and ADR-0007
- Every bug fix — the regression test comes first, and it must fail before the fix
- Every migration — that it applies, and that it is reversible or explicitly marked one-way
The invariant tests
These encode the decisions most likely to be broken by a well-meaning change. They live in packages/db and must never be weakened:
- A second session rating updates the shelf projection and leaves the first session’s rating intact.
- A session participant with a null
user_idround-trips through every participant query. - A session insert with only
game_idandplayed_atsucceeds. - No table foreign-keys to
clerk_user_id— schema assertion, per ADR-0005.
What not to test
- Framework behaviour. Drizzle, Hono and Zod have their own suites.
- Getters, pass-throughs, and generated code.
- Implementation detail. A test asserting how something works blocks the refactor it should have survived.
Test data
Fixtures live beside the tests. Database tests get a transaction rolled back at teardown — no shared mutable state between tests, no ordering dependence.
Catalogue fixtures come from packages/catalogue, so the same data seeds tests and local development.
Anti-patterns
Each of these turns a red gate green while removing the coverage:
- Deleting or loosening an assertion to pass
it.skipwithout a linked issue- Widening a type to silence the typechecker
- Asserting
toBeDefined()where the actual value matters - Snapshot tests over structures nobody reads — a diff nobody understands gets accepted blind
If a check is genuinely wrong, that is a work item with an ADR, not an inline edit.