Document spec gaps: auth, phase scaling, observability, testing

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>
This commit is contained in:
2026-01-11 07:49:56 +00:00
parent 97a424e41d
commit 6a8d55c0b9
9 changed files with 596 additions and 29 deletions

204
specs/testing.md Normal file
View File

@@ -0,0 +1,204 @@
# 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