critical priority medium complexity testing pending testing specialist Tier 2

Acceptance Criteria

Test suite passes `flutter test` with zero failures
Test covers result == none when daysSinceContact < remindAfterDays
Test covers result == remind when daysSinceContact == remindAfterDays (boundary: inclusive)
Test covers result == remind when daysSinceContact == escalateAfterDays - 1 (boundary: exclusive)
Test covers result == escalate when daysSinceContact == escalateAfterDays (boundary: inclusive)
Test covers result == escalate when no contact has ever been recorded (null lastContactDate)
Test covers result == none when daysSinceContact == 0 (contact made today)
All dependencies (AssignmentContactTrackingRepository, clock function) are mocked — no real Supabase calls
Each test has a descriptive name following the pattern: `evaluate_returns_{variant}_when_{condition}`
Test file is located under `test/domain/reminders/` mirroring the source directory structure
Code coverage for `ReminderEvaluationServiceImpl` is 100% branch coverage as verified by `flutter test --coverage`

Technical Requirements

frameworks
flutter_test
Mockito (or manual fakes)
Dart
apis
AssignmentContactTrackingRepository (mocked)
clock function (injected DateTime stub)
data models
ReminderEvaluationResult
OrgReminderThresholds
ContactTrackingRecord
performance requirements
Full test suite must complete in under 5 seconds — no real async delays or timers
Use fixed DateTime stubs (not DateTime.now()) for deterministic results
security requirements
Tests must not contain real assignment IDs, user IDs, or any production data
Mock data uses clearly fictional values (e.g., 'test-assignment-001')

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Inject a `clockFn` (`DateTime Function()`) into `ReminderEvaluationServiceImpl` to enable deterministic time in tests — set it to `() => DateTime.utc(2025, 6, 15)` in test setup. Use Mockito's `when().thenAnswer()` pattern to stub repository responses. For the no-contact-ever test, stub the repository to return `null` for `getLastContactDate(assignmentId)`. Write a shared `_makeThresholds({int remind = 7, int escalate = 14})` helper to reduce boilerplate across threshold tests.

Prefer manual fakes over generated mocks if the repository interface is simple — fewer dependencies and faster test compilation.

Testing Requirements

This task IS the testing requirement. Structure tests using `group()` blocks per scenario category: (1) below-threshold group, (2) remind-threshold group, (3) escalate-threshold group, (4) no-contact-ever group, (5) boundary-condition group. Use `setUp()` to instantiate the service with injected mocks before each test. Parameterize boundary tests using a table-driven approach where possible.

Verify that exhaustive switch on `ReminderEvaluationResult` in test assertions catches compiler errors if new variants are added.

Component
Reminder Evaluation Service
service medium
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.