Files
solelog/docs/superpowers/specs/2026-07-22-per-domain-test-safety-net-design.md

137 lines
7.3 KiB
Markdown

# Design — Per-domain test safety net
**Date:** 2026-07-22
**Status:** Approved (brainstorming) → ready for implementation plan
**Goal:** Establish a per-domain test safety net across all three workspaces so future
changes can't silently break existing behavior, and shake out latent bugs in the process.
## Decisions (from brainstorming)
1. **Deliverable = full safety net.** Coverage tooling + per-domain gap-fill (edge / error /
authz / regression cases) + one full-lifecycle integration test per domain + a living
"guaranteed behaviors" checklist doc.
2. **Scope = all three layers, equal depth.** `apps/api`, `apps/worker`, `apps/admin`.
3. **Bug handling = log in Plane + fix trivial inline.** Every finding becomes a Plane work
item; small/safe fixes land inline in the same pass; risky / behavior-changing ones get
their own ticket and are deferred (flagged as such).
4. **Target near-100% coverage per domain** (the working bar). Every remaining uncovered
line is either tested or explicitly annotated as defensive/unreachable — no silent gaps.
A CI threshold *gate* (build fails under X%) is still deferred; the target is enforced by
review for now.
## Current baseline (2026-07-22)
| Workspace | Test files | Tests | Status |
| ----------- | ---------- | ----- | ------- |
| `apps/api` | 15 | 103 | ✅ green |
| `apps/worker` | 8 | 30 | ✅ green |
| `apps/admin` | 11 | 50 | ✅ green |
- No `@vitest/coverage-v8` in any workspace — untested paths are currently guesswork.
- **API harness:** real file DB (`file:./.tmp/test.db`), migrated once per run, serial
(`fileParallelism: false`). Helpers in `apps/api/test/helpers.ts`: `createTestUser`,
`authToken`, `bearer`, `seedActivity`. The DB is reset **once per run, not per file**, so
domain state accumulates across files — this already caused a "test bleed" bug (tests had
to scope queries to avoid each other's rows). This fragility is fixed as part of this work.
- **Client harness:** jsdom + Testing Library, per-workspace `src/test/setup.ts`.
## Work
### 1. Coverage tooling (first — turns guesswork into a measured gap list)
- Add `@vitest/coverage-v8` (dev) + a `test:coverage` script to all three workspaces.
- Config per workspace: include `src/**`; exclude tests, config files, and generated /
non-logic files (`src/db/schema.ts`, `*.d.ts`, `main.tsx`, `vite-env.d.ts`).
- Record a **baseline coverage % per domain** so every gap filled is measured, not guessed.
- **Working target: near-100% per domain.** Drive each domain's coverage up until the only
remainder is genuinely defensive / unreachable code, which is annotated (`/* c8 ignore */`
with a reason) rather than left as a silent gap. A CI *gate* is still deferred — enforce by
review for now.
### 2. Fix the API harness fragility (prerequisite for reliable domain isolation)
- Add a `resetDb()` helper (truncate `work_sessions`, `activities`, and the better-auth
tables `session`, `account`, `user`, `verification`) usable in `beforeEach` for domain
suites, so each domain's tests are isolated instead of inheriting accumulated state.
- Migration still runs once per run (top-level, before the DB file is opened/locked — the
existing Windows EBUSY constraint in `setup.ts` stays respected).
- Domain suites that adopt `resetDb()` can then assert exact counts without dodging other
suites' rows. Existing suites are migrated to it where it removes a scoping workaround.
### 3. Per-domain audit + fill (equal depth across all three layers)
For **each** domain: enumerate guaranteed behaviors (happy path + edges) → diff against
existing tests + coverage → add missing **edge / error / authz / regression** tests → add
**one full-lifecycle integration test** (§4) → record the domain's behavior checklist (§6).
**API (7 domains):**
- **Auth & `/me`** — sign-in/out, bearer token, `/api/me` role field, CORS.
- **Activities** — CRUD, admin-gated writes, reorder, `insole_types` subset handling.
- **Work-sessions (worker)** — start / pause / resume / stop / discard, active list,
history, worked-time-excludes-paused, per-user scoping (a worker never sees another's).
- **Worker export / CSV** — `/api/export`, `buildSessionsCsv` byte-shape.
- **Admin sessions** — cross-user views, active view, manual create/edit, stop/discard,
users roster.
- **Reports + admin export** — `/api/admin/report` aggregation & filters; `/api/admin/export`
all-users CSV.
- **Admin users** — create / set role / reset password / deactivate / reactivate + lockout
guards (no self-deactivate/demote, last-active-admin invariant, banned-admin gate).
**Worker (5 domains):** Auth (context / storage / login) · Stopwatch lifecycle + server
reconcile · History · Account / logout · lib (`api`/`ApiError`, `stopwatch` math).
**Admin (6 domains):** Auth (admin-only gate) · Live view · Sessions + SessionForm ·
Reports + date-range · Users · Activities.
### 4. Lifecycle integration tests (where interaction bugs hide)
One per domain, walking the real workflow. Anchor examples:
- **Worker session (API):** seed activity → start → pause → resume → stop → assert
worked-time excludes paused → appears in history → CSV row correct.
- **Admin ↔ worker (API):** worker starts → admin stops it → session lands in report +
export with correct totals.
- **User lifecycle (API):** admin creates worker → worker signs in → admin deactivates →
sign-in blocked **and** live bearer token revoked → reactivate → sign-in works again.
- **Stopwatch reconcile (worker):** running locally → admin stops server-side → next poll
clears the stuck timer (the reconcile path in `Stopwatch.tsx`).
### 5. Bugs → Plane
- New parent work item: **"Testing & hardening — per-domain safety net"** (project SoleLog,
id `1956d3d3-15ed-4850-93d8-96d5fb17f213`).
- One child per domain (audit+fill task), plus one child per bug found.
- Trivial / safe fixes land inline in the same pass; risky / behavior-changing fixes get
their own ticket and stay deferred, flagged explicitly.
### 6. Deliverables
- Coverage wired in all three workspaces; baseline recorded; all suites green.
- Gap tests + one lifecycle test per domain.
- `docs/` doc: **per-domain "guaranteed behaviors" checklist** — the living
definition-of-done future work checks against.
- Plane epic + per-domain tasks + logged bugs.
### 7. Execution vehicle
Large, parallelizable sweep → the implementation plan is structured to **run as a Workflow**:
fan out one agent per domain (audit + fill), verify, then synthesize — rather than a serial
slog. Bugs found during a domain's pass are reported structurally and triaged per §5.
## Out of scope
- A CI coverage-threshold gate that fails the build (deferred; the near-100% target is
enforced by review for now, not by CI).
- E2E browser automation (Playwright/Cypress) — the client integration tests stay at the
Testing-Library level.
- Load/performance testing.
- Any Phase 5 deploy work (hosting is already handled on the maintainer's machine).
## Non-negotiables (not simplified away)
- Authz / role-scoping paths get explicit tests (worker-can't-see-others, admin-only writes,
lockout guards) — these are trust boundaries.
- Error/409/401/403 paths are covered, not just happy paths.
- No test asserts against another test's accumulated rows (harness isolation, §2).