Skip to content

Latest commit

 

History

History
379 lines (280 loc) · 11.7 KB

File metadata and controls

379 lines (280 loc) · 11.7 KB

Phase 4: Contract Tests - Complete

Summary

Phase 4 of the edge functions testing plan has been successfully completed. Database schema contract tests have been implemented to catch schema drift issues before they cause production failures.

Result: ✅ 86/86 tests passing (100% success rate, +19 tests from Phase 4)


What Was Accomplished

1. Created Comprehensive Schema Contract Tests

File Created: supabase/functions/_shared/schema-contracts.test.ts (19 tests)

Purpose: Verify that data transformers produce records matching the actual database schema. This prevents silent failures when code references columns that don't exist or have been renamed.


Test Coverage

Contract Tests by Category (19 tests total)

1. Daily Summary Table Contracts (3 tests)

  • ✅ Has device_id (not tracker_id) - REGRESSION TEST
  • ✅ Has all required fields (device_id, activity_date, vendor_metadata)
  • ✅ Allows null for optional numeric fields (steps, distance_m, etc.)

Why This Matters:

  • The tracker_id → device_id rename was a production bug
  • Ensures transformers produce valid records for database inserts
  • Validates that 0 is preserved (not converted to null)

2. Sleep Sessions Table Contracts (3 tests)

  • ✅ Has device_id (not tracker_id) - REGRESSION TEST
  • ✅ Has all required fields (device_id, start_time, end_time, total_duration_minutes, vendor_metadata)
  • ✅ Handles naps (short duration sleep sessions)

Why This Matters:

  • Sleep sessions can be naps (< 3 hours) or main sleep
  • Timestamps must be ISO 8601 format
  • Duration in minutes (not seconds)

3. Activities Table Contracts (3 tests)

  • ✅ Has device_id (not tracker_id) - REGRESSION TEST
  • ✅ Has all required fields (device_id, activity_type, start_time, vendor_metadata)
  • ✅ Allows null for optional fields (duration_sec, distance_m, calories_burned)

Why This Matters:

  • Some activities (like Yoga) don't have distance
  • 0 calories is valid (different from null)
  • activity_type must be a non-empty string

4. Intraday Data Table Contracts (3 tests)

  • ✅ Has device_id (not tracker_id) - REGRESSION TEST
  • ✅ Has all required fields (device_id, timestamp)
  • ✅ Allows null for metrics (HR-only or steps-only data points)

Why This Matters:

  • Some data points only have heart rate OR steps
  • Timestamps must be precise (minute-level granularity)
  • Allows for sparse data (null is valid for missing metrics)

5. Cross-Table Consistency Tests (2 tests)

  • ✅ All fitness tables use device_id consistently - REGRESSION TEST
  • ✅ All fitness tables have vendor_metadata for extensibility

Why This Matters:

  • Ensures consistency across all fitness tracking tables
  • vendor_metadata allows provider-specific data without schema changes
  • Catches regressions where one table reverts to old column names

6. Data Type Contract Tests (3 tests)

  • ✅ Timestamps are ISO 8601 strings (not Date objects)
  • ✅ Dates are YYYY-MM-DD strings (not full timestamps)
  • ✅ Numeric fields accept 0 as valid value (not null)

Why This Matters:

  • Database expects timestamptz as ISO 8601 strings
  • activity_date is date-only (no time component)
  • CRITICAL: 0 ≠ null (0 = "measured as zero", null = "no data")

7. Schema Versioning Tests (2 tests)

  • vendor_metadata structure is documented for each provider
  • ✅ Schema uses consistent naming conventions

Why This Matters:

  • Documents expected vendor_metadata fields for WHOOP vs Fitbit
  • Validates naming conventions (FK ends with _id, timestamps end with _time, etc.)
  • Ensures future schema changes maintain consistency

Critical Bugs Prevented

Bug 1: tracker_id → device_id Rename

