Files
phaseflow/specs/testing.md
Petru Paler 6a8d55c0b9 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>
2026-01-11 07:49:56 +00:00

4.9 KiB

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):

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

// 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.

// 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

// 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

# 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