Files
solelog/docs/reference/domain-test-checklist.md
2026-07-22 12:01:03 +02:00

5.1 KiB

Domain test checklist — guaranteed behaviors

Living definition-of-done. Each domain lists the behaviors its tests guarantee. When you change a domain, its box must still hold (or you update this list on purpose). Baseline established 2026-07-22 (see plan 2026-07-22-per-domain-test-safety-net). Tracked as Plane epic SL-70.

Coverage baseline (Phase A, 2026-07-22)

Workspace % Stmts % Branch % Funcs % Lines
api 96.94 74.29 100 96.94
worker 89.62 87.42 70.58 89.62
admin 92.43 84.91 78.75 92.43

Target after the sweep: near-100% per domain; remaining uncovered lines annotated /* c8 ignore next -- <reason> */, not left silent.

Infra (health/db/schema/seed/env/migrate)

Src covered: src/routes/health.ts, src/db/client.ts, src/db/ensure-dir.ts, src/db/migrate.ts, src/db/seed.ts, src/env.ts — all at 100% stmts/branch/funcs/lines. (src/db/schema.ts is generated and excluded from coverage by config.)

Guaranteed behaviors:

  • GET /health returns { status: 'ok' }.
  • env.ts applies every dev default (DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL, PORT, WEB_ORIGINS) when the vars are unset, and reads each override when set (PORT coerced to a number).
  • env.WEB_ORIGINS parses CORS_ORIGINS into a list, trimming whitespace and dropping empty entries; falls back to the default localhost origins when CORS_ORIGINS is empty/unset.
  • runMigrations() is idempotent — re-running it against an already-migrated DB does not throw.
  • ensureDbDir() is a no-op for non-file: URLs and for a bare filename in the current dir.
  • seed() seeds the reference activities idempotently and seeds the dev worker + dev admin with the correct roles, idempotently.
  • seed() never creates the dev users when NODE_ENV=production (reference activities still seeded).
  • The migrate.ts / seed.ts CLI direct-run blocks are c8 ignored — unreachable under the test runner (exercised only via tsx src/db/{migrate,seed}.ts).

Auth & /me

Src covered: src/auth.ts, src/routes/me.ts, src/lib/require-user.ts, and the CORS middleware in src/app.ts — all at 100% stmts/branch/funcs/lines.

Guaranteed behaviors:

  • Sign-in with valid credentials returns a bearer token (set-auth-token header); sign-in with a wrong password fails (status ≥ 400).
  • Public sign-up is rejected (disableSignUp — admin creates users).
  • GET /api/me returns 401 without a token, and { user: { id, email, name, role } } with a valid bearer; role reflects the user (worker / admin).
  • GET /api/me falls back to role 'worker' when the session user has no role set.
  • getSessionUser maps id, role, and banned from the session; defaults role to 'worker' and banned to false when unset; returns null when there is no session.
  • isAdmin is true only for the admin role (false for worker and for null).
  • adminGuard returns 401 without a session, 403 for a non-admin, and 403 for a banned admin that still holds a valid token; it passes through for an active admin. (Exercised through the /api/admin/* surface in admin-users.test.ts.)
  • CORS: the allowed SPA origins (worker 5173, admin 5174) are echoed on the access-control-allow-origin header, OPTIONS preflight is answered with the allowed methods, and set-auth-token is exposed so the SPA can read the bearer on sign-in.

Activities

Src covered: src/routes/activities.ts — 100% stmts/branch/funcs/lines. The one unreachable branch (row.sortOrder ?? 0 in toActivity; sort_order is NOT NULL DEFAULT 0) is c8 ignored with a reason, not left silent.

Guaranteed behaviors:

  • GET /api/activities requires a session (401 without a token); any authenticated user — worker or admin — may read.
  • The list is ordered by sort_order then name, and ?insole_type=<type> filters to activities whose insole_types include that type.
  • POST/PUT/DELETE/PUT /reorder are admin-only: 401 without a token, 403 for a worker.
  • Create validates a non-empty (trimmed) name (400 otherwise) and defaults insole_types to all three (Kurk, Berk, 3D) when omitted.
  • Create appends the new activity with a sort_order one higher than the current maximum.
  • insole_types round-trips through create/list as the exact stored subset.
  • Update (PUT /:id) 404s for a non-numeric or unknown id, 400s on an invalid body, and returns the updated activity otherwise.
  • Delete (DELETE /:id) 404s for a non-numeric id, removes the activity and its work-session rows (no FK cascade), and returns { success: true }.
  • Reorder (PUT /reorder) sets sort_order from the ids array position; it 400s on an empty array (zod min(1)) and when the ids are not exactly the full known set (wrong count or an unknown id).
  • Lifecycle: an admin creates three activities → reorders them → a worker's GET reflects the new order → the admin deletes one → it is gone from the list.