high priority medium complexity testing pending testing specialist Tier 5

Acceptance Criteria

ScenarioRuleRepository: fetch returns cached result on second call without issuing a new Supabase query (cache hit verified via call count assertion)
ScenarioRuleRepository: cache miss triggers a Supabase query and populates the cache for subsequent calls
PromptHistoryRepository: hasBeenDelivered returns true when a matching idempotency key already exists in the prompt_delivery_history table
PromptHistoryRepository: hasBeenDelivered returns false for a novel idempotency key
PromptHistoryRepository: attempting to record a duplicate idempotency key does not throw and returns a no-op result
RLS rejection test: mocked Supabase client returning a 403/RLS error results in a typed RepositoryException with code cross_chapter_access_denied — no unhandled exceptions
PushNotificationDispatcher: dispatch() with a valid token returns DispatchResult.sent and the Edge Function stub receives the correct payload
PushNotificationDispatcher: dispatch() when Edge Function returns 'invalid-registration-token' triggers token deletion on the mocked Supabase client
Offline fallback test: ScenarioRuleRepository returns stale cached rules when Supabase throws a network exception
All test files follow flutter_test conventions and are placed in test/repositories/ — no test logic in lib/
Test suite runs to completion in under 30 seconds with no flaky async assertions

Technical Requirements

frameworks
flutter_test
mockito or mocktail for Dart mocking
Riverpod (ProviderContainer for isolated provider testing)
apis
Mocked Supabase Flutter SDK client
Stubbed Supabase Edge Function HTTP client
data models
device_token
activity (for scenario rule context)
performance requirements
Full test suite completes in under 30 seconds
No real network calls — all external dependencies mocked
security requirements
Tests must verify that RLS rejections surface as typed exceptions — not swallowed silently
No real Supabase credentials or FCM keys in test code — use placeholder strings

Execution Context

Execution Tier
Tier 5

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.

Component
Prompt History Repository
data medium
Epic Risks (2)
high impact medium prob security

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.

medium impact high prob integration

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.