docs: AI-optimized docs restructure (Phases 1–8) (#61)

* docs(dialog-pattern): add generic entity-dialog spec

Introduceert docs/patterns/dialog.md als bron-of-truth voor elke
create/edit/detail-dialog in Scrum4Me, ongeacht het achterliggende
dataobject. Bevat 14 secties: uitgangspunten, stack, component-
architectuur, layout, validatie, drielaagse demo-policy, submission,
dialog-gedrag, theming, footer, triggers/URL-state, per-entiteit
profile-template, out-of-scope, en een verificatie-checklist.

Registreert het patroon in CLAUDE.md "Implementatiepatronen"-tabel
zodat Claude (en mensen) de spec verplicht raadplegen voor elke
nieuwe dialog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(dialog-pattern): convert task spec + add pbi/story entity-profiles

Reduceert docs/scrum4me-task-dialog.md van 507 naar ~140 regels: alle
gedeelde regels verhuisd naar docs/patterns/dialog.md, dit document
bevat nu alleen Task-specifieke velden, URL-pattern, status-veld,
server actions, triggers en bewuste out-of-scope-keuzes.

Voegt twee nieuwe entity-profielen toe voor bestaande dialogen:
- docs/scrum4me-pbi-dialog.md (PbiDialog: state-based, code+title-rij,
  PbiStatusSelect, geen delete in v1)
- docs/scrum4me-story-dialog.md (StoryDialog: state-based, header met
  status/priority badges, inline activity-log, demo-readonly-fallback,
  inline-delete-confirm i.p.v. AlertDialog)

Beide profielen documenteren expliciet de "Bekende gaps t.o.v.
generieke spec" zodat opvolgende PR's de afwijkingen kunnen
rechtzetten of bewust kunnen accorderen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Added pdevelopment docs

* docs(plans): add docs-restructure plan for AI-optimized lookup

Audit of existing 39 doc files (~10.700 lines) and a phased restructure
proposal aimed at minimising the tokens an AI agent has to read to find
the right reference. Captures resolved decisions on language (English),
ADR template (Nygard default with MADR escape-hatch), index generator
(node script), and folder taxonomy. Proposal status — fase 1 to follow.

* docs(adr): add ADR scaffolding (templates, README, meta-ADR)

Set up docs/adr/ as the canonical home for architecture decisions:

- templates/nygard.md — default four-section format (Status, Context,
  Decision, Consequences) for one-way-door decisions.
- templates/madr.md — MADR v4 with YAML front-matter and explicit
  Considered Options for decisions where rejected alternatives matter.
- README.md — naming convention (NNNN-kebab-case), template-selection
  guidance (Nygard default; MADR for auth, queue mechanics, agent
  integration), status lifecycle, and ADR roster.
- 0000-record-architecture-decisions.md — meta-ADR establishing the
  practice itself, in Nygard format.

Backfilling existing implicit decisions (base-ui-over-radix, float
sort_order, demo-user three-layer policy, etc.) is fase 6 of the
docs-restructure plan.

* feat(docs): add docs index generator + initial INDEX.md

scripts/generate-docs-index.mjs walks docs/**/*.md, parses YAML
front-matter (or first H1 fallback) and a Nygard-style ## Status
section, then writes docs/INDEX.md with grouped tables for ADRs,
Specs, Plans (with archive subsection), Patterns, and Other.

Pure Node 20 (no external deps); idempotent — running it twice
produces byte-identical output. Excludes adr/templates/, the ADR
README, INDEX.md itself, and any *_*.md sidecar file.

Wire-up:
- package.json: docs:index → node scripts/generate-docs-index.mjs

Initial run indexed 35 docs across the existing structure; the
generated INDEX.md is committed so the table is reviewable in the
PR before hooking generation into a pre-commit step.

* chore: ignore Obsidian vault and personal sidecar files

Add .obsidian/ (Obsidian vault config) and _*.md (personal sidecar
notes) to .gitignore so the docs/ tree can serve as canonical source
of truth while still being usable as an Obsidian vault for personal
authoring. The docs index generator already excludes the same _*.md
pattern from INDEX.md.

* docs(plans): add PBI bulk-create spec for docs-restructure

Machine-parseable spec for an executor that calls the scrum4me MCP
(create_pbi → create_story → create_task) to seed the docs-restructure
work into the DB.

- Section 1 (Context) is the PBI description; serves as task-context
  via mcp__scrum4me__get_claude_context.
- Section 2 lists the 6 resolved decisions (English, MD3+styling
  merged, solo-paneel merged, .Plans archived, Nygard ADR default,
  node index script).
- Section 3 records what already shipped on this branch so the
  executor doesn't duplicate the ADR scaffolding or index generator.
- Section 4 carries the structured YAML graph: 1 PBI, 8 stories
  (one per phase), 39 tasks. product_id is REPLACE_ME — fill before
  running.
- YAML validated with PyYAML; field schema sanity-checked.

* docs(junk-cleanup): remove stub patterns/test.md

* docs(junk-cleanup): archive .Plans/ to docs/plans/archive/

* docs(front-matter): add YAML front-matter to docs/ root

* docs(front-matter): add YAML front-matter to patterns/

* docs(front-matter): add YAML front-matter to plans + agent files

* docs(index): regenerate INDEX.md after front-matter pass

* docs(naming): drop scrum4me- prefix from doc filenames

* docs(naming): lowercase API.md and MD3 filenames

* docs(naming): rename plan file to kebab-case ASCII

* docs(naming): rename middleware.md to proxy.md (next 16)

* docs(naming): polish CLAUDE.md doc-index after renames

* docs(taxonomy): scaffold topical folders under docs/

* docs(taxonomy): move spec files into docs/specs/

* docs(taxonomy): move design/api/qa/backlog/assets into folders

* docs(taxonomy): move agent-instruction-audit into decisions/

* docs(split): break architecture.md into 6 topical files

* docs(split): merge solo-paneel-spec into specs/functional.md

* docs(split): merge md3-color-scheme into design/styling

* docs(trim): extract branch/commit rules into runbook

* docs(trim): extract MCP integration into runbook

* docs(adr): add 0001-base-ui-over-radix

* docs(adr): add 0002-float-sort-order

* docs(adr): add 0003-one-branch-per-milestone

* docs(adr): add 0004-status-enum-mapping

* docs(adr): add 0005-iron-session-over-nextauth

* docs(adr): add 0006-demo-user-three-layer-policy

* docs(adr): add 0007-claude-question-channel-design

* docs(adr): add 0008-agent-instructions-in-claude-md + update README index

* docs(index): regenerate after ADR 0001-0008

* docs(glossary): add docs/glossary.md

* chore(docs): regenerate INDEX.md in pre-commit hook

* docs(readme): link INDEX + glossary + agent instructions

* feat(docs): add doc-link checker script

* chore(docs): wire docs:check-links and docs npm scripts

* ci(docs): block merge on broken doc links

* docs(links): fix broken cross-references after restructure

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Janpeter Visser 2026-05-03 03:21:59 +02:00 committed by GitHub
parent 289bcf9bf0
commit 7e45bbdbc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 12364 additions and 3154 deletions

462
docs/qa/api-test-plan.md Normal file
View file

@ -0,0 +1,462 @@
---
title: "Scrum4Me — API Test Plan"
status: active
audience: [maintainer, contributor]
language: nl
last_updated: 2026-05-03
---
# Scrum4Me — API Test Plan
**Versie:** 1.0
**Datum:** 25 april 2026
**Auteur:** Jan Peter Visser
**Status:** Draft
---
## 1. Introduction
This document describes the test plan and test planning for the Scrum4Me API endpoints — the integration surface that Claude Code and external agents use to interact with the application. It covers strategy, scope, test cases, tooling, exit criteria, and a phased execution schedule.
The API consists of 7 endpoints that form the Definition of Done checkpoint: all 7 must pass curl-level verification before the project ships.
---
## 2. Objectives
| # | Objective |
|---|---|
| O-1 | Verify that all 7 API endpoints return correct responses for valid input |
| O-2 | Verify that unauthenticated requests are rejected with 401 |
| O-3 | Verify that demo users cannot perform write operations (403) |
| O-4 | Verify that cross-user access is impossible at every endpoint |
| O-5 | Verify that all Zod validation schemas reject malformed input with 400 |
| O-6 | Verify edge cases: resource-not-found (404), empty result sets, boundary values |
| O-7 | Produce executable curl scripts that satisfy the DoD requirement |
---
## 3. Scope
### 3.1 In scope
| Endpoint | Method | Auth type | Write | Demo check |
|---|---|---|---|---|
| `/api/products` | GET | Bearer token | No | No |
| `/api/products/:id/next-story` | GET | Bearer token | No | No |
| `/api/sprints/:id/tasks` | GET | Bearer token | No | No |
| `/api/stories/:id/tasks/reorder` | PATCH | Bearer token | Yes | Yes |
| `/api/stories/:id/log` | POST | Bearer token | Yes | Yes |
| `/api/tasks/:id` | PATCH | Bearer token | Yes | Yes |
| `/api/todos` | POST | Bearer token | Yes | Yes |
### 3.2 Out of scope
- `/api/profile/avatar` (GET/POST) — session-cookie auth, separate concern
- Server Actions (`actions/*.ts`) — UI-layer, covered by separate acceptance testing
- Frontend components and pages
- Database migrations and schema changes
- Performance and load testing
---
## 4. Test Strategy
### 4.1 Layers
The strategy uses two complementary test layers. Together they satisfy both the automated regression requirement and the DoD curl requirement.
```
┌─────────────────────────────────────────────────────────────────────┐
│ Layer 1 — Vitest Unit Tests (automated, mocked) │
│ Fast, deterministic, no external DB required │
│ Covers: auth, demo block, cross-user isolation, input validation │
├─────────────────────────────────────────────────────────────────────┤
│ Layer 2 — Curl Scripts (manual, real DB) │
│ Executable against localhost:3000 with seeded test data │
│ Covers: happy paths, response shape, DoD compliance │
└─────────────────────────────────────────────────────────────────────┘
```
No Playwright, no Jest — Vitest is already configured and the existing `__tests__/api/security.test.ts` establishes the mock pattern to follow.
### 4.2 Vitest approach
All unit tests mock two dependencies only:
```typescript
vi.mock('@/lib/prisma', () => ({
prisma: { /* model methods as vi.fn() */ }
}))
vi.mock('@/lib/api-auth', () => ({
authenticateApiRequest: vi.fn()
}))
```
This approach:
- Tests route handler logic in isolation
- Keeps tests fast (no network/DB)
- Follows the pattern already validated in `security.test.ts`
- Does not fall into the mock/prod divergence trap because the mocked boundary (`authenticateApiRequest` + `prisma`) is stable and narrow
### 4.3 Curl approach
A single shell script `scripts/test-api.sh` with:
- A `TOKEN` variable set at the top (obtained from a seeded user via the UI)
- One function per endpoint, each printing pass/fail based on HTTP status
- Run order that follows the Lars flow (read → write → verify)
### 4.4 Test data
The existing seed (`prisma/seed.ts`) creates:
- `lars` — full-permission user, used as the primary test actor
- `demo` — read-only user, used for demo-block tests
A second regular user (`tester`) must be created manually (or added to the seed) to test cross-user isolation scenarios.
---
## 5. Test Cases
The following tables list every test case by endpoint. Each case has an ID, description, input, expected HTTP status, and which layer covers it.
### TC format
| Field | Meaning |
|---|---|
| ID | Unique identifier, e.g. `TC-P-01` (P = products) |
| Layer | V = Vitest, C = Curl |
| Input | What is sent |
| Expected | HTTP status + key response fields |
---
### 5.1 GET /api/products
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-P-01 | V | No token | No Authorization header | 401 |
| TC-P-02 | V | Invalid token | `Bearer invalid123` | 401 |
| TC-P-03 | V | Revoked token | Valid hash but `revoked_at` set | 401 |
| TC-P-04 | V | Valid token, owns 2 products | Valid token, 2 products in DB | 200, array of 2 |
| TC-P-05 | V | Valid token, is team member | Valid token, member of product owned by other user | 200, includes that product |
| TC-P-06 | V | Valid token, no products | Valid token, no products in DB | 200, empty array |
| TC-P-07 | V | Archived products excluded | 1 active + 1 archived product | 200, array of 1 |
| TC-P-08 | V | Cross-user: other user's products not returned | Token for user A, products owned by user B | 200, empty array |
| TC-P-09 | C | Happy path (lars) | Lars' token | 200, ≥1 product |
---
### 5.2 GET /api/products/:id/next-story
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-NS-01 | V | No token | No Authorization header | 401 |
| TC-NS-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-NS-03 | V | Product not found / not accessible | Valid token, unknown product id | 404 |
| TC-NS-04 | V | No active sprint | Valid token, product with no ACTIVE sprint | 404 |
| TC-NS-05 | V | Active sprint, no IN_SPRINT stories | Valid token, sprint exists but 0 stories | 404 |
| TC-NS-06 | V | Returns highest-priority story | Valid token, 3 IN_SPRINT stories with tasks | 200, story with tasks array |
| TC-NS-07 | V | Cross-user: other user's product | Token for user A, product owned by user B | 404 |
| TC-NS-08 | C | Happy path (lars, active sprint) | Lars' token + DevPlanner product id | 200, story object |
---
### 5.3 GET /api/sprints/:id/tasks?limit=10
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-ST-01 | V | No token | No Authorization header | 401 |
| TC-ST-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-ST-03 | V | Sprint not found / not accessible | Valid token, unknown sprint id | 404 |
| TC-ST-04 | V | Cross-user: other user's sprint | Token for user A, sprint in user B's product | 404 |
| TC-ST-05 | V | Default limit applied | No `?limit` param | 200, ≤10 tasks |
| TC-ST-06 | V | Custom limit respected | `?limit=3`, sprint has 5 tasks | 200, exactly 3 tasks |
| TC-ST-07 | V | Limit boundary: limit=1 | Sprint has multiple tasks | 200, exactly 1 task |
| TC-ST-08 | V | Sprint with 0 tasks | Valid sprint, no tasks | 200, empty array |
| TC-ST-09 | C | Happy path (lars) | Lars' token + active sprint id + `?limit=10` | 200, tasks array |
---
### 5.4 PATCH /api/stories/:id/tasks/reorder
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-RO-01 | V | No token | No Authorization header | 401 |
| TC-RO-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-RO-03 | V | Demo user | Valid token, `isDemo: true` | 403 |
| TC-RO-04 | V | Story not found / not accessible | Valid token, unknown story id | 404 |
| TC-RO-05 | V | Cross-user: other user's story | Token for user A, story in user B's product | 404 |
| TC-RO-06 | V | Empty task_ids array | `{ "task_ids": [] }` | 400 |
| TC-RO-07 | V | task_ids not array | `{ "task_ids": "abc" }` | 400 |
| TC-RO-08 | V | task_ids contains IDs from different story | Valid token, mixed-story task IDs | 400 |
| TC-RO-09 | V | Happy path | Valid token + valid task_ids in new order | 200 |
| TC-RO-10 | C | Happy path (lars) | Lars' token + story id + ordered task ids | 200 |
---
### 5.5 POST /api/stories/:id/log
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-L-01 | V | No token | No Authorization header | 401 |
| TC-L-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-L-03 | V | Demo user | Valid token, `isDemo: true` | 403 |
| TC-L-04 | V | Story not found / not accessible | Valid token, unknown story id | 404 |
| TC-L-05 | V | Cross-user: other user's story | Token for user A, story in user B's product | 404 |
| TC-L-06 | V | Missing `type` field | `{ "content": "..." }` | 400 |
| TC-L-07 | V | Unknown `type` value | `{ "type": "UNKNOWN", "content": "..." }` | 400 |
| TC-L-08 | V | IMPLEMENTATION_PLAN — missing content | `{ "type": "IMPLEMENTATION_PLAN" }` | 400 |
| TC-L-09 | V | IMPLEMENTATION_PLAN — happy path | `{ "type": "IMPLEMENTATION_PLAN", "content": "Approach: ..." }` | 201 |
| TC-L-10 | V | TEST_RESULT — missing status | `{ "type": "TEST_RESULT", "content": "..." }` | 400 |
| TC-L-11 | V | TEST_RESULT — invalid status | `{ "type": "TEST_RESULT", "content": "...", "status": "UNKNOWN" }` | 400 |
| TC-L-12 | V | TEST_RESULT — happy path PASSED | `{ "type": "TEST_RESULT", "content": "...", "status": "PASSED" }` | 201 |
| TC-L-13 | V | TEST_RESULT — happy path FAILED | `{ "type": "TEST_RESULT", "content": "...", "status": "FAILED" }` | 201 |
| TC-L-14 | V | COMMIT — missing commit_hash | `{ "type": "COMMIT", "content": "...", "commit_message": "..." }` | 400 |
| TC-L-15 | V | COMMIT — missing commit_message | `{ "type": "COMMIT", "content": "...", "commit_hash": "abc1234" }` | 400 |
| TC-L-16 | V | COMMIT — happy path | `{ "type": "COMMIT", "content": "...", "commit_hash": "abc1234", "commit_message": "feat: ..." }` | 201 |
| TC-L-17 | C | IMPLEMENTATION_PLAN (lars) | Lars' token + story id + IMPLEMENTATION_PLAN body | 201 |
| TC-L-18 | C | TEST_RESULT PASSED (lars) | Lars' token + story id + TEST_RESULT body | 201 |
| TC-L-19 | C | COMMIT (lars) | Lars' token + story id + COMMIT body | 201 |
---
### 5.6 PATCH /api/tasks/:id
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-T-01 | V | No token | No Authorization header | 401 |
| TC-T-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-T-03 | V | Demo user | Valid token, `isDemo: true` | 403 |
| TC-T-04 | V | Task not found | Valid token, unknown task id | 404 |
| TC-T-05 | V | Cross-user: task in other user's product | Token for user A, task in user B's product | 404 |
| TC-T-06 | V | Invalid status value | `{ "status": "UNKNOWN" }` | 400 |
| TC-T-07 | V | Empty body (no recognized fields) | `{}` | 400 |
| TC-T-08 | V | Update status only | `{ "status": "IN_PROGRESS" }` | 200 |
| TC-T-09 | V | Update implementation_plan only | `{ "implementation_plan": "Step 1: ..." }` | 200 |
| TC-T-10 | V | Update both fields | `{ "status": "DONE", "implementation_plan": "..." }` | 200 |
| TC-T-11 | V | Team member can update task | Token for team member (not owner), valid task | 200 |
| TC-T-12 | C | Update status to IN_PROGRESS (lars) | Lars' token + task id + `{ "status": "IN_PROGRESS" }` | 200 |
| TC-T-13 | C | Update status to DONE (lars) | Lars' token + task id + `{ "status": "DONE" }` | 200 |
---
### 5.7 POST /api/todos
| ID | Layer | Scenario | Input | Expected |
|---|---|---|---|---|
| TC-TD-01 | V | No token | No Authorization header | 401 |
| TC-TD-02 | V | Invalid token | `Bearer invalid` | 401 |
| TC-TD-03 | V | Demo user | Valid token, `isDemo: true` | 403 |
| TC-TD-04 | V | Missing title | `{ "product_id": "..." }` | 400 |
| TC-TD-05 | V | Empty title | `{ "title": "" }` | 400 |
| TC-TD-06 | V | Without product_id (global todo) | `{ "title": "My todo" }` | 201 |
| TC-TD-07 | V | With valid product_id | `{ "title": "My todo", "product_id": "..." }` | 201 |
| TC-TD-08 | V | With product_id not accessible to user | `{ "title": "...", "product_id": "<other user's product>" }` | 403 or 404 |
| TC-TD-09 | C | Happy path without product (lars) | Lars' token + `{ "title": "Test todo" }` | 201 |
| TC-TD-10 | C | Happy path with product (lars) | Lars' token + `{ "title": "...", "product_id": "..." }` | 201 |
---
## 6. Test Files
| File | Endpoints covered | Layer |
|---|---|---|
| `__tests__/api/security.test.ts` | products (GET), tasks/:id (PATCH) — already exists, extend | V |
| `__tests__/api/products.test.ts` | GET /api/products — happy paths + edge cases | V |
| `__tests__/api/next-story.test.ts` | GET /api/products/:id/next-story | V |
| `__tests__/api/sprint-tasks.test.ts` | GET /api/sprints/:id/tasks | V |
| `__tests__/api/story-log.test.ts` | POST /api/stories/:id/log | V |
| `__tests__/api/reorder.test.ts` | PATCH /api/stories/:id/tasks/reorder | V |
| `__tests__/api/tasks.test.ts` | PATCH /api/tasks/:id | V |
| `__tests__/api/todos.test.ts` | POST /api/todos | V |
| `scripts/test-api.sh` | All 7 endpoints | C |
---
## 7. Exit Criteria
The test phase is complete when all of the following are met:
| Criterion | Target |
|---|---|
| All Vitest tests pass | `npm test` exits 0 |
| No test is skipped or pending | 0 skipped |
| All curl scripts return expected HTTP codes | 100% pass rate on `scripts/test-api.sh` |
| Demo user blocked on all 4 write endpoints | Verified via TC-RO-03, TC-L-03, TC-T-03, TC-TD-03 |
| Cross-user access impossible | Verified via TC-P-08, TC-NS-07, TC-ST-04, TC-RO-05, TC-L-05, TC-T-05, TC-TD-08 |
| Security review passed | No cross-user data leak found in any endpoint |
---
## 8. Risks & Mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Mock divergence: mocked Prisma behavior differs from real DB | Medium | High | Keep mocked boundary narrow (only `prisma.*` calls, not business logic); validate happy paths with curl against real DB |
| Seed data insufficient for cross-user tests | Medium | Medium | Add a second test user `tester` to `prisma/seed.ts` |
| API token not available during curl tests | Low | High | Document token creation step clearly in `test-api.sh` header |
| Zod schema changes break test expectations | Low | Low | Vitest tests will catch this immediately on next `npm test` run |
| Reorder scope validation not fully covered | Medium | High | TC-RO-08 explicitly tests mixed-story IDs; add to security review checklist |
---
## 9. Dependencies
- `prisma/seed.ts` must create (or document creation of) a second user for cross-user tests
- `scripts/` directory must exist before running the curl script
- A valid API token must be obtained from a seeded `lars` user session before running curl tests
- `npm test` must be runnable without a live database (all Vitest tests mock Prisma)
---
---
# Test Planning
## Overview
| Phase | What | Duration | Start | End |
|---|---|---|---|---|
| P0 | Setup & shared infrastructure | 0.5 day | 2026-04-28 | 2026-04-28 |
| P1 | Security & auth layer (Vitest) | 1 day | 2026-04-29 | 2026-04-29 |
| P2 | Per-endpoint unit tests (Vitest) | 2 days | 2026-04-30 | 2026-05-01 |
| P3 | Curl scripts | 0.5 day | 2026-05-02 | 2026-05-02 |
| P4 | Review, edge cases, DoD verification | 0.5 day | 2026-05-05 | 2026-05-05 |
Total: **4.5 days**
---
## Phase 0 — Setup & Infrastructure (2026-04-28)
**Goal:** Everything required to write and run tests is in place.
### Tasks
| ID | Task | Deliverable |
|---|---|---|
| P0-1 | Add `tester` user to `prisma/seed.ts` with no shared products | Updated seed file |
| P0-2 | Create `scripts/` directory with `test-api.sh` skeleton (TOKEN var, helper functions, empty cases) | `scripts/test-api.sh` |
| P0-3 | Verify `npm test` runs cleanly on `security.test.ts` | Green CI |
| P0-4 | Create `__tests__/api/` test file skeletons (empty describe blocks) for each new file | 7 new `.test.ts` files |
**Done when:** `npm test` passes, all skeleton files exist, seed creates 3 users (demo, lars, tester).
---
## Phase 1 — Security & Auth Layer (2026-04-29)
**Goal:** All auth (401), demo-block (403), and cross-user isolation test cases pass for all endpoints.
### Tasks
| ID | Task | Test cases | File |
|---|---|---|---|
| P1-1 | Extend `security.test.ts` — add missing endpoints to auth/demo/cross-user coverage | TC-NS-0103,07 / TC-ST-0104 / TC-RO-0105 / TC-L-0105 / TC-TD-0103,08 | `security.test.ts` |
| P1-2 | Verify all 401 cases return `{ error: 'Unauthorized' }` | TC-P-0103 etc. | `security.test.ts` |
| P1-3 | Verify all 403 demo cases return `{ error: 'Niet beschikbaar in demo-modus' }` | TC-RO-03, TC-L-03, TC-T-03, TC-TD-03 | `security.test.ts` |
| P1-4 | Verify cross-user returns empty array or 404 (not 403) for read endpoints | TC-P-08, TC-NS-07, TC-ST-04 | `security.test.ts` |
**Done when:** All P1 test cases green, 0 skipped. Security test file covers all 7 endpoints.
---
## Phase 2 — Per-Endpoint Unit Tests (2026-04-30 2026-05-01)
**Goal:** Happy paths, input validation, and edge cases covered per endpoint.
### Day 1 (2026-04-30) — Read endpoints
| ID | Task | Test cases | File |
|---|---|---|---|
| P2-1 | `products.test.ts` — happy paths, empty result, archived filter | TC-P-0407,09 | `products.test.ts` |
| P2-2 | `next-story.test.ts` — happy path, no sprint, no stories | TC-NS-0406,08 | `next-story.test.ts` |
| P2-3 | `sprint-tasks.test.ts` — happy path, limit param, empty sprint | TC-ST-0509 | `sprint-tasks.test.ts` |
### Day 2 (2026-05-01) — Write endpoints
| ID | Task | Test cases | File |
|---|---|---|---|
| P2-4 | `story-log.test.ts` — all 3 log types, each field validation | TC-L-0616 | `story-log.test.ts` |
| P2-5 | `reorder.test.ts` — happy path, empty array, mixed-story IDs | TC-RO-0609 | `reorder.test.ts` |
| P2-6 | `tasks.test.ts` — status update, plan update, both, invalid status, team member access | TC-T-0611 | `tasks.test.ts` |
| P2-7 | `todos.test.ts` — with/without product_id, empty title | TC-TD-0407 | `todos.test.ts` |
**Done when:** All Vitest files pass, `npm test` exits 0 with ≥60 test cases across all files.
---
## Phase 3 — Curl Scripts (2026-05-02)
**Goal:** `scripts/test-api.sh` covers all 7 endpoints and all curl test cases pass against localhost.
### Tasks
| ID | Task | Test cases | |
|---|---|---|---|
| P3-1 | Implement curl script for GET endpoints (products, next-story, sprint-tasks) | TC-P-09, TC-NS-08, TC-ST-09 | |
| P3-2 | Implement curl script for write endpoints (reorder, log ×3, tasks ×2, todos ×2) | TC-RO-10, TC-L-1719, TC-T-1213, TC-TD-0910 | |
| P3-3 | Add negative cases to curl script: no token (401), demo token (403) | TC-P-01, TC-TD-03 | |
| P3-4 | Run full script against seeded local DB, fix any failures | All C cases | |
| P3-5 | Document token acquisition steps in `scripts/README.md` or script header | | |
**Done when:** `bash scripts/test-api.sh` prints all PASS, no failures.
---
## Phase 4 — Review & DoD Verification (2026-05-05)
**Goal:** Test suite is complete, DoD exit criteria are all met.
### Tasks
| ID | Task |
|---|---|
| P4-1 | Run full `npm test` — verify 0 failures, 0 skipped |
| P4-2 | Run `scripts/test-api.sh` against staging/Neon DB — verify all PASS |
| P4-3 | Walk through security review checklist (cross-user access per endpoint) |
| P4-4 | Verify demo user is blocked on all 4 write endpoints via curl |
| P4-5 | Update `__tests__/lars-flow-checklist.md` to reference the new curl script |
| P4-6 | Add test instructions to README (`npm test`, `bash scripts/test-api.sh`) |
| P4-7 | Commit test files per commit strategy (separate commits per layer) |
**Done when:** All exit criteria from section 7 are met. Test plan status → `Approved`.
---
## Commit Plan
Following the strict commit strategy, test work is committed in these layers:
```
chore(tests): add tester user to prisma seed
test(security): extend security.test.ts to cover all 7 endpoints
test(products): add unit tests for GET /api/products
test(next-story): add unit tests for GET /api/products/:id/next-story
test(sprint-tasks): add unit tests for GET /api/sprints/:id/tasks
test(story-log): add unit tests for POST /api/stories/:id/log
test(reorder): add unit tests for PATCH /api/stories/:id/tasks/reorder
test(tasks): add unit tests for PATCH /api/tasks/:id
test(todos): add unit tests for POST /api/todos
chore(scripts): add test-api.sh curl test script
docs(tests): update README with test instructions
```
---
## Summary Timeline
```
Week of 2026-04-28
Mon 28 Apr │ P0 — Setup & infrastructure
Tue 29 Apr │ P1 — Security & auth layer (Vitest)
Wed 30 Apr │ P2 Day 1 — Read endpoint unit tests
Thu 01 May │ P2 Day 2 — Write endpoint unit tests
Fri 02 May │ P3 — Curl scripts
Week of 2026-05-05
Mon 05 May │ P4 — Review, DoD verification, commit
```