Impact: Production bug where code referenced old column name Fix: REGRESSION tests in all 4 table contracts Protection: 4 tests ensure we never revert to tracker_id

Bug 2: Zero Value Loss

Impact: 0 calories/steps/distance would show as "no data" Fix: Explicit tests that 0 is preserved (not converted to null) Protection: 1 test validates 0 vs null semantics across all numeric fields

Bug 3: Timestamp Format Mismatch

Impact: Database expects ISO 8601 strings, not Date objects Fix: Tests validate string format with 'T' separator and 'Z' UTC indicator Protection: 2 tests ensure correct timestamp and date formats

Bug 4: Missing Required Fields

Impact: Database inserts fail if required fields are missing Fix: Tests validate all required fields are present Protection: 4 tests (one per table) check required field presence


Test Helpers

Validation Functions

validateRequiredFields()

  • Ensures all required fields exist in a record
  • Throws descriptive error if missing
  • Used in all contract tests

validateForbiddenFields()

  • Ensures renamed/removed fields are NOT present
  • Catches regressions where old column names creep back in
  • Critical for tracker_id → device_id migration

Running Contract Tests

All Tests

cd supabase/functions
./run-tests.sh

Contract Tests Only

cd supabase/functions
deno test --allow-env --allow-net _shared/schema-contracts.test.ts

Watch Mode

deno test --allow-env --allow-net --watch _shared/schema-contracts.test.ts

Integration with Existing Tests

Updated Test Runner

run-tests.sh now includes contract tests:

Test Execution Order:

  1. Orchestrator regression tests (12 tests)
  2. WHOOP client integration tests (5 tests)
  3. Fitbit client integration tests (6 tests)
  4. Fitbit transformer unit tests (28 tests)
  5. Schema contract tests (19 tests) ← NEW
  6. WHOOP transformer unit tests (16 tests)

Total: 86 tests, ~184ms runtime


What Contract Tests Do NOT Cover

Contract tests verify structure (schema), not behavior (logic):

Not Tested by Contract Tests:

  • Unit conversions (kJ → kcal, ms → seconds)
  • Data transformations (API response → database record)
  • Business logic (deduplication, nap detection, sport mapping)
  • API calls or token refresh

Already Tested by Other Phases:

  • Phase 1 (Transformer Unit Tests) - Covers all transformations and conversions
  • Phase 2 (Integration Tests) - Covers API clients and retry logic
  • Phase 3 (Orchestrator Regression Tests) - Covers function-to-function auth

Contract tests complement, not replace, existing tests.


Example Contract Violations

Violation 1: Using tracker_id Instead of device_id

// ❌ WRONG - Will fail contract test
const record = {
  tracker_id: "device-123",  // OLD COLUMN NAME
  activity_date: "2026-02-03",
  steps: 10000,
};

// ✅ CORRECT - Passes contract test
const record = {
  device_id: "device-123",   // NEW COLUMN NAME
  activity_date: "2026-02-03",
  steps: 10000,
};

Violation 2: Missing Required Fields

// ❌ WRONG - Missing vendor_metadata
const record = {
  device_id: "device-123",
  activity_date: "2026-02-03",
  steps: 10000,
  // Missing vendor_metadata - database insert will fail
};

// ✅ CORRECT - Includes vendor_metadata
const record = {
  device_id: "device-123",
  activity_date: "2026-02-03",
  steps: 10000,
  vendor_metadata: {},  // Required, even if empty
};

Violation 3: Using Date Objects Instead of Strings

// ❌ WRONG - Date object
const record = {
  device_id: "device-123",
  start_time: new Date("2026-02-03T08:00:00.000Z"),  // Date object
};

// ✅ CORRECT - ISO 8601 string
const record = {
  device_id: "device-123",
  start_time: "2026-02-03T08:00:00.000Z",  // String
};

Violation 4: Converting 0 to null

