Scrum4Me/docs/qa/api-test-plan.md

462 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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
```