---
phase: 16-multi-user-data-model
plan: 04
type: execute
wave: 3
depends_on: ["16-01", "16-02"]
files_modified:
- tests/services/item.service.test.ts
- tests/services/category.service.test.ts
- tests/services/thread.service.test.ts
- tests/services/setup.service.test.ts
- tests/services/totals.test.ts
- tests/services/csv.service.test.ts
- tests/services/auth.service.test.ts
- tests/services/oauth.service.test.ts
- tests/routes/items.test.ts
- tests/routes/categories.test.ts
- tests/routes/threads.test.ts
- tests/routes/setups.test.ts
- tests/routes/auth.test.ts
- tests/routes/images.test.ts
- tests/routes/oauth.test.ts
- tests/routes/params.test.ts
- tests/mcp/tools.test.ts
autonomous: true
requirements:
- MULTI-02
- MULTI-04
- MULTI-05
must_haves:
truths:
- "All existing tests pass after updating to use { db, userId } from createTestDb"
- "Service tests pass userId to every service function call"
- "Route tests set userId on the test app context"
- "MCP tests pass userId to MCP server creation"
- "At least one cross-user isolation test exists proving User A cannot see User B's data"
artifacts:
- path: "tests/services/item.service.test.ts"
provides: "User-scoped item service tests"
contains: "userId"
- path: "tests/mcp/tools.test.ts"
provides: "User-scoped MCP tool tests"
contains: "userId"
key_links:
- from: "tests/helpers/db.ts"
to: "tests/services/*.test.ts"
via: "createTestDb returns { db, userId }"
pattern: "const \\{ db, userId \\}"
- from: "tests/services/item.service.test.ts"
to: "src/server/services/item.service.ts"
via: "passes userId to all service calls"
pattern: "getAllItems.*db.*userId"
---
Update all test files to work with the new multi-user data model: destructure { db, userId } from createTestDb(), pass userId to all service calls, set userId in route test contexts, and add cross-user isolation tests.
Purpose: Tests validate that multi-user isolation works correctly. Without updated tests, we cannot verify that MULTI-02 (cross-user isolation) is enforced. The test suite must pass green before the phase is complete.
Output: All 17 test files updated, cross-user isolation tests added, full test suite passes.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/16-multi-user-data-model/16-CONTEXT.md
@.planning/phases/16-multi-user-data-model/16-RESEARCH.md
@.planning/phases/16-multi-user-data-model/16-01-SUMMARY.md
@.planning/phases/16-multi-user-data-model/16-02-SUMMARY.md
@tests/helpers/db.ts
@tests/services/item.service.test.ts
@tests/services/category.service.test.ts
@tests/routes/items.test.ts
@tests/mcp/tools.test.ts
Test update pattern for service tests:
```typescript
// Before:
let db: any;
beforeEach(async () => { db = await createTestDb(); });
// ...
const items = await getAllItems(db);
// After:
let db: any;
let userId: number;
beforeEach(async () => { ({ db, userId } = await createTestDb()); });
// ...
const items = await getAllItems(db, userId);
```
Test update pattern for route tests:
```typescript
// Before:
const testApp = new Hono();
testApp.use("*", async (c, next) => { c.set("db", db); await next(); });
// After:
const testApp = new Hono();
testApp.use("*", async (c, next) => { c.set("db", db); c.set("userId", userId); await next(); });
```
Cross-user isolation test pattern:
```typescript
import { createSecondTestUser } from "../helpers/db";
test("user cannot see other user's items", async () => {
const userId2 = await createSecondTestUser(db);
await createItem(db, userId, { name: "User 1 Item", ... });
await createItem(db, userId2, { name: "User 2 Item", ... });
const user1Items = await getAllItems(db, userId);
const user2Items = await getAllItems(db, userId2);
expect(user1Items).toHaveLength(1);
expect(user1Items[0].name).toBe("User 1 Item");
expect(user2Items).toHaveLength(1);
expect(user2Items[0].name).toBe("User 2 Item");
});
```
Task 1: Update all service test files to pass userId
tests/services/item.service.test.ts, tests/services/category.service.test.ts, tests/services/thread.service.test.ts, tests/services/setup.service.test.ts, tests/services/totals.test.ts, tests/services/csv.service.test.ts, tests/services/auth.service.test.ts, tests/services/oauth.service.test.ts
tests/services/item.service.test.ts, tests/services/category.service.test.ts, tests/services/thread.service.test.ts, tests/services/setup.service.test.ts, tests/services/totals.test.ts, tests/services/csv.service.test.ts, tests/services/auth.service.test.ts, tests/services/oauth.service.test.ts, tests/helpers/db.ts
For EACH of the 8 service test files, apply this systematic transformation:
1. **Change createTestDb destructuring** from `db = await createTestDb()` to `({ db, userId } = await createTestDb())`. Add `userId` variable declaration alongside `db`.
2. **Add userId to every service function call** as the second argument after `db`. Go through every call to any service function in each test and add `userId`.
3. **Fix category references**: Tests that reference "Uncategorized" by hardcoded ID 1 must instead look up the category by name+userId or use the ID returned from the seeded data. The test helper already seeds Uncategorized with the test user's ID.
4. **Specific file notes:**
**item.service.test.ts** (~160 lines):
- Destructure `{ db, userId }` from createTestDb
- Every `getAllItems(db)` becomes `getAllItems(db, userId)`
- Every `getItemById(db, id)` becomes `getItemById(db, userId, id)`
- Every `createItem(db, data)` becomes `createItem(db, userId, data)`
- Every `updateItem(db, id, data)` becomes `updateItem(db, userId, id, data)`
- Every `deleteItem(db, id)` becomes `deleteItem(db, userId, id)`
- ADD a cross-user isolation test: create a second user with `createSecondTestUser(db)`, create items for each user, verify each user only sees their own items, and verify getItemById returns null for another user's item ID
**category.service.test.ts** (~97 lines):
- Same destructuring pattern
- Add userId to all category service calls
- ADD a test for composite unique constraint: two users can have categories with the same name
- Fix any hardcoded "Uncategorized" ID references
**thread.service.test.ts** (~523 lines):
- Same destructuring pattern
- Add userId to ALL thread, candidate, and resolve calls
- Candidate operations need userId (for parent thread verification)
- Thread resolution test: verify the created item has the correct userId
- ADD a cross-user isolation test for threads
**setup.service.test.ts** (~293 lines):
- Same destructuring pattern
- Add userId to all setup service calls
- syncSetupItems calls need userId
- ADD a test that verifies a user cannot add another user's items to their setup
**totals.test.ts** (~79 lines):
- Same destructuring pattern
- Add userId to getTotals calls
**csv.service.test.ts** (~196 lines):
- Same destructuring pattern
- Add userId to import/export calls
- CSV import must create categories with userId
**auth.service.test.ts** (~68 lines):
- Same destructuring pattern
- Add userId to createApiKey, listApiKeys, deleteApiKey calls
- Update verifyApiKey assertions to check for `{ userId }` return instead of boolean `true`
**oauth.service.test.ts** (~290 lines):
- Same destructuring pattern
- Add userId where needed (createTokens, verifyAccessToken)
- Update verifyAccessToken assertions to check for `{ userId }` return instead of boolean
5. **Import `createSecondTestUser`** from helpers/db.ts in files that add isolation tests (at minimum: item, category, thread, setup test files).
After all files are updated, run `bun test` to verify the full suite passes.
bun test tests/services/ 2>&1 | tail -5
- All 8 service test files use `{ db, userId } = await createTestDb()`
- Every service function call in tests includes userId argument
- `item.service.test.ts` has a cross-user isolation test using `createSecondTestUser`
- `category.service.test.ts` has a composite unique constraint test (same name, different users)
- `auth.service.test.ts` checks `verifyApiKey` returns `{ userId }` not boolean
- `oauth.service.test.ts` checks `verifyAccessToken` returns `{ userId }` not boolean
- `bun test tests/services/` passes all tests
All 8 service test files updated with userId, cross-user isolation tests added, all service tests pass
Task 2: Update route tests, MCP tests, and run full suite
tests/routes/items.test.ts, tests/routes/categories.test.ts, tests/routes/threads.test.ts, tests/routes/setups.test.ts, tests/routes/auth.test.ts, tests/routes/images.test.ts, tests/routes/oauth.test.ts, tests/routes/params.test.ts, tests/mcp/tools.test.ts
tests/routes/items.test.ts, tests/routes/categories.test.ts, tests/routes/threads.test.ts, tests/routes/setups.test.ts, tests/routes/auth.test.ts, tests/routes/images.test.ts, tests/routes/oauth.test.ts, tests/routes/params.test.ts, tests/mcp/tools.test.ts
**Route tests** (7 files):
Route tests create a test Hono app with middleware that sets `db` on context. They need TWO changes:
1. **Destructure `{ db, userId }`** from createTestDb
2. **Set userId on the test app context** in the middleware setup:
```typescript
testApp.use("*", async (c, next) => {
c.set("db", db);
c.set("userId", userId);
await next();
});
```
For each route test file, read the file first to understand its middleware setup pattern, then add `c.set("userId", userId)` to the middleware. The route handlers will then pick it up via `c.get("userId")`.
**Specific notes per file:**
**items.test.ts** (~207 lines): Set userId in context middleware. Tests should work as-is since routes now pass userId to services.
**categories.test.ts** (~91 lines): Set userId in context middleware.
**threads.test.ts** (~413 lines): Set userId in context middleware. Thread tests may reference hardcoded Uncategorized category ID -- fix these.
**setups.test.ts** (~305 lines): Set userId in context middleware.
**auth.test.ts** (~139 lines): Set userId in context middleware for API key management routes. Auth-specific routes (login, setup) may not need userId.
**images.test.ts** (~26 lines): Set userId in context middleware if needed.
**oauth.test.ts** (~443 lines): May not need userId for OAuth flow tests, but set it for consistency. OAuth token creation may need userId.
**params.test.ts** (~81 lines): Set userId in context middleware.
**MCP tests** (1 file):
**tools.test.ts** (~253 lines):
- Destructure `{ db, userId }` from createTestDb
- Update `createMcpServer(db)` calls to `createMcpServer(db, userId)`
- MCP tool calls should then work since tools now receive userId from the server
- ADD an isolation test: create data as user 1, verify MCP tools as user 2 don't return user 1's data
After ALL test files are updated, run the FULL test suite:
```bash
bun test
```
Fix any remaining failures. Common issues to watch for:
- Missing userId argument (TypeScript will flag this)
- Hardcoded category ID 1 (use the seeded category from createTestDb)
- Boolean vs object return type from verifyApiKey/verifyAccessToken
- Settings tests using single key lookup instead of composite
- Route tests not setting userId in context
Also run lint to ensure no formatting issues:
```bash
bun run lint
```
bun test 2>&1 | tail -10
- All route test files set `c.set("userId", userId)` in test middleware
- All route test files use `{ db, userId } = await createTestDb()`
- MCP tools.test.ts passes userId to createMcpServer
- MCP tools.test.ts has a cross-user isolation test
- `bun test` passes ALL tests (0 failures)
- `bun run lint` passes
All 17 test files updated, full test suite passes green, lint passes, cross-user isolation verified by tests
After all tasks complete:
1. `bun test` exits with 0 failures
2. `bun run lint` passes
3. `grep -r "createSecondTestUser" tests/` shows isolation tests exist
4. `grep -r 'c.set("userId"' tests/routes/` shows userId set in all route test middleware
5. `grep "createMcpServer(db, userId)" tests/mcp/tools.test.ts` confirms MCP test update
- Full test suite passes (`bun test` returns 0 failures)
- All service tests pass userId to every service call
- All route tests set userId in test app context
- MCP tests pass userId to createMcpServer
- Cross-user isolation tests exist for items, categories, threads, setups
- Composite unique constraint test exists for categories
- Lint passes