Unit test repositories and dispatcher
epic-scenario-based-follow-up-prompts-foundation-task-009 — Write unit and integration tests for ScenarioRuleRepository, PromptHistoryRepository, and PushNotificationDispatcher. Cover: successful rule fetch and cache hit/miss, deduplication via hasBeenDelivered returning true on duplicate idempotency key, RLS policy rejection for cross-chapter access, FCM dispatch success and failure paths, and offline cache fallback when Supabase is unreachable. Use flutter_test with mocked Supabase client and FCM stub.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Use mocktail (preferred over mockito for null-safety ergonomics) to create mock classes for SupabaseClient and its query chain. For the Supabase query builder chain (from().select().eq().single()), use a fake implementation that returns pre-configured responses — the fluent builder pattern makes deep mocking brittle, so prefer a thin wrapper interface on each repository that can be cleanly mocked. For the offline/cache fallback test, throw a SocketException from the mocked client and assert the repository returns the previously cached value. For the RLS test, return a PostgrestException with code '42501' and assert the repository maps it to RepositoryException(code: cross_chapter_access_denied).
Group tests by repository class for readability and to allow selective execution with flutter test --name.
Testing Requirements
This task IS the testing task. Organise tests into three describe groups: ScenarioRuleRepository, PromptHistoryRepository, PushNotificationDispatcher. Use mocktail to mock SupabaseClient, SupabaseQueryBuilder, and the Edge Function invoker. Each group must cover the happy path, the main failure path, and at least one edge case (cache, deduplication, offline).
Use setUp/tearDown to reset mock state. Assert both return values and side effects (e.g., delete called on mocked client). Run with flutter test --coverage and verify ScenarioRuleRepository, PromptHistoryRepository, and PushNotificationDispatcher each exceed 80% line coverage.
Supabase RLS policies for chapter-scoped rule access may interact unexpectedly with service-role keys used by the Edge Function, potentially blocking backend reads or leaking cross-chapter data.
Mitigation & Contingency
Mitigation: Write and review RLS policies in isolation with automated policy tests before merging; define a dedicated service-role bypass policy scoped to the edge function's Postgres role.
Contingency: If RLS blocks the edge function, temporarily use a bypass policy with audit logging while a permanent fix is implemented; escalate to a Supabase security review.
FCM device tokens become invalid when users reinstall the app or revoke permissions; stale tokens cause silent delivery failures that are hard to detect without explicit error handling.
Mitigation & Contingency
Mitigation: Implement token invalidation handling in PushNotificationDispatcher that removes stale tokens from the database on FCM 404/410 responses; log all delivery failures with structured output.
Contingency: If token hygiene proves unreliable, add a periodic token refresh job that re-registers all active users' tokens via the FCM registration endpoint.