critical priority high complexity testing pending testing specialist Tier 5

Acceptance Criteria

Test suite passes with `dart test` / `flutter test` with zero failures and zero skips
Test: given N open assignments all returning 'remind' outcome, reminderDispatcher is called exactly N times and escalationDispatcher is called 0 times; SchedulerRunResult.totalReminded == N
Test: given N open assignments all returning 'escalate' outcome, escalationDispatcher is called exactly N times and reminderDispatcher is called 0 times; SchedulerRunResult.totalEscalated == N
Test: given N open assignments all returning 'none' outcome, neither dispatcher is called; SchedulerRunResult.totalSkipped == N
Test: given a mixed set of outcomes, dispatcher call counts match the exact distribution and SchedulerRunResult totals sum to N
Test: given 0 open assignments, both dispatchers are called 0 times and SchedulerRunResult returns zeros with no error
Test: given a large set (e.g., 500 assignments across multiple batches), all assignments are evaluated exactly once with no duplicates or omissions
Test: when the idempotency guard detects an active run, the scheduler exits immediately — assignment repository and both dispatchers receive 0 calls; return value signals duplicate-skip
Test: when a dispatcher call throws, the error is captured in SchedulerRunResult and processing continues for remaining assignments (no early exit)
All injectable dependencies (assignment repository, evaluator, reminder dispatcher, escalation dispatcher, idempotency guard) are replaced with mocks/stubs — no live Supabase calls
Code coverage for ReminderSchedulerService is ≥ 95% line and branch coverage as reported by `dart test --coverage`

Technical Requirements

frameworks
Dart
flutter_test
mockito or mocktail (Dart mocking library)
data models
Assignment
ReminderEvaluationOutcome
SchedulerRunResult
ReminderDispatchResult
EscalationDispatchResult
performance requirements
Full test suite must complete in under 10 seconds on CI
Large-batch test (500 assignments) must complete in under 2 seconds
security requirements
Test fixtures must not contain real user data — use generated fake IDs and anonymous placeholder values

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

If ReminderSchedulerService is not yet coded against interfaces (abstractions), refactor its constructor to accept injectable abstractions before writing tests — this is a prerequisite. Use `setUp` and `tearDown` to reset mock state between tests rather than re-declaring mocks per test (reduces boilerplate). For the large-batch test, use a loop to generate 500 stub Assignment objects with alternating outcomes; assert dispatcher call counts rather than inspecting each invocation individually to keep assertions fast. For idempotency tests, configure the guard mock to return `isRunning: true` and verify the service returns a specific sentinel result (e.g., `SchedulerRunResult.duplicateSkip()`).

Document each test group with a short comment explaining the invariant being verified — these tests double as living specification for the scheduler contract.

Testing Requirements

This task IS the testing requirement. All tests are pure unit tests using flutter_test (or dart test). Use mockito/mocktail to generate mocks for all service dependencies. Organize tests in a describe/group hierarchy: (1) normal routing by outcome, (2) edge cases (empty set, all-same outcome, mixed), (3) batch boundaries (batch size = 1, batch size > assignment count, exactly divisible, remainder), (4) idempotency guard, (5) dispatcher error resilience.

Each test must be independent — no shared mutable state between tests. After the suite, run `dart test --coverage` and verify coverage thresholds before marking the task done.

Component
Reminder Scheduler Service
service high
Epic Risks (3)
medium impact high prob scope

The idempotency window (how long after a reminder is sent before another can be sent for the same assignment) is not explicitly specified. An incorrect window — too short, duplicate reminders appear; too long, a resolved and re-opened situation is not re-notified. This ambiguity could result in user-visible bugs post-launch.

Mitigation & Contingency

Mitigation: Before implementation, define the idempotency window explicitly with stakeholders: a reminder is suppressed if a same-type notification record exists with sent_at within the last (reminder_days - 1) days. Document this rule as a named constant in the service with a comment referencing the decision.

Contingency: If the window is wrong in production, it is a single constant change with a hotfix deployment. The notification_log table allows re-processing without data migration.

high impact medium prob technical

For organisations with thousands of open assignments (e.g., NHF with 1,400 chapters), the daily scheduler query over all open assignments could time out or consume excessive Supabase compute units, especially if the contact tracking query lacks proper indexing.

Mitigation & Contingency

Mitigation: Add a composite index on assignments(status, last_contact_date) before running performance tests. Use cursor-based pagination in the scheduler (query 500 rows at a time). Run a load test with 10,000 synthetic assignments as described in the feature documentation before merging.

Contingency: If the query is too slow for synchronous execution, move the evaluation to the Edge Function (cron trigger epic) and use Supabase's built-in parallelism. The service interface does not change, only the execution context.

medium impact medium prob integration

If the push notification service fails (FCM outage, invalid device token) during dispatch, the in-app notification may already be persisted but the push is silently lost. Inconsistent state makes it impossible to report accurate delivery status.

Mitigation & Contingency

Mitigation: Implement push dispatch and in-app persistence as separate operations with independent error handling. Record delivery_status as 'pending', 'delivered', or 'failed' on the notification_log row. Retry failed push deliveries up to 3 times with exponential backoff.

Contingency: If FCM is consistently unavailable, the in-app notification is still visible to the user, providing a degraded but functional fallback. Alert on consecutive push failures via the cron trigger's error logging.