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:
@@ -11,8 +11,8 @@ files_modified:
|
|||||||
- apps/api/src/db/migrations/0002_lists_schema.sql
|
- apps/api/src/db/migrations/0002_lists_schema.sql
|
||||||
- apps/api/test/setup.ts
|
- apps/api/test/setup.ts
|
||||||
- apps/api/vitest.config.ts
|
- apps/api/vitest.config.ts
|
||||||
- apps/api/src/routes/lists.test.ts
|
- apps/api/tests/routes/lists.test.ts
|
||||||
- apps/api/src/lib/listEmitter.test.ts
|
- apps/api/tests/lib/listEmitter.test.ts
|
||||||
- apps/pwa/src/hooks/useListSSE.test.ts
|
- apps/pwa/src/hooks/useListSSE.test.ts
|
||||||
- apps/pwa/src/routes/ListDetail.test.tsx
|
- apps/pwa/src/routes/ListDetail.test.tsx
|
||||||
- apps/pwa/src/App.tsx
|
- apps/pwa/src/App.tsx
|
||||||
@@ -42,7 +42,7 @@ must_haves:
|
|||||||
- path: "apps/pwa/src/routes/ListsIndex.tsx"
|
- path: "apps/pwa/src/routes/ListsIndex.tsx"
|
||||||
provides: "Lists surface with empty state"
|
provides: "Lists surface with empty state"
|
||||||
min_lines: 20
|
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"
|
provides: "RED test stubs for LIST-01/02/03/04 API behavior"
|
||||||
contains: "describe"
|
contains: "describe"
|
||||||
key_links:
|
key_links:
|
||||||
@@ -109,7 +109,7 @@ Output: New deps installed; three tables migrated; API vitest harness + 4 RED st
|
|||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
<name>Task 2: Install new dependencies + scaffold API test harness with Wave-0 RED stubs</name>
|
<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>
|
<read_first>
|
||||||
- apps/api/vitest.config.ts
|
- apps/api/vitest.config.ts
|
||||||
- apps/api/src/db/client.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.
|
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:
|
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/tests/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/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/hooks/useListSSE.test.ts — D-11 bounded backoff (mock EventSource) stubs
|
||||||
- apps/pwa/src/routes/ListDetail.test.tsx — D-07 optimistic update + rollback stubs
|
- apps/pwa/src/routes/ListDetail.test.tsx — D-07 optimistic update + rollback stubs
|
||||||
</action>
|
</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)
|
- Tables: `lists`, `list_shares`, `list_items` (apps/api/src/db/schema.ts)
|
||||||
- Migration: `apps/api/src/db/migrations/0002_lists_schema.sql` (+ journal/snapshot)
|
- 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`
|
- 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
|
- 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
|
- Store: `useListsStore` (apps/pwa/src/store/listsStore.ts) with activeTab/createListSheetOpen
|
||||||
- App.tsx now exports a BrowserRouter-wrapped App + AppShell
|
- App.tsx now exports a BrowserRouter-wrapped App + AppShell
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ key_files:
|
|||||||
- apps/pwa/src/store/listsStore.ts
|
- apps/pwa/src/store/listsStore.ts
|
||||||
- apps/pwa/src/api/listsClient.ts (fetchLists + List/ListItem types)
|
- apps/pwa/src/api/listsClient.ts (fetchLists + List/ListItem types)
|
||||||
- apps/api/test/setup.ts
|
- apps/api/test/setup.ts
|
||||||
- apps/api/src/routes/lists.test.ts
|
- apps/api/tests/routes/lists.test.ts
|
||||||
- apps/api/src/lib/listEmitter.test.ts
|
- apps/api/tests/lib/listEmitter.test.ts
|
||||||
- apps/pwa/src/hooks/useListSSE.test.ts
|
- apps/pwa/src/hooks/useListSSE.test.ts
|
||||||
- apps/pwa/src/routes/ListDetail.test.tsx
|
- apps/pwa/src/routes/ListDetail.test.tsx
|
||||||
- apps/api/src/db/migrations/0002_lists_schema.sql
|
- apps/api/src/db/migrations/0002_lists_schema.sql
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ wave: 2
|
|||||||
depends_on: ["04-01"]
|
depends_on: ["04-01"]
|
||||||
files_modified:
|
files_modified:
|
||||||
- apps/api/src/lib/listEmitter.ts
|
- 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.ts
|
||||||
- apps/api/src/lib/listAccess.test.ts
|
- apps/api/src/lib/listAccess.test.ts
|
||||||
autonomous: true
|
autonomous: true
|
||||||
@@ -26,7 +26,7 @@ must_haves:
|
|||||||
- path: "apps/api/src/lib/listAccess.ts"
|
- path: "apps/api/src/lib/listAccess.ts"
|
||||||
provides: "getAccessibleListIds(userId) access-scope query"
|
provides: "getAccessibleListIds(userId) access-scope query"
|
||||||
exports: ["getAccessibleListIds"]
|
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)"
|
provides: "scoped fan-out correctness tests (D-04)"
|
||||||
contains: "describe"
|
contains: "describe"
|
||||||
key_links:
|
key_links:
|
||||||
@@ -65,11 +65,11 @@ Output: `publishListEvent`/`subscribeListEvents` (in-memory EventEmitter singlet
|
|||||||
<feature>
|
<feature>
|
||||||
<name>Scoped in-memory list event fan-out + access-scope query (D-04)</name>
|
<name>Scoped in-memory list event fan-out + access-scope query (D-04)</name>
|
||||||
<files>
|
<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
|
apps/api/src/lib/listAccess.ts, apps/api/src/lib/listAccess.test.ts
|
||||||
</files>
|
</files>
|
||||||
<read_first>
|
<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/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)
|
- 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)
|
- .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>
|
</threat_model>
|
||||||
|
|
||||||
<verification>
|
<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.
|
- Test 2 (cross-list isolation) and Test 7 (private list excluded) MUST be present and green.
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ Output: `publishListEvent`/`subscribeListEvents` (in-memory EventEmitter singlet
|
|||||||
**Symbols/files this plan creates (exclude from drift verification):**
|
**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/listEmitter.ts` exporting `publishListEvent(listId, event)`, `subscribeListEvents(listId, handler): () => void`, type `ListEvent`
|
||||||
- `apps/api/src/lib/listAccess.ts` exporting `getAccessibleListIds(userId): Promise<number[]>`
|
- `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>
|
</artifacts_produced>
|
||||||
|
|
||||||
<output>
|
<output>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ wave: 2
|
|||||||
depends_on: ["04-01"]
|
depends_on: ["04-01"]
|
||||||
files_modified:
|
files_modified:
|
||||||
- apps/api/src/routes/lists.ts
|
- 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/api/src/index.ts
|
||||||
- apps/pwa/src/api/listsClient.ts
|
- apps/pwa/src/api/listsClient.ts
|
||||||
- apps/pwa/src/routes/ListsIndex.tsx
|
- 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">
|
<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>
|
<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>
|
<read_first>
|
||||||
- apps/api/src/routes/events.ts (full — resolveUserId, zod schemas, handler/try-catch/401 conventions)
|
- 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/index.ts (route mount order)
|
||||||
- apps/api/src/auth/user.ts (upsertUser, deriveDisplayName signatures)
|
- apps/api/src/auth/user.ts (upsertUser, deriveDisplayName signatures)
|
||||||
- apps/api/src/db/schema.ts (lists, listShares, listItems, users)
|
- 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.
|
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>
|
</action>
|
||||||
<verify>
|
<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>
|
</verify>
|
||||||
<acceptance_criteria>
|
<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.
|
- 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>
|
</threat_model>
|
||||||
|
|
||||||
<verification>
|
<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.
|
- `playwright-cli`: create + delete a list end to end.
|
||||||
- D-04 negative test green.
|
- D-04 negative test green.
|
||||||
</verification>
|
</verification>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ wave: 3
|
|||||||
depends_on: ["04-03"]
|
depends_on: ["04-03"]
|
||||||
files_modified:
|
files_modified:
|
||||||
- apps/api/src/routes/lists.ts
|
- 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.ts
|
||||||
- apps/api/src/lib/rank.test.ts
|
- apps/api/src/lib/rank.test.ts
|
||||||
- apps/pwa/src/api/listsClient.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">
|
<task type="auto" tdd="true">
|
||||||
<name>Task 1: Item endpoints + fractional-rank assignment (LIST-02, D-05/D-08/D-09)</name>
|
<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>
|
<read_first>
|
||||||
- apps/api/src/routes/lists.ts (listsRouter from Plan 03 — extend; access-check pattern)
|
- 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)
|
- 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-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)
|
- .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).
|
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>
|
</action>
|
||||||
<verify>
|
<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>
|
</verify>
|
||||||
<acceptance_criteria>
|
<acceptance_criteria>
|
||||||
- rank.ts tests green; ordering stable.
|
- rank.ts tests green; ordering stable.
|
||||||
@@ -172,7 +172,7 @@ Output: item endpoints on listsRouter (POST items, per-field PATCH, DELETE); ran
|
|||||||
</threat_model>
|
</threat_model>
|
||||||
|
|
||||||
<verification>
|
<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.
|
- `playwright-cli`: add / check / delete items in a real browser.
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ files_modified:
|
|||||||
- apps/pwa/src/components/ItemRow.tsx
|
- apps/pwa/src/components/ItemRow.tsx
|
||||||
- apps/pwa/src/api/listsClient.ts
|
- apps/pwa/src/api/listsClient.ts
|
||||||
- apps/api/src/lib/rank.test.ts
|
- apps/api/src/lib/rank.test.ts
|
||||||
- apps/api/src/routes/lists.test.ts
|
- apps/api/tests/routes/lists.test.ts
|
||||||
autonomous: true
|
autonomous: true
|
||||||
requirements: [LIST-03]
|
requirements: [LIST-03]
|
||||||
user_setup: []
|
user_setup: []
|
||||||
@@ -97,10 +97,10 @@ Output: dnd-kit DndContext/SortableContext in ListDetail; sortable ItemRow with
|
|||||||
|
|
||||||
<task type="auto">
|
<task type="auto">
|
||||||
<name>Task 2: Strengthen server-side reorder ordering tests (LIST-03, D-13)</name>
|
<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>
|
<read_first>
|
||||||
- apps/api/src/lib/rank.test.ts (from Plan 04)
|
- 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-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)
|
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md §"Common Pitfalls" Pitfall 2 (precision)
|
||||||
</read_first>
|
</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.
|
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>
|
</action>
|
||||||
<verify>
|
<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>
|
</verify>
|
||||||
<acceptance_criteria>
|
<acceptance_criteria>
|
||||||
- LIST-03 ordering test ("PATCH new rank produces correct fractional order") is present and green.
|
- 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>
|
</threat_model>
|
||||||
|
|
||||||
<verification>
|
<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.
|
- `playwright-cli`: drag-reorder persists across reload.
|
||||||
</verification>
|
</verification>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ depends_on: ["04-02", "04-04", "04-05"]
|
|||||||
files_modified:
|
files_modified:
|
||||||
- apps/api/src/routes/sse.ts
|
- apps/api/src/routes/sse.ts
|
||||||
- apps/api/src/routes/lists.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.ts
|
||||||
- apps/pwa/src/hooks/useListSSE.test.ts
|
- apps/pwa/src/hooks/useListSSE.test.ts
|
||||||
- apps/pwa/src/components/LiveSyncIndicator.tsx
|
- 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">
|
<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>
|
<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>
|
<read_first>
|
||||||
- apps/api/src/routes/sse.ts (existing /heartbeat streamSSE pattern — extend)
|
- apps/api/src/routes/sse.ts (existing /heartbeat streamSSE pattern — extend)
|
||||||
- apps/api/src/routes/lists.ts (item/list write handlers from Plans 03–04 — add emit seams)
|
- apps/api/src/routes/lists.ts (item/list write handlers from Plans 03–04 — add emit seams)
|
||||||
- apps/api/src/lib/listEmitter.ts (publishListEvent, subscribeListEvents — Plan 02)
|
- apps/api/src/lib/listEmitter.ts (publishListEvent, subscribeListEvents — Plan 02)
|
||||||
- apps/api/src/lib/listAccess.ts (getAccessibleListIds — 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-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
|
- .planning/phases/04-shared-lists-live-sync/04-RESEARCH.md Finding 1 + Finding 3
|
||||||
</read_first>
|
</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.
|
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>
|
</action>
|
||||||
<verify>
|
<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>
|
</verify>
|
||||||
<acceptance_criteria>
|
<acceptance_criteria>
|
||||||
- GET /api/sse/lists subscribes only to getAccessibleListIds channels; 401 when unauthenticated.
|
- 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>
|
</threat_model>
|
||||||
|
|
||||||
<verification>
|
<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.
|
- `playwright-cli` two-context live-update check.
|
||||||
- D-04 route-layer no-leak test green.
|
- D-04 route-layer no-leak test green.
|
||||||
</verification>
|
</verification>
|
||||||
|
|||||||
@@ -1020,8 +1020,8 @@ useMutation({
|
|||||||
|
|
||||||
### Wave 0 Gaps
|
### Wave 0 Gaps
|
||||||
|
|
||||||
- [ ] `apps/api/src/routes/lists.test.ts` — covers LIST-01/02/03/04 API behavior
|
- [ ] `apps/api/tests/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/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/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)
|
- [ ] `apps/pwa/src/routes/ListDetail.test.tsx` — covers optimistic update + rollback (D-07)
|
||||||
|
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ created: 2026-06-09
|
|||||||
|
|
||||||
## Wave 0 Requirements
|
## Wave 0 Requirements
|
||||||
|
|
||||||
- [ ] `apps/api/src/routes/lists.test.ts` — LIST-01/02/03/04 API behavior
|
- [ ] `apps/api/tests/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/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/hooks/useListSSE.test.ts` — D-11 bounded backoff with mock EventSource
|
||||||
- [ ] `apps/pwa/src/routes/ListDetail.test.tsx` — optimistic update + rollback (D-07)
|
- [ ] `apps/pwa/src/routes/ListDetail.test.tsx` — optimistic update + rollback (D-07)
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
* Run: pnpm --filter @familysync/api test
|
* Run: pnpm --filter @familysync/api test
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
describe('listEmitter — scoped fan-out correctness (D-04)', () => {
|
describe('listEmitter — scoped fan-out correctness (D-04)', () => {
|
||||||
it.todo('publishListEvent emits only to subscribers for the matching listId')
|
it.todo('publishListEvent emits only to subscribers for the matching listId')
|
||||||
it.todo('publishListEvent does NOT emit to subscribers for a different listId')
|
it.todo('publishListEvent does NOT emit to subscribers for a different listId')
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
* Run: pnpm --filter @familysync/api test
|
* Run: pnpm --filter @familysync/api test
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { describe, it } from 'vitest'
|
||||||
|
|
||||||
describe('GET /api/lists — LIST-01: returns only accessible lists', () => {
|
describe('GET /api/lists — LIST-01: returns only accessible lists', () => {
|
||||||
it.todo('returns empty array when user has no lists')
|
it.todo('returns empty array when user has no lists')
|
||||||
it.todo('returns lists owned by the current user')
|
it.todo('returns lists owned by the current user')
|
||||||
Reference in New Issue
Block a user