diff --git a/docs/superpowers/plans/2026-03-22-test-coverage-documentation.md b/docs/superpowers/plans/2026-03-22-test-coverage-documentation.md new file mode 100644 index 0000000..3fefb89 --- /dev/null +++ b/docs/superpowers/plans/2026-03-22-test-coverage-documentation.md @@ -0,0 +1,553 @@ +# Test Coverage & Documentation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Close the quality gaps identified in the tech lead assessment: add frontend page tests, JSDoc documentation, backend auth middleware tests, and E2E tests. + +**Architecture:** Frontend tests use the existing Vitest + @solidjs/testing-library + manual fetch mocking pattern (no MSW — the existing `vi.fn()` pattern works well). Backend tests use inline `#[cfg(test)]` modules. E2E tests use Playwright against a Docker stack. + +**Tech Stack:** Vitest, @solidjs/testing-library, Playwright, Rust #[cfg(test)], Docker + +**Spec:** `docs/superpowers/specs/2026-03-22-test-coverage-documentation-design.md` + +**Note:** The spec called for schema.rs (6 tests) and token.rs (4 tests), but exploration revealed these already have 7 and 8 tests respectively. Only `middleware/auth.rs` (0 tests) remains as a backend gap. + +--- + +## File Map + +### New Files +- `frontend/src/__tests__/test-utils.tsx` — shared `renderWithProviders()` helper +- `frontend/src/__tests__/pages/settings.test.tsx` — Settings page tests +- `frontend/src/__tests__/pages/home.test.tsx` — Home page tests +- `frontend/src/__tests__/pages/sources.test.tsx` — Sources page tests +- `frontend/src/__tests__/pages/login.test.tsx` — Login page tests +- `frontend/src/__tests__/pages/register.test.tsx` — Register page tests +- `frontend/src/__tests__/pages/generate.test.tsx` — GenerateSynthesis page tests +- `e2e/playwright.config.ts` — Playwright config +- `e2e/package.json` — E2E dependencies +- `e2e/docker-compose.test.yml` — test Docker stack +- `e2e/seed.ts` — DB seed script +- `e2e/helpers/auth.ts` — auth helpers +- `e2e/tests/*.spec.ts` — 5 E2E test files + +### Modified Files (documentation only — no logic changes) +- `frontend/src/api/client.ts` — JSDoc +- `frontend/src/api/auth.ts` — JSDoc +- `frontend/src/api/settings.ts` — JSDoc +- `frontend/src/api/sources.ts` — JSDoc +- `frontend/src/api/syntheses.ts` — JSDoc +- `frontend/src/api/admin.ts` — JSDoc +- `frontend/src/api/config.ts` — JSDoc +- `frontend/src/api/apiKeys.ts` — JSDoc +- `frontend/src/pages/*.tsx` (all 11 pages) — JSDoc +- `frontend/src/components/*.tsx` (all 10 components) — JSDoc +- `frontend/src/utils/*.ts` (all 3 utils) — JSDoc +- `frontend/src/contexts/AuthContext.tsx` — JSDoc +- `backend/src/middleware/auth.rs` — unit tests added + +--- + +## Task 1: Backend Auth Middleware Unit Tests + +**Files:** +- Modify: `backend/src/middleware/auth.rs` + +- [ ] **Step 1: Read the current cookie extraction logic** + +Read `/Users/oabrivard/Projects/rust/ai_synth/backend/src/middleware/auth.rs` lines 34-81 to understand the `AuthUser::from_request_parts()` implementation. The cookie extraction is at lines 41-51. + +- [ ] **Step 2: Write unit tests** + +Add a `#[cfg(test)] mod tests` block at the bottom of `auth.rs`. Since the full `from_request_parts` requires a DB connection, test the cookie extraction logic as a standalone helper function. Extract the cookie parsing into a testable function if it isn't already: + +```rust +/// Extract session token from Cookie header value. +/// Returns None if the session cookie is not found. +fn extract_session_token(cookie_header: &str, cookie_name: &str) -> Option { + cookie_header + .split(';') + .map(|s| s.trim()) + .find(|s| s.starts_with(&format!("{}=", cookie_name))) + .and_then(|s| s.strip_prefix(&format!("{}=", cookie_name))) + .map(|s| s.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + + const COOKIE_NAME: &str = "session"; + + #[test] + fn test_valid_session_cookie_extracted() { + let header = "session=abc123def"; + let result = extract_session_token(header, COOKIE_NAME); + assert_eq!(result, Some("abc123def".into())); + } + + #[test] + fn test_missing_session_cookie_returns_none() { + let header = "other=value; another=thing"; + let result = extract_session_token(header, COOKIE_NAME); + assert_eq!(result, None); + } + + #[test] + fn test_multiple_cookies_correct_one_picked() { + let header = "theme=dark; session=mytoken123; lang=fr"; + let result = extract_session_token(header, COOKIE_NAME); + assert_eq!(result, Some("mytoken123".into())); + } + + #[test] + fn test_cookie_with_whitespace_trimmed() { + let header = " session=abc123 ; other=val "; + let result = extract_session_token(header, COOKIE_NAME); + assert_eq!(result, Some("abc123".into())); + } + + #[test] + fn test_empty_cookie_header_returns_none() { + let header = ""; + let result = extract_session_token(header, COOKIE_NAME); + assert_eq!(result, None); + } +} +``` + +If the current code already has a separate extraction function, add the tests to it. If the extraction is inline in `from_request_parts`, extract it first as shown above, then use it in `from_request_parts`. + +- [ ] **Step 3: Run tests** + +Run: `cd backend && cargo test --lib middleware` +Expected: 5 new tests PASS + +- [ ] **Step 4: Commit** + +```bash +cd /Users/oabrivard/Projects/rust/ai_synth +git add backend/src/middleware/auth.rs +git commit -m "test: add unit tests for auth middleware cookie extraction" +``` + +--- + +## Task 2: Frontend Test Utilities + +**Files:** +- Create: `frontend/src/__tests__/test-utils.tsx` + +- [ ] **Step 1: Create shared test utilities** + +```tsx +import { render } from '@solidjs/testing-library'; +import { Router } from '@solidjs/router'; +import type { ParentComponent } from 'solid-js'; +import { I18nProvider } from '~/i18n'; +import { ToastProvider } from '~/components/ui/Toast'; + +/** + * Wraps a component in all required providers for testing. + * Includes: Router, I18nProvider, ToastProvider. + * AuthContext is NOT included — mock it per-test as needed. + */ +export function renderWithProviders(ui: () => any) { + return render(() => ( + + + + {ui()} + + + + )); +} + +/** + * Creates a mock fetch that returns the given response for any call. + * Resets between tests via afterEach. + */ +export function mockFetch(response: any, status = 200) { + const fn = vi.fn().mockResolvedValue({ + ok: status >= 200 && status < 300, + status, + json: () => Promise.resolve(response), + text: () => Promise.resolve(JSON.stringify(response)), + headers: new Headers({ 'content-type': 'application/json' }), + }); + globalThis.fetch = fn; + return fn; +} + +/** + * Creates a mock fetch that returns different responses per URL pattern. + */ +export function mockFetchRoutes(routes: Record) { + const fn = vi.fn().mockImplementation((url: string) => { + const match = Object.entries(routes).find(([pattern]) => url.includes(pattern)); + const { body, status } = match?.[1] ?? { body: {}, status: 404 }; + return Promise.resolve({ + ok: (status ?? 200) >= 200 && (status ?? 200) < 300, + status: status ?? 200, + json: () => Promise.resolve(body), + text: () => Promise.resolve(JSON.stringify(body)), + headers: new Headers({ 'content-type': 'application/json' }), + }); + }); + globalThis.fetch = fn; + return fn; +} +``` + +- [ ] **Step 2: Verify it compiles** + +Run: `cd frontend && npx tsc --noEmit` +Expected: no errors + +- [ ] **Step 3: Commit** + +```bash +git add frontend/src/__tests__/test-utils.tsx +git commit -m "test: add shared test utilities (renderWithProviders, mockFetch)" +``` + +--- + +## Task 3: Frontend Page Tests — Home + +**Files:** +- Create: `frontend/src/__tests__/pages/home.test.tsx` + +- [ ] **Step 1: Read Home.tsx** + +Read `/Users/oabrivard/Projects/rust/ai_synth/frontend/src/pages/Home.tsx` to understand the component structure, signals, and API calls. + +- [ ] **Step 2: Write tests** + +Write ~7 tests covering: list rendering, empty state, new synthesis link, delete double-click flow, auto-cancel, in-progress banner. Mock `~/api/syntheses` module. Use `renderWithProviders` from test-utils. Use `vi.mock()` for the API module and `waitFor` for async assertions. + +- [ ] **Step 3: Run tests** + +Run: `cd frontend && npx vitest run src/__tests__/pages/home.test.tsx` +Expected: all PASS + +- [ ] **Step 4: Commit** + +```bash +git add frontend/src/__tests__/pages/home.test.tsx +git commit -m "test: add Home page interaction tests" +``` + +--- + +## Task 4: Frontend Page Tests — Settings + +**Files:** +- Create: `frontend/src/__tests__/pages/settings.test.tsx` + +- [ ] **Step 1: Read Settings.tsx** + +Read `/Users/oabrivard/Projects/rust/ai_synth/frontend/src/pages/Settings.tsx` — understand provider/model selection, rate limits, export/import, save flow. + +- [ ] **Step 2: Write tests** + +Write ~10 tests covering: form rendering with API data, save calls PUT, provider dropdown, dual model dropdowns, rate limit inputs, rate limit reset, export triggers download, import populates form. Mock `~/api/settings`, `~/api/config`, `~/api/apiKeys`. + +- [ ] **Step 3: Run tests** + +Run: `cd frontend && npx vitest run src/__tests__/pages/settings.test.tsx` +Expected: all PASS + +- [ ] **Step 4: Commit** + +```bash +git add frontend/src/__tests__/pages/settings.test.tsx +git commit -m "test: add Settings page interaction tests" +``` + +--- + +## Task 5: Frontend Page Tests — Sources + +**Files:** +- Create: `frontend/src/__tests__/pages/sources.test.tsx` + +- [ ] **Step 1: Read Sources.tsx** + +Read `/Users/oabrivard/Projects/rust/ai_synth/frontend/src/pages/Sources.tsx`. + +- [ ] **Step 2: Write tests** + +Write ~8 tests covering: list rendering, empty state, add form validation, add form submit, delete confirmation, bulk import, CSV export trigger, CSV import trigger. Mock `~/api/sources`. + +- [ ] **Step 3: Run and commit** + +Run: `cd frontend && npx vitest run src/__tests__/pages/sources.test.tsx` + +```bash +git add frontend/src/__tests__/pages/sources.test.tsx +git commit -m "test: add Sources page interaction tests" +``` + +--- + +## Task 6: Frontend Page Tests — Login + Register + +**Files:** +- Create: `frontend/src/__tests__/pages/login.test.tsx` +- Create: `frontend/src/__tests__/pages/register.test.tsx` + +- [ ] **Step 1: Read Login.tsx and Register.tsx** + +Read both files to understand Turnstile integration, form submission, success/error states, resend cooldown. + +- [ ] **Step 2: Write Login tests (~4 tests)** + +Renders email input + submit, submit calls API, success shows "check inbox", error shows message. Mock `~/api/auth`. Stub `window.turnstile` in setup. + +- [ ] **Step 3: Write Register tests (~4 tests)** + +Renders email + display name + submit, submit calls API, success shows confirmation, error shows message. + +- [ ] **Step 4: Run and commit** + +Run: `cd frontend && npx vitest run src/__tests__/pages/login.test.tsx src/__tests__/pages/register.test.tsx` + +```bash +git add frontend/src/__tests__/pages/login.test.tsx frontend/src/__tests__/pages/register.test.tsx +git commit -m "test: add Login and Register page interaction tests" +``` + +--- + +## Task 7: Frontend Page Tests — GenerateSynthesis + +**Files:** +- Create: `frontend/src/__tests__/pages/generate.test.tsx` + +- [ ] **Step 1: Read GenerateSynthesis.tsx** + +Read to understand SSE state machine, step progression, provider display, completion redirect. + +- [ ] **Step 2: Write tests (~6 tests)** + +Renders launch button with settings info, launch calls POST, progress bar updates from mocked SSE events, step checklist shows states, completion triggers redirect, error shows retry. Mock `~/api/syntheses`, `~/api/settings`, `~/api/config`. Stub `EventSource` globally. + +- [ ] **Step 3: Run and commit** + +Run: `cd frontend && npx vitest run src/__tests__/pages/generate.test.tsx` + +```bash +git add frontend/src/__tests__/pages/generate.test.tsx +git commit -m "test: add GenerateSynthesis page interaction tests" +``` + +--- + +## Task 8: Frontend JSDoc Documentation — API Layer + +**Files:** +- Modify: `frontend/src/api/client.ts` +- Modify: `frontend/src/api/auth.ts` +- Modify: `frontend/src/api/settings.ts` +- Modify: `frontend/src/api/sources.ts` +- Modify: `frontend/src/api/syntheses.ts` +- Modify: `frontend/src/api/admin.ts` +- Modify: `frontend/src/api/config.ts` +- Modify: `frontend/src/api/apiKeys.ts` + +- [ ] **Step 1: Add JSDoc to api/client.ts** + +Document: class purpose ("Fetch wrapper for authenticated API calls"), CSRF strategy ("Adds X-Requested-With header to all requests"), credential handling ("Uses same-origin credentials for session cookies"), 401 redirect ("Redirects to /login on 401 Unauthorized"), each public method. + +- [ ] **Step 2: Add JSDoc to all other API modules** + +Brief `/** ... */` on each exported function describing what endpoint it calls and what it returns. Example: +```typescript +/** POST /auth/register — Create account with email + Turnstile token. */ +``` + +- [ ] **Step 3: Verify no code changes** + +Run: `cd frontend && npx tsc --noEmit && npx vitest run` +Expected: no errors, all tests pass (docs only) + +- [ ] **Step 4: Commit** + +```bash +git add frontend/src/api/ +git commit -m "docs: add JSDoc to all frontend API modules" +``` + +--- + +## Task 9: Frontend JSDoc Documentation — Pages + Components + +**Files:** +- Modify: all files in `frontend/src/pages/`, `frontend/src/components/`, `frontend/src/utils/`, `frontend/src/contexts/` + +- [ ] **Step 1: Document pages** + +Add JSDoc to each page component. Focus on: +- `Settings.tsx` — export/import logic, provider auto-detection, rate limit null handling, dual model state +- `GenerateSynthesis.tsx` — SSE state machine, step progression, reconnection +- `Home.tsx` — delete confirmation timer pattern +- `Sources.tsx` — bulk import parsing, CSV flow +- Brief JSDoc on all other pages + +- [ ] **Step 2: Document components** + +Add JSDoc to each component. Focus on: +- `ApiKeyManager.tsx` — key CRUD flow, show/hide toggle, test button +- `Turnstile.tsx` — widget lifecycle, polling initialization, cleanup +- Brief JSDoc on all other components including ui/ + +- [ ] **Step 3: Document utilities and context** + +- `utils/sse.ts` — reconnection backoff logic, event parsing, cleanup +- `utils/dates.ts` — locale configuration +- `utils/providers.ts` — capability detection +- `contexts/AuthContext.tsx` — session check on mount, isAdmin derived signal + +- [ ] **Step 4: Verify and commit** + +Run: `cd frontend && npx tsc --noEmit && npx vitest run` + +```bash +git add frontend/src/pages/ frontend/src/components/ frontend/src/utils/ frontend/src/contexts/ +git commit -m "docs: add JSDoc to all frontend pages, components, utilities" +``` + +--- + +## Task 10: E2E Infrastructure + +**Files:** +- Create: `e2e/package.json` +- Create: `e2e/playwright.config.ts` +- Create: `e2e/docker-compose.test.yml` +- Create: `e2e/seed.ts` +- Create: `e2e/helpers/auth.ts` + +- [ ] **Step 1: Create e2e/package.json** + +```json +{ + "name": "ai-synth-e2e", + "private": true, + "scripts": { + "test": "playwright test", + "test:ui": "playwright test --ui", + "seed": "npx tsx seed.ts" + }, + "devDependencies": { + "@playwright/test": "^1.50.0", + "pg": "^8.13.0", + "tsx": "^4.21.0" + } +} +``` + +- [ ] **Step 2: Create playwright.config.ts** + +Base URL `http://localhost:8080`, 30s timeout, 1 retry, screenshots on failure, single worker (sequential for DB state). + +- [ ] **Step 3: Create docker-compose.test.yml** + +Extends main `docker-compose.yml`. Uses test Postgres with fresh DB. Sets test environment variables (TEST turnstile/resend keys). Exposes on port 8080. + +- [ ] **Step 4: Create seed.ts** + +Connects to Postgres. Creates admin user (email_verified=true, role=admin) with a known session token. Creates regular user with known session token. Both tokens stored in known-hashes table for cookie injection. + +- [ ] **Step 5: Create helpers/auth.ts** + +`loginAsAdmin(page)` — sets session cookie. `loginAsUser(page)` — sets session cookie. `registerAndVerify(page, email)` — inserts magic link token in DB, navigates to verify URL. + +- [ ] **Step 6: Install and verify** + +```bash +cd e2e && npm install && npx playwright install chromium +``` + +- [ ] **Step 7: Commit** + +```bash +git add e2e/ +git commit -m "test: add E2E infrastructure (Playwright + Docker + seed)" +``` + +--- + +## Task 11: E2E Tests — 5 Flows + +**Files:** +- Create: `e2e/tests/registration.spec.ts` +- Create: `e2e/tests/admin-providers.spec.ts` +- Create: `e2e/tests/settings.spec.ts` +- Create: `e2e/tests/sources.spec.ts` +- Create: `e2e/tests/settings-export.spec.ts` + +- [ ] **Step 1: Write registration flow** + +Navigate to /register, fill email, submit, extract token from DB, visit verify URL, assert redirected to Home with user email in navbar. + +- [ ] **Step 2: Write admin provider config flow** + +Login as admin, navigate to /admin/providers, enable provider, add model, save, verify API returns updated config. + +- [ ] **Step 3: Write settings config flow** + +Login as user, go to /settings, change theme + select provider/models, save, reload, assert values persisted. + +- [ ] **Step 4: Write sources CRUD flow** + +Login, add source, verify in list, bulk import 3, verify count = 4, delete one, verify count = 3, export CSV. + +- [ ] **Step 5: Write settings export/import flow** + +Login, configure settings, export JSON, change settings to different values, save, import exported JSON, save, reload, assert original values restored. + +- [ ] **Step 6: Run E2E (manual — requires Docker)** + +```bash +cd e2e +docker compose -f docker-compose.test.yml up -d --wait +npm run seed +npm test +docker compose -f docker-compose.test.yml down -v +``` + +- [ ] **Step 7: Commit** + +```bash +git add e2e/tests/ +git commit -m "test: add 5 E2E test flows (registration, admin, settings, sources, export)" +``` + +--- + +## Task 12: Final Verification + +- [ ] **Step 1: Full backend check** + +Run: `cd backend && cargo check && cargo test --lib` +Expected: compiles, all tests pass + +- [ ] **Step 2: Full frontend check** + +Run: `cd frontend && npx tsc --noEmit && npx vitest run` +Expected: type-checks, all tests pass (original 103 + ~39 new page tests) + +- [ ] **Step 3: Verify frontend build** + +Run: `cd frontend && npx vite build` +Expected: production build succeeds + +- [ ] **Step 4: Final commit** + +```bash +git add -A +git commit -m "test: complete test coverage and documentation improvements" +```