high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test file exists at `test/repositories/certification_expiry_repository_test.dart`
Test: getMentorsExpiringSoon(threshold: 30) builds a Supabase query filtering `expiry_date` between NOW() and NOW()+30 days — verified by capturing mock call arguments
Test: getMentorsExpiringSoon(threshold: 60) builds a query with the correct 60-day upper bound
Test: getMentorsExpiringSoon(threshold: 90) builds a query with the correct 90-day upper bound
Test: threshold deduplication — a mentor already notified at the 60-day threshold is excluded from the 30-day query results (hasNotificationBeenSentForThreshold check respected)
Test: empty Supabase response returns an empty list without throwing
Test: Supabase PostgrestException is caught and re-thrown as a typed CertificationExpiryRepositoryException
Test: valid JSON fixture (representing a real Supabase response row) is correctly deserialized into a CertificationExpiryRecord model
Test: JSON fixture with a null expiry_date field is handled gracefully (either rejected or treated as a special case — behavior must be documented)
Minimum 80% method coverage on CertificationExpiryRepository as reported by `flutter test --coverage`

Technical Requirements

frameworks
flutter_test
mocktail (preferred) or mockito for Supabase client mocking
Riverpod (if repository is provided via Riverpod — override in tests)
apis
Supabase Flutter SDK (mocked)
CertificationExpiryRepository public API
data models
CertificationExpiryRecord (peer_mentor_id, expiry_date, threshold_days, mentor_name)
NotificationRecord (for deduplication check interaction)
performance requirements
Full unit test suite must complete within 10 seconds
No network calls — all Supabase interactions must be mocked
security requirements
No real Supabase credentials in test files — use mock clients only
JSON fixtures must use synthetic data (fake names, fake IDs)

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The trickiest part of mocking Supabase PostgREST builders is that they return `this` (fluent API), so the mock must chain correctly. With mocktail, use `when(() => mockClient.from(any())).thenReturn(mockQueryBuilder)` and ensure `mockQueryBuilder` stubs both `.gte()` and `.lte()` to return itself. For the threshold deduplication test, the repository likely calls `NotificationRecordRepository.hasNotificationBeenSentForThreshold()` internally — mock that dependency too. Structure tests in `group()` blocks by method name: `group('getMentorsExpiringSoon', () {...})`, `group('error handling', () {...})`, `group('deserialization', () {...})`.

For the date-range assertions, avoid comparing exact DateTime values (flaky due to test execution time) — instead verify that the mock was called with arguments matching `greaterThanOrEqualTo(DateTime.now())` using a custom matcher, or inject a `Clock` abstraction into the repository.

Testing Requirements

This task is entirely test authoring. Use mocktail to create a `MockSupabaseClient` and `MockPostgrestFilterBuilder` that intercept `.from('certification_expiry_tracking').select()...` calls. Create JSON fixture files under `test/fixtures/certification_expiry/` — one for a valid single-record response, one for a multi-record response, one for an empty array, and one with a null expiry_date. Use `setUpAll` to initialize the repository with the mock client.

Verify method coverage using `flutter test --coverage && genhtml coverage/lcov.info -o coverage/html`. The 80% target applies specifically to `CertificationExpiryRepository` — not the whole test file. Document any methods deliberately excluded from coverage (e.g., private helpers) with a `// coverage:ignore-line` comment and a justification.

Component
Certification Expiry Repository
data medium
Epic Risks (3)
high impact medium prob technical

The RLS policy predicate that checks certification_expiry_date and suppression_status on every coordinator list query could cause full table scans at scale, degrading response time for coordinator contact list screens across all chapters.

Mitigation & Contingency

Mitigation: Add a partial index on (certification_expiry_date, suppression_status) filtered to active mentors. Benchmark the policy predicate against a representative data set (500+ mentors) during development using EXPLAIN ANALYZE on Supabase staging.

Contingency: If the index does not resolve the performance issue, introduce a computed boolean column is_publicly_visible that is updated by the mentor_visibility_suppressor service and indexed separately, shifting the predicate cost to write time rather than read time.

medium impact medium prob integration

FCM device tokens become invalid when users reinstall the app or switch devices. If the token management strategy does not handle token refresh reliably, notification delivery will silently fail for a significant portion of the user base without surfacing errors.

Mitigation & Contingency

Mitigation: Implement the FCM token refresh callback in the Flutter client to upsert the latest token to Supabase on every app launch. Store token with a last_refreshed_at timestamp. The FCM sender should handle UNREGISTERED error codes by deleting stale tokens.

Contingency: If token staleness becomes widespread, add a token health check that forces re-registration during the expiry check edge function run by querying mentors whose token was last refreshed more than 30 days ago and triggering a silent push to prompt re-registration.

medium impact low prob integration

The certification expiry and notification record tables may have column naming or constraint conflicts with existing tables in the peer mentor status and certification management features, causing migration failures in shared Supabase environments.

Mitigation & Contingency

Mitigation: Audit existing table schemas for user_roles, certifications, and notification tables before writing migrations. Prefix new columns with expiry_ to avoid collisions. Run migrations against a clean Supabase branch environment before merging.

Contingency: If a conflict is found post-merge, apply ALTER TABLE migrations to rename conflicting columns and issue a hotfix migration. Communicate schema changes to all dependent feature teams via a shared migration changelog.