refactor(04-01): move API list test stubs into tests/ mirror dir to match convention

The two Wave-0 RED stubs (lists.test.ts, listEmitter.test.ts) were co-located in
src/ but all existing API tests live in apps/api/tests/. Move them to tests/routes/
and tests/lib/, add explicit vitest imports to match the tests/ convention, and
update path references in downstream plans 04-02..04-06. PWA tests keep co-location
(that IS the PWA convention).
This commit is contained in:
Lucas Berger
2026-06-09 12:10:31 -04:00
parent 0fd4d66ee7
commit 60745b3281
11 changed files with 43 additions and 39 deletions
@@ -11,8 +11,8 @@ files_modified:
- apps/api/src/db/migrations/0002_lists_schema.sql
- apps/api/test/setup.ts
- apps/api/vitest.config.ts
- apps/api/src/routes/lists.test.ts
- apps/api/src/lib/listEmitter.test.ts
- apps/api/tests/routes/lists.test.ts
- apps/api/tests/lib/listEmitter.test.ts
- apps/pwa/src/hooks/useListSSE.test.ts
- apps/pwa/src/routes/ListDetail.test.tsx
- apps/pwa/src/App.tsx
@@ -42,7 +42,7 @@ must_haves:
- path: "apps/pwa/src/routes/ListsIndex.tsx"
provides: "Lists surface with empty state"
min_lines: 20
- path: "apps/api/src/routes/lists.test.ts"
- path: "apps/api/tests/routes/lists.test.ts"
provides: "RED test stubs for LIST-01/02/03/04 API behavior"
contains: "describe"
key_links:
@@ -109,7 +109,7 @@ Output: New deps installed; three tables migrated; API vitest harness + 4 RED st
<task type="auto">
<name>Task 2: Install new dependencies + scaffold API test harness with Wave-0 RED stubs</name>
<files>apps/pwa/package.json, apps/api/package.json, apps/api/test/setup.ts, apps/api/vitest.config.ts, apps/api/src/routes/lists.test.ts, apps/api/src/lib/listEmitter.test.ts, apps/pwa/src/hooks/useListSSE.test.ts, apps/pwa/src/routes/ListDetail.test.tsx</files>
<files>apps/pwa/package.json, apps/api/package.json, apps/api/test/setup.ts, apps/api/vitest.config.ts, apps/api/tests/routes/lists.test.ts, apps/api/tests/lib/listEmitter.test.ts, apps/pwa/src/hooks/useListSSE.test.ts, apps/pwa/src/routes/ListDetail.test.tsx</files>
<read_first>
- apps/api/vitest.config.ts
- apps/api/src/db/client.ts
@@ -123,8 +123,8 @@ Output: New deps installed; three tables migrated; API vitest harness + 4 RED st
Scaffold the API test harness: the API currently has zero test files. Create `apps/api/test/setup.ts` and reference it from `apps/api/vitest.config.ts` (`test.setupFiles`). The setup file must establish how DB-backed route tests connect — point at the local MariaDB via the existing `apps/api/src/db/client.ts` pool (DB_HOST/DB_NAME from env), and provide a per-test cleanup (truncate lists/list_items/list_shares between tests). Pure-logic tests (listEmitter, fractional rank) do NOT need the DB.
Create the four Wave-0 RED stub test files listed in 04-VALIDATION.md, each with `describe`/`it.todo` or `it(... )` blocks that compile and FAIL (red) rather than error on import — they import the not-yet-existing modules behind a guard or use `it.todo` placeholders that downstream plans convert to real assertions:
- apps/api/src/routes/lists.test.ts — LIST-01/02/03/04 API behavior stubs
- apps/api/src/lib/listEmitter.test.ts — scoped fan-out correctness (D-04) stubs
- apps/api/tests/routes/lists.test.ts — LIST-01/02/03/04 API behavior stubs
- apps/api/tests/lib/listEmitter.test.ts — scoped fan-out correctness (D-04) stubs
- apps/pwa/src/hooks/useListSSE.test.ts — D-11 bounded backoff (mock EventSource) stubs
- apps/pwa/src/routes/ListDetail.test.tsx — D-07 optimistic update + rollback stubs
</action>
@@ -248,7 +248,7 @@ Output: New deps installed; three tables migrated; API vitest harness + 4 RED st
- Tables: `lists`, `list_shares`, `list_items` (apps/api/src/db/schema.ts)
- Migration: `apps/api/src/db/migrations/0002_lists_schema.sql` (+ journal/snapshot)
- API test harness: `apps/api/test/setup.ts`; setupFiles wiring in `apps/api/vitest.config.ts`
- RED stub tests: `apps/api/src/routes/lists.test.ts`, `apps/api/src/lib/listEmitter.test.ts`, `apps/pwa/src/hooks/useListSSE.test.ts`, `apps/pwa/src/routes/ListDetail.test.tsx`
- RED stub tests: `apps/api/tests/routes/lists.test.ts`, `apps/api/tests/lib/listEmitter.test.ts`, `apps/pwa/src/hooks/useListSSE.test.ts`, `apps/pwa/src/routes/ListDetail.test.tsx`
- Components: `BottomTabBar` (apps/pwa/src/components/BottomTabBar.tsx), `ListsIndex` (apps/pwa/src/routes/ListsIndex.tsx), temporary ListDetail placeholder route element
- Store: `useListsStore` (apps/pwa/src/store/listsStore.ts) with activeTab/createListSheetOpen
- App.tsx now exports a BrowserRouter-wrapped App + AppShell
@@ -38,8 +38,8 @@ key_files:
- apps/pwa/src/store/listsStore.ts
- apps/pwa/src/api/listsClient.ts (fetchLists + List/ListItem types)
- apps/api/test/setup.ts
- apps/api/src/routes/lists.test.ts
- apps/api/src/lib/listEmitter.test.ts
- apps/api/tests/routes/lists.test.ts
- apps/api/tests/lib/listEmitter.test.ts
- apps/pwa/src/hooks/useListSSE.test.ts
- apps/pwa/src/routes/ListDetail.test.tsx
- apps/api/src/db/migrations/0002_lists_schema.sql
@@ -6,7 +6,7 @@ wave: 2
depends_on: ["04-01"]
files_modified:
- apps/api/src/lib/listEmitter.ts
- apps/api/src/lib/listEmitter.test.ts
- apps/api/tests/lib/listEmitter.test.ts
- apps/api/src/lib/listAccess.ts
- apps/api/src/lib/listAccess.test.ts
autonomous: true
@@ -26,7 +26,7 @@ must_haves:
- path: "apps/api/src/lib/listAccess.ts"
provides: "getAccessibleListIds(userId) access-scope query"
exports: ["getAccessibleListIds"]
- path: "apps/api/src/lib/listEmitter.test.ts"
- path: "apps/api/tests/lib/listEmitter.test.ts"
provides: "scoped fan-out correctness tests (D-04)"
contains: "describe"
key_links:
@@ -65,11 +65,11 @@ Output: `publishListEvent`/`subscribeListEvents` (in-memory EventEmitter singlet
<feature>
<name>Scoped in-memory list event fan-out + access-scope query (D-04)</name>
<files>
apps/api/src/lib/listEmitter.ts, apps/api/src/lib/listEmitter.test.ts,
apps/api/src/lib/listEmitter.ts, apps/api/tests/lib/listEmitter.test.ts,
apps/api/src/lib/listAccess.ts, apps/api/src/lib/listAccess.test.ts
</files>
<read_first>
- apps/api/src/lib/listEmitter.test.ts (RED stub from Plan 01 — convert to real assertions)
- apps/api/tests/lib/listEmitter.test.ts (RED stub from Plan 01 — convert to real assertions)
- apps/api/src/db/schema.ts (lists, listShares tables created in Plan 01)
- apps/api/src/routes/events.ts lines 1-110 (db query + drizzle and/or/eq conventions)
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md Finding 1 + Finding 3 (verbatim patterns)
@@ -116,7 +116,7 @@ Output: `publishListEvent`/`subscribeListEvents` (in-memory EventEmitter singlet
</threat_model>
<verification>
<automated>pnpm --filter @familysync/api exec vitest run src/lib/listEmitter.test.ts src/lib/listAccess.test.ts</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/lib/listEmitter.test.ts src/lib/listAccess.test.ts</automated>
- Test 2 (cross-list isolation) and Test 7 (private list excluded) MUST be present and green.
</verification>
@@ -131,7 +131,7 @@ Output: `publishListEvent`/`subscribeListEvents` (in-memory EventEmitter singlet
**Symbols/files this plan creates (exclude from drift verification):**
- `apps/api/src/lib/listEmitter.ts` exporting `publishListEvent(listId, event)`, `subscribeListEvents(listId, handler): () => void`, type `ListEvent`
- `apps/api/src/lib/listAccess.ts` exporting `getAccessibleListIds(userId): Promise<number[]>`
- Tests: `apps/api/src/lib/listEmitter.test.ts`, `apps/api/src/lib/listAccess.test.ts`
- Tests: `apps/api/tests/lib/listEmitter.test.ts`, `apps/api/src/lib/listAccess.test.ts`
</artifacts_produced>
<output>
@@ -6,7 +6,7 @@ wave: 2
depends_on: ["04-01"]
files_modified:
- apps/api/src/routes/lists.ts
- apps/api/src/routes/lists.test.ts
- apps/api/tests/routes/lists.test.ts
- apps/api/src/index.ts
- apps/pwa/src/api/listsClient.ts
- apps/pwa/src/routes/ListsIndex.tsx
@@ -80,10 +80,10 @@ Output: listsRouter mounted at /api/lists; ListsIndex wired to real data; Create
<task type="auto" tdd="true">
<name>Task 1: Lists router — POST/GET/PATCH/DELETE /api/lists with scoped access (LIST-01, D-01/D-02/D-04/D-06)</name>
<files>apps/api/src/routes/lists.ts, apps/api/src/routes/lists.test.ts, apps/api/src/index.ts</files>
<files>apps/api/src/routes/lists.ts, apps/api/tests/routes/lists.test.ts, apps/api/src/index.ts</files>
<read_first>
- apps/api/src/routes/events.ts (full — resolveUserId, zod schemas, handler/try-catch/401 conventions)
- apps/api/src/routes/lists.test.ts (RED stub from Plan 01)
- apps/api/tests/routes/lists.test.ts (RED stub from Plan 01)
- apps/api/src/index.ts (route mount order)
- apps/api/src/auth/user.ts (upsertUser, deriveDisplayName signatures)
- apps/api/src/db/schema.ts (lists, listShares, listItems, users)
@@ -107,7 +107,7 @@ Output: listsRouter mounted at /api/lists; ListsIndex wired to real data; Create
Mount in index.ts: `import { listsRouter }` and `app.route('/api/lists', listsRouter)` after the sseRouter mount (so it sits behind the OIDC/dev-bypass guard). Do NOT add fan-out emit calls here yet — Plan 06 adds publishListEvent triggers once the SSE endpoint exists (leave a commented seam, note it in SUMMARY). NOTE: per-field item PATCH and item endpoints are Plan 04; this plan is lists only.
</action>
<verify>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts && grep -q "app.route('/api/lists'" apps/api/src/index.ts && pnpm --filter @familysync/api typecheck</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts && grep -q "app.route('/api/lists'" apps/api/src/index.ts && pnpm --filter @familysync/api typecheck</automated>
</verify>
<acceptance_criteria>
- lists.test.ts: all create/get/delete/patch/scope tests green, including the D-04 "private list of another member is NOT returned by GET /api/lists" assertion.
@@ -176,7 +176,7 @@ Output: listsRouter mounted at /api/lists; ListsIndex wired to real data; Create
</threat_model>
<verification>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
- `playwright-cli`: create + delete a list end to end.
- D-04 negative test green.
</verification>
@@ -6,7 +6,7 @@ wave: 3
depends_on: ["04-03"]
files_modified:
- apps/api/src/routes/lists.ts
- apps/api/src/routes/lists.test.ts
- apps/api/tests/routes/lists.test.ts
- apps/api/src/lib/rank.ts
- apps/api/src/lib/rank.test.ts
- apps/pwa/src/api/listsClient.ts
@@ -77,10 +77,10 @@ Output: item endpoints on listsRouter (POST items, per-field PATCH, DELETE); ran
<task type="auto" tdd="true">
<name>Task 1: Item endpoints + fractional-rank assignment (LIST-02, D-05/D-08/D-09)</name>
<files>apps/api/src/routes/lists.ts, apps/api/src/routes/lists.test.ts, apps/api/src/lib/rank.ts, apps/api/src/lib/rank.test.ts</files>
<files>apps/api/src/routes/lists.ts, apps/api/tests/routes/lists.test.ts, apps/api/src/lib/rank.ts, apps/api/src/lib/rank.test.ts</files>
<read_first>
- apps/api/src/routes/lists.ts (listsRouter from Plan 03 — extend; access-check pattern)
- apps/api/src/routes/lists.test.ts (item stubs)
- apps/api/tests/routes/lists.test.ts (item stubs)
- apps/api/src/db/schema.ts (listItems)
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md Finding 2 (fractional-indexing API), Finding 6 (per-field PATCH zod), §"Open Questions" item 2 (uncheck rank)
- .planning/phases/04-shared-lists-live-sync/04-PATTERNS.md §"apps/api/src/routes/lists.ts" (zod patchItemSchema, ownership verification)
@@ -105,7 +105,7 @@ Output: item endpoints on listsRouter (POST items, per-field PATCH, DELETE); ran
Note the route paths: items-by-list use /:id/items (nested under lists); single-item mutations use /list-items/:itemId at the listsRouter root (matches RESEARCH architecture diagram). Mount accordingly so both resolve under /api. Do NOT add publishListEvent here — Plan 06 inserts fan-out triggers (leave a clearly commented seam after each successful write).
</action>
<verify>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts src/lib/rank.test.ts && pnpm --filter @familysync/api typecheck</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts src/lib/rank.test.ts && pnpm --filter @familysync/api typecheck</automated>
</verify>
<acceptance_criteria>
- rank.ts tests green; ordering stable.
@@ -172,7 +172,7 @@ Output: item endpoints on listsRouter (POST items, per-field PATCH, DELETE); ran
</threat_model>
<verification>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts src/lib/rank.test.ts && pnpm --filter @familysync/pwa exec vitest run src/routes/ListDetail.test.tsx</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts src/lib/rank.test.ts && pnpm --filter @familysync/pwa exec vitest run src/routes/ListDetail.test.tsx</automated>
- `playwright-cli`: add / check / delete items in a real browser.
</verification>
@@ -9,7 +9,7 @@ files_modified:
- apps/pwa/src/components/ItemRow.tsx
- apps/pwa/src/api/listsClient.ts
- apps/api/src/lib/rank.test.ts
- apps/api/src/routes/lists.test.ts
- apps/api/tests/routes/lists.test.ts
autonomous: true
requirements: [LIST-03]
user_setup: []
@@ -97,10 +97,10 @@ Output: dnd-kit DndContext/SortableContext in ListDetail; sortable ItemRow with
<task type="auto">
<name>Task 2: Strengthen server-side reorder ordering tests (LIST-03, D-13)</name>
<files>apps/api/src/lib/rank.test.ts, apps/api/src/routes/lists.test.ts</files>
<files>apps/api/src/lib/rank.test.ts, apps/api/tests/routes/lists.test.ts</files>
<read_first>
- apps/api/src/lib/rank.test.ts (from Plan 04)
- apps/api/src/routes/lists.test.ts (PATCH position coverage)
- apps/api/tests/routes/lists.test.ts (PATCH position coverage)
- .planning/phases/04-shared-lists-live-sync/04-VALIDATION.md (LIST-03 row: "PATCH new rank produces correct fractional order")
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md §"Common Pitfalls" Pitfall 2 (precision)
</read_first>
@@ -108,7 +108,7 @@ Output: dnd-kit DndContext/SortableContext in ListDetail; sortable ItemRow with
Add server-side tests proving reorder correctness: (a) repeated mid-point inserts via rankBetween produce strictly increasing distinct strings over many iterations (precision does not collapse — Pitfall 2); (b) PATCH /api/list-items/:id { position } updates only rank and a subsequent GET returns items in the new ASC order; (c) moving an item between two neighbors yields a rank strictly between theirs. These align the LIST-03 row in 04-VALIDATION.md to a green automated check. No production behavior change — Plan 04 already implements the PATCH position path.
</action>
<verify>
<automated>pnpm --filter @familysync/api exec vitest run src/lib/rank.test.ts src/routes/lists.test.ts</automated>
<automated>pnpm --filter @familysync/api exec vitest run src/lib/rank.test.ts tests/routes/lists.test.ts</automated>
</verify>
<acceptance_criteria>
- LIST-03 ordering test ("PATCH new rank produces correct fractional order") is present and green.
@@ -136,7 +136,7 @@ Output: dnd-kit DndContext/SortableContext in ListDetail; sortable ItemRow with
</threat_model>
<verification>
<automated>pnpm --filter @familysync/api exec vitest run src/lib/rank.test.ts src/routes/lists.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
<automated>pnpm --filter @familysync/api exec vitest run src/lib/rank.test.ts tests/routes/lists.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
- `playwright-cli`: drag-reorder persists across reload.
</verification>
@@ -7,7 +7,7 @@ depends_on: ["04-02", "04-04", "04-05"]
files_modified:
- apps/api/src/routes/sse.ts
- apps/api/src/routes/lists.ts
- apps/api/src/routes/lists.test.ts
- apps/api/tests/routes/lists.test.ts
- apps/pwa/src/hooks/useListSSE.ts
- apps/pwa/src/hooks/useListSSE.test.ts
- apps/pwa/src/components/LiveSyncIndicator.tsx
@@ -76,13 +76,13 @@ Output: /api/sse/lists endpoint; publishListEvent triggers in lists.ts; useListS
<task type="auto" tdd="true">
<name>Task 1: Scoped /api/sse/lists endpoint + fan-out triggers on every write (LIST-04, D-04/D-10)</name>
<files>apps/api/src/routes/sse.ts, apps/api/src/routes/lists.ts, apps/api/src/routes/lists.test.ts</files>
<files>apps/api/src/routes/sse.ts, apps/api/src/routes/lists.ts, apps/api/tests/routes/lists.test.ts</files>
<read_first>
- apps/api/src/routes/sse.ts (existing /heartbeat streamSSE pattern — extend)
- apps/api/src/routes/lists.ts (item/list write handlers from Plans 0304 — add emit seams)
- apps/api/src/lib/listEmitter.ts (publishListEvent, subscribeListEvents — Plan 02)
- apps/api/src/lib/listAccess.ts (getAccessibleListIds — Plan 02)
- apps/api/src/routes/lists.test.ts (LIST-04 stub incl. private-list no-leak)
- apps/api/tests/routes/lists.test.ts (LIST-04 stub incl. private-list no-leak)
- .planning/phases/04-shared-lists-live-sync/04-PATTERNS.md §"apps/api/src/routes/sse.ts" (the /lists endpoint pattern verbatim)
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md Finding 1 + Finding 3
</read_first>
@@ -100,7 +100,7 @@ Output: /api/sse/lists endpoint; publishListEvent triggers in lists.ts; useListS
Mount: /api/sse/lists is already under /api/sse (sseRouter mounted in index.ts) — no index.ts change needed beyond what exists. Confirm it sits behind the OIDC/dev-bypass guard.
</action>
<verify>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts && grep -q "publishListEvent" apps/api/src/routes/lists.ts && grep -q "/lists" apps/api/src/routes/sse.ts && pnpm --filter @familysync/api typecheck</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts && grep -q "publishListEvent" apps/api/src/routes/lists.ts && grep -q "/lists" apps/api/src/routes/sse.ts && pnpm --filter @familysync/api typecheck</automated>
</verify>
<acceptance_criteria>
- GET /api/sse/lists subscribes only to getAccessibleListIds channels; 401 when unauthenticated.
@@ -169,7 +169,7 @@ Output: /api/sse/lists endpoint; publishListEvent triggers in lists.ts; useListS
</threat_model>
<verification>
<automated>pnpm --filter @familysync/api exec vitest run src/routes/lists.test.ts && pnpm --filter @familysync/pwa exec vitest run src/hooks/useListSSE.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
<automated>pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts && pnpm --filter @familysync/pwa exec vitest run src/hooks/useListSSE.test.ts && pnpm --filter @familysync/pwa exec tsc --noEmit</automated>
- `playwright-cli` two-context live-update check.
- D-04 route-layer no-leak test green.
</verification>
@@ -1020,8 +1020,8 @@ useMutation({
### Wave 0 Gaps
- [ ] `apps/api/src/routes/lists.test.ts` — covers LIST-01/02/03/04 API behavior
- [ ] `apps/api/src/lib/listEmitter.test.ts` — covers scoped fan-out correctness (D-04)
- [ ] `apps/api/tests/routes/lists.test.ts` — covers LIST-01/02/03/04 API behavior
- [ ] `apps/api/tests/lib/listEmitter.test.ts` — covers scoped fan-out correctness (D-04)
- [ ] `apps/pwa/src/hooks/useListSSE.test.ts` — covers D-11 bounded backoff with mock EventSource
- [ ] `apps/pwa/src/routes/ListDetail.test.tsx` — covers optimistic update + rollback (D-07)
@@ -56,8 +56,8 @@ created: 2026-06-09
## Wave 0 Requirements
- [ ] `apps/api/src/routes/lists.test.ts` — LIST-01/02/03/04 API behavior
- [ ] `apps/api/src/lib/listEmitter.test.ts` — scoped fan-out correctness (D-04)
- [ ] `apps/api/tests/routes/lists.test.ts` — LIST-01/02/03/04 API behavior
- [ ] `apps/api/tests/lib/listEmitter.test.ts` — scoped fan-out correctness (D-04)
- [ ] `apps/pwa/src/hooks/useListSSE.test.ts` — D-11 bounded backoff with mock EventSource
- [ ] `apps/pwa/src/routes/ListDetail.test.tsx` — optimistic update + rollback (D-07)