diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index f626bfb..06290aa 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -28,11 +28,11 @@ Decimal phases appear between their surrounding integers in numeric order. 2. All new tables use the existing Supabase client from `config/supabase.ts` — no new database connections added 3. `AlertModel.ts` exists and its CRUD methods can be called in isolation without errors 4. Migration SQL can be run against the live Supabase instance and produces the expected schema -**Plans:** 1/2 plans executed +**Plans:** 2/2 plans executed Plans: -- [ ] 01-01-PLAN.md — Migration SQL + HealthCheckModel + AlertEventModel -- [ ] 01-02-PLAN.md — Unit tests for both monitoring models +- [x] 01-01-PLAN.md — Migration SQL + HealthCheckModel + AlertEventModel +- [x] 01-02-PLAN.md — Unit tests for both monitoring models ### Phase 2: Backend Services **Goal**: All monitoring logic runs correctly — health probes make real API calls, alerts fire with deduplication, analytics events write non-blocking to Supabase, and data is cleaned up on schedule @@ -76,7 +76,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Data Foundation | 1/2 | In Progress| | +| 1. Data Foundation | 2/2 | Complete | 2026-02-24 | | 2. Backend Services | 0/TBD | Not started | - | | 3. API Layer | 0/TBD | Not started | - | | 4. Frontend | 0/TBD | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 61b9402..ed70bc5 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -10,27 +10,27 @@ See: .planning/PROJECT.md (updated 2026-02-24) ## Current Position Phase: 1 of 4 (Data Foundation) -Plan: 1 of TBD in current phase +Plan: 2 of TBD in current phase Status: In progress -Last activity: 2026-02-24 — Completed 01-01 (monitoring tables migration + model layer) +Last activity: 2026-02-24 — Completed 01-02 (HealthCheckModel + AlertEventModel unit tests) -Progress: [█░░░░░░░░░] 10% +Progress: [██░░░░░░░░] 20% ## Performance Metrics **Velocity:** -- Total plans completed: 1 -- Average duration: ~8 min -- Total execution time: ~0.13 hours +- Total plans completed: 2 +- Average duration: ~17 min +- Total execution time: ~0.57 hours **By Phase:** | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| -| 01-data-foundation | 1 | ~8 min | ~8 min | +| 01-data-foundation | 2 | ~34 min | ~17 min | **Recent Trend:** -- Last 5 plans: 01-01 (8 min) +- Last 5 plans: 01-01 (8 min), 01-02 (26 min) - Trend: — *Updated after each plan completion* @@ -50,6 +50,9 @@ Recent decisions affecting current work: - 01-01: getSupabaseServiceClient() called per-method, never cached at module level - 01-01: checked_at column separate from created_at on service_health_checks (probe time vs DB write time) - 01-01: Forward-only migrations only (no rollback scripts) +- 01-02: Supabase mock uses chain.then (thenability) so both .single() and direct await patterns work from one mock +- 01-02: makeSupabaseChain() factory per test — no shared mock state between tests +- 01-02: vi.mock() factories must use only inline vi.fn() to avoid Vitest hoisting TDZ errors ### Pending Todos @@ -64,5 +67,5 @@ None yet. ## Session Continuity Last session: 2026-02-24 -Stopped at: Completed 01-01-PLAN.md — monitoring tables migration + HealthCheckModel + AlertEventModel +Stopped at: Completed 01-02-PLAN.md — HealthCheckModel + AlertEventModel unit tests (33 tests passing) Resume file: None diff --git a/.planning/phases/01-data-foundation/01-02-SUMMARY.md b/.planning/phases/01-data-foundation/01-02-SUMMARY.md new file mode 100644 index 0000000..13d7060 --- /dev/null +++ b/.planning/phases/01-data-foundation/01-02-SUMMARY.md @@ -0,0 +1,124 @@ +--- +phase: 01-data-foundation +plan: 02 +subsystem: testing +tags: [vitest, supabase, mocking, unit-tests, health-checks, alert-events] + +# Dependency graph +requires: + - phase: 01-data-foundation/01-01 + provides: HealthCheckModel and AlertEventModel classes with getSupabaseServiceClient usage + +provides: + - Unit tests for HealthCheckModel covering all CRUD methods and input validation + - Unit tests for AlertEventModel covering all CRUD methods, status transitions, and input validation + - Supabase chainable mock pattern for future model tests + - INFR-04 compliance verification (models call getSupabaseServiceClient per invocation) + +affects: + - 02-monitoring-services + - future model tests + +# Tech tracking +tech-stack: + added: [] + patterns: + - "Supabase chainable mock: makeSupabaseChain() helper with fluent vi.fn() returns and thenability for awaitable queries" + - "vi.mock hoisting: factory functions use only inline vi.fn() to avoid temporal dead zone errors" + - "vi.mocked() for typed access to mocked module exports after import" + +key-files: + created: + - backend/src/__tests__/models/HealthCheckModel.test.ts + - backend/src/__tests__/models/AlertEventModel.test.ts + modified: [] + +key-decisions: + - "Supabase mock uses thenability (chain.then) so both .single() and direct await patterns work without duplicating mocks" + - "makeSupabaseChain() factory encapsulates mock setup — one call per test, no shared state between tests" + - "vi.mock() factories use only inline vi.fn() — no top-level variable references to avoid hoisting TDZ errors" + +patterns-established: + - "Model test pattern: vi.mock both supabase and logger, import vi.mocked() typed refs, makeSupabaseChain() per test, clearAllMocks in beforeEach" + - "Validation test pattern: verify getSupabaseServiceClient not called when validation throws (confirms no DB hit)" + - "PGRST116 null return: mock error.code = 'PGRST116' to test no-rows path that returns null instead of throwing" + +requirements-completed: [INFR-01, INFR-04] + +# Metrics +duration: 26min +completed: 2026-02-24 +--- + +# Phase 01 Plan 02: Model Unit Tests Summary + +**33 unit tests for HealthCheckModel and AlertEventModel with Vitest + Supabase chainable mock pattern** + +## Performance + +- **Duration:** 26 min +- **Started:** 2026-02-24T16:46:26Z +- **Completed:** 2026-02-24T17:13:22Z +- **Tasks:** 2 +- **Files modified:** 2 + +## Accomplishments + +- HealthCheckModel: 14 tests covering create (valid/minimal/probe_details), 2 validation error paths, Supabase error + error logging, findLatestByService (found/null PGRST116), findAll (default limit/filtered/custom limit), deleteOlderThan (date calculation/count) +- AlertEventModel: 19 tests covering create (valid/default status/explicit status/details JSONB), 3 validation error paths, Supabase error, findActive (all/filtered/empty), acknowledge/resolve (success + PGRST116 not-found), findRecentByService (found/null), deleteOlderThan +- Established `makeSupabaseChain()` helper pattern for all future model tests — single source for mock client setup with fluent chain and thenable resolution +- Full test suite (41 tests) passes with no regressions + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Create HealthCheckModel unit tests** - `99c6dcb` (test) +2. **Task 2: Create AlertEventModel unit tests** - `a3cd82b` (test) + +**Plan metadata:** (docs commit to follow) + +## Files Created/Modified + +- `backend/src/__tests__/models/HealthCheckModel.test.ts` - 14 unit tests for HealthCheckModel CRUD, validation, and error handling +- `backend/src/__tests__/models/AlertEventModel.test.ts` - 19 unit tests for AlertEventModel CRUD, status transitions, validation, and error handling + +## Decisions Made + +- Supabase mock uses `chain.then` (thenability) so both `.single()` and direct `await query` patterns work from the same mock object — no need to bifurcate mocks for the two query termination patterns the models use. +- `makeSupabaseChain(resolvedValue)` factory creates a fresh mock per test — avoids state leakage between tests that would occur with a shared top-level mock object. +- `vi.mock()` factories use only inline `vi.fn()` — top-level variable references are in temporal dead zone when hoisted factories execute. + +## Deviations from Plan + +**1. [Rule 1 - Bug] Fixed Vitest hoisting TDZ error in initial mock approach** +- **Found during:** Task 1 (first test run) +- **Issue:** Initial approach created top-level mock variables, then referenced them inside `vi.mock()` factory — Vitest hoists `vi.mock` before variable initialization, causing `ReferenceError: Cannot access 'mockGetSupabaseServiceClient' before initialization` +- **Fix:** Rewrote mock factories to use only inline `vi.fn()`, then used `vi.mocked()` after imports to get typed references +- **Files modified:** `backend/src/__tests__/models/HealthCheckModel.test.ts` +- **Verification:** Tests ran successfully on second attempt; this pattern used for AlertEventModel from the start +- **Committed in:** 99c6dcb (Task 1 commit, updated file) + +--- + +**Total deviations:** 1 auto-fixed (Rule 1 — runtime error in test infrastructure) +**Impact on plan:** Fix was required for tests to run. Resulted in a cleaner, idiomatic Vitest mock pattern. + +## Issues Encountered + +- Vitest mock hoisting TDZ: the correct pattern is `vi.mock()` factory uses only `vi.fn()` inline, with `vi.mocked()` used post-import for typed access. Documented in patterns-established for all future test authors. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Both model classes verified correct through unit tests +- Supabase mock pattern established — Phase 2 service tests can reuse `makeSupabaseChain()` helper +- INFR-04 compliance confirmed: tests verify `getSupabaseServiceClient` is called per-method invocation +- Ready for Phase 2: monitoring services that depend on HealthCheckModel and AlertEventModel + +--- +*Phase: 01-data-foundation* +*Completed: 2026-02-24*