# Testing Specification ## Job to Be Done When I make changes to the codebase, I want automated tests to catch regressions, so that I can deploy with confidence. ## Testing Strategy PhaseFlow uses **unit and integration tests** with Vitest. End-to-end tests are not required for MVP (authorized skip). ### Test Types | Type | Scope | Tools | Location | |------|-------|-------|----------| | Unit | Pure functions, utilities | Vitest | Colocated `*.test.ts` | | Integration | API routes, PocketBase interactions | Vitest + supertest | Colocated `*.test.ts` | ## Framework **Vitest** - Fast, Vite-native test runner with TypeScript support. **Configuration (`vitest.config.ts`):** ```typescript import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { globals: true, environment: 'node', include: ['src/**/*.test.ts'], coverage: { provider: 'v8', reporter: ['text', 'html'], }, }, }); ``` ## Unit Tests Test pure functions in isolation without external dependencies. ### Priority Targets | Module | Functions | Priority | |--------|-----------|----------| | `src/lib/cycle.ts` | `getCycleDay`, `getPhase`, `getPhaseConfig` | High | | `src/lib/decision-engine.ts` | `getTrainingDecision` | High | | `src/lib/nutrition.ts` | `getNutritionGuidance`, `getSeeds`, `getMacros` | Medium | | `src/lib/ics.ts` | `generatePhaseEvents`, `generateCalendarFeed` | Medium | | `src/lib/encryption.ts` | `encrypt`, `decrypt` | Medium | ### Example Test ```typescript // src/lib/cycle.test.ts import { describe, it, expect } from 'vitest'; import { getCycleDay, getPhase } from './cycle'; describe('getCycleDay', () => { it('returns 1 on period start date', () => { const lastPeriod = new Date('2024-01-01'); const today = new Date('2024-01-01'); expect(getCycleDay(lastPeriod, 31, today)).toBe(1); }); it('handles cycle rollover', () => { const lastPeriod = new Date('2024-01-01'); const today = new Date('2024-02-01'); // Day 32 expect(getCycleDay(lastPeriod, 31, today)).toBe(1); }); }); describe('getPhase', () => { it('returns MENSTRUAL for days 1-3', () => { expect(getPhase(1, 31)).toBe('MENSTRUAL'); expect(getPhase(3, 31)).toBe('MENSTRUAL'); }); it('scales correctly for 28-day cycle', () => { // LATE_LUTEAL should be days 22-28 for 28-day cycle expect(getPhase(22, 28)).toBe('LATE_LUTEAL'); expect(getPhase(21, 28)).toBe('EARLY_LUTEAL'); }); }); ``` ## Integration Tests Test API routes and PocketBase interactions. ### Setup Use a test PocketBase instance or PocketBase's testing utilities. ```typescript // src/test/setup.ts import PocketBase from 'pocketbase'; export const testPb = new PocketBase(process.env.TEST_POCKETBASE_URL); beforeAll(async () => { // Setup test data }); afterAll(async () => { // Cleanup }); ``` ### Priority Targets | Route | Tests | |-------|-------| | `GET /api/today` | Returns decision with valid auth | | `GET /api/cycle/current` | Returns correct phase info | | `POST /api/cycle/period` | Updates user record | | `GET /api/user` | Returns authenticated user | | `PATCH /api/user` | Updates user fields | | `GET /api/health` | Returns health status | ### Example Test ```typescript // src/app/api/today/route.test.ts import { describe, it, expect } from 'vitest'; import { GET } from './route'; describe('GET /api/today', () => { it('returns 401 without auth', async () => { const request = new Request('http://localhost/api/today'); const response = await GET(request); expect(response.status).toBe(401); }); it('returns decision with valid auth', async () => { // Setup authenticated request const request = new Request('http://localhost/api/today', { headers: { Cookie: 'pb_auth=...' }, }); const response = await GET(request); expect(response.status).toBe(200); const data = await response.json(); expect(data).toHaveProperty('decision'); expect(data).toHaveProperty('cycleDay'); }); }); ``` ## File Naming Tests colocated with source files: ``` src/ lib/ cycle.ts cycle.test.ts decision-engine.ts decision-engine.test.ts app/ api/ today/ route.ts route.test.ts ``` ## Running Tests ```bash # Run all tests npm test # Run with coverage npm run test:coverage # Run in watch mode npm run test:watch # Run specific file npm test -- src/lib/cycle.test.ts ``` ## Coverage Expectations No strict coverage thresholds for MVP, but aim for: - 80%+ coverage on `src/lib/` (core logic) - Key API routes tested for auth and happy path ## Success Criteria 1. All tests pass in CI before merge 2. Core decision engine logic has comprehensive tests 3. Phase scaling tested for multiple cycle lengths 4. API auth tested for protected routes ## Acceptance Tests - [ ] `npm test` runs without errors - [ ] Unit tests cover decision engine logic - [ ] Unit tests cover cycle phase calculations - [ ] Integration tests verify API authentication - [ ] Tests run in CI pipeline