// ❌ WRONG - Loses zero values
const calories = record.kilojoule ? Math.round(record.kilojoule * 0.239) : null;
// If kilojoule is 0, this returns null (data loss)

// ✅ CORRECT - Preserves zero values
const calories = record.kilojoule != null ? Math.round(record.kilojoule * 0.239) : null;
// If kilojoule is 0, this returns 0 (correct)

Naming Conventions Enforced

Contract tests validate these naming conventions:

  1. Foreign Keys: End with _id

    • device_id, user_id, cycle_id
    • device, tracker
  2. Timestamps: End with _time or _at

    • start_time, end_time, created_at
    • start, end, created
  3. Durations: End with _minutes or _sec

    • total_duration_minutes, duration_sec
    • duration, time
  4. Distances: End with _m (meters)

    • distance_m
    • distance, distance_km
  5. Dates: End with _date

    • activity_date
    • date, day

Performance

  • Runtime: ~10ms for all 19 contract tests
  • Fast enough for: Pre-commit hooks, CI/CD, watch mode
  • No external dependencies: All tests are pure validation

Future Contract Tests (Phase 5 - TODO)

Not Yet Implemented:

  • E2E tests with real database connection
  • Foreign key constraint validation (device_id references connected_devices)
  • Index existence checks (performance)
  • RLS policy validation (security)

Why Not Yet:

  • Requires test database setup
  • Requires Supabase client initialization
  • Higher complexity, slower execution

Current contract tests are sufficient for catching schema drift without database access.


CI/CD Integration (Already Ready)

Contract tests are included in:

  • run-tests.sh - Local test runner
  • pre-deploy.sh - Pre-deployment safety check
  • ⏸️ GitHub Actions workflow (when activated)

No additional setup required - contract tests run automatically.


Documentation References

  • Testing Plan: /docs/edge-functions-testing-plan.md - Full 4-phase plan
  • Phase 1 Complete: /docs/phase-1-transformers-complete.md - Transformer tests (44 tests)
  • Phase 2 Complete: /docs/phase-2-tests-fixed.md - Integration tests (23 tests)
  • Phase 4 Complete: This document - Contract tests (19 tests)
  • Quick Reference: /supabase/functions/TESTING.md - Commands and examples
  • CI/CD Guide: /ci-cd/EDGE-FUNCTION-TESTS.md - GitHub Actions setup

Success Metrics

Metric Target Actual Status
Test Pass Rate 100% 100% (86/86)
Contract Coverage All tables 4/4 tables
Runtime < 500ms ~184ms
Flaky Tests 0 0
Regressions Prevented N/A 4 (tracker_id, 0 values, timestamps, required fields)

Remaining Phases

Phase Status Tests
Phase 1: Transformer Unit Tests ✅ Complete 44 tests
Phase 2: Integration Tests ✅ Complete 23 tests
Phase 3: Orchestrator Regression Tests ✅ Complete (included in Phase 2)
Phase 4: Contract Tests Complete 19 tests
Phase 5: E2E Tests with Test DB ⏸️ TODO 0 tests

Total Implemented: 86 tests (Phases 1-4) Remaining: Phase 5 (E2E with database)


Conclusion

Phase 4 is now complete with:

  • 86 tests passing (23 from Phase 2 + 44 from Phase 1 + 19 from Phase 4)
  • All 4 fitness tables have contract tests
  • Fast execution (~184ms for all tests)
  • 4 critical bugs prevented (schema drift, zero values, timestamps, required fields)
  • Ready for production use

The contract tests provide a critical safety net:

  1. Preventing schema drift when columns are renamed or removed
  2. Validating data types (strings vs Date objects, 0 vs null)
  3. Ensuring required fields are never omitted
  4. Documenting schema contracts through executable tests

Phases 1-4 combined: 86 tests, 100% reliable, ready for CI/CD! 🎉


Last Updated: February 3, 2026 Status: ✅ PHASE 4 COMPLETE Next Phase: Phase 5 (E2E tests with test database) - Optional, not critical