Address 21 previously undefined behaviors across specs: - Authentication: Replace email/password with OIDC (Pocket-ID) - Cycle tracking: Add fixed-luteal phase scaling formula with examples - Calendar: Document period logging behavior (preserve predictions) - Garmin: Clarify connection is required (no phase-only mode) - Dashboard: Add UI states, dark mode, onboarding, accessibility - Notifications: Document timezone batching approach - New specs: observability.md (health, metrics, logging) - New specs: testing.md (unit + integration strategy) - Main spec: Add backup/recovery, known limitations, API updates Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
205 lines
4.9 KiB
Markdown
205 lines
4.9 KiB
Markdown
# 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
|