---
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-01–03,07 / TC-ST-01–04 / TC-RO-01–05 / TC-L-01–05 / TC-TD-01–03,08 | `security.test.ts` |
| P1-2 | Verify all 401 cases return `{ error: 'Unauthorized' }` | TC-P-01–03 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-04–07,09 | `products.test.ts` |
| P2-2 | `next-story.test.ts` — happy path, no sprint, no stories | TC-NS-04–06,08 | `next-story.test.ts` |
| P2-3 | `sprint-tasks.test.ts` — happy path, limit param, empty sprint | TC-ST-05–09 | `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-06–16 | `story-log.test.ts` |
| P2-5 | `reorder.test.ts` — happy path, empty array, mixed-story IDs | TC-RO-06–09 | `reorder.test.ts` |
| P2-6 | `tasks.test.ts` — status update, plan update, both, invalid status, team member access | TC-T-06–11 | `tasks.test.ts` |
| P2-7 | `todos.test.ts` — with/without product_id, empty title | TC-TD-04–07 | `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-17–19, TC-T-12–13, TC-TD-09–10 | |
| 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
```
