You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ai_synth/docs/superpowers/plans/2026-03-22-test-coverage-do...

554 lines
18 KiB
Markdown

# 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<String> {
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(() => (
<Router>
<I18nProvider locale="fr">
<ToastProvider>
{ui()}
</ToastProvider>
</I18nProvider>
</Router>
));
}
/**
* 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<string, { body: any; status?: number }>) {
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"
```