Unit and integration tests for lifecycle and reminder pipeline
epic-certification-management-core-logic-task-012 — Write comprehensive unit tests for CertificationManagementService (lifecycle transitions, auto-pause idempotency, Dynamics sync error handling) and CertificationReminderService (threshold evaluation, deduplication logic, payload generation, log writing). Use mock repositories and mock PushNotificationService. Include integration tests that run against a local Supabase instance to verify RLS and transactional correctness.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 7 - 84 tasks
Can start after Tier 6 completes
Implementation Notes
Set up a shared test helper file (test/helpers/certification_test_helpers.dart) with factory methods for creating mock CertificationExpiryCandidate, FcmPayload, and certification database records — avoid duplicating fixture construction across test files. For Dynamics sync error handling tests, use a MockDynamicsSyncService that throws specific exception types matching the real service's error hierarchy; this ensures test realism without needing a live Dynamics instance. Integration tests should use supabase_flutter's test client pointed at localhost:54321 (supabase start default port) and apply seed SQL via a beforeAll() hook. The auto-pause idempotency test is a regression guard for a common async bug: calling auto-pause concurrently should use a database-level unique constraint or advisory lock — the test should attempt two simultaneous auto-pause calls and assert only one coordinator notification was created.
Organise the CI workflow to run unit tests first (fast, no Docker) and integration tests second (requires Docker service); this minimises CI time on code that fails unit tests.
Testing Requirements
This task IS the testing task. Structure test files as follows: test/services/certification_management_service_test.dart (lifecycle, auto-pause, Dynamics sync — approx. 15–20 test cases), test/services/certification_reminder_service_test.dart (threshold eval, deduplication, payload builders, dispatch loop — approx. 20–25 test cases), test/integration/certification_pipeline_integration_test.dart (RLS, transactional correctness, end-to-end deduplication — approx.
8–10 test cases). Use group() and setUp()/tearDown() for test organisation. Each test must have a descriptive name following the pattern 'given [state], when [action], then [outcome]'. Run coverage with: flutter test --coverage && genhtml coverage/lcov.info -o coverage/html.
Fail CI if coverage drops below 90%.
The auto-pause workflow requires CertificationManagementService to call PauseManagementService and HLFDynamicsSyncService in the same logical transaction. If PauseManagementService succeeds but the Dynamics webhook fails, the mentor is paused locally but remains visible on the HLF portal.
Mitigation & Contingency
Mitigation: Implement a saga pattern: write a pending sync event to the database before calling Dynamics, and have a background retry job consume pending events. This guarantees eventual consistency even if the webhook fails transiently.
Contingency: If the Dynamics sync fails after auto-pause, surface an explicit coordinator alert in the dashboard indicating 'Dynamics sync pending — mentor may still be visible on portal'. Allow manual retry from coordinator UI.
If the nightly cron job runs concurrently (e.g., due to infra retry), CertificationReminderService could dispatch duplicate notifications to mentors before the cert_notification_log insert is visible to the second invocation.
Mitigation & Contingency
Mitigation: Use Supabase's upsert with a unique constraint on (mentor_id, threshold_days, cert_id) in cert_notification_log. The second concurrent insert will fail gracefully and the duplicate dispatch will be skipped.
Contingency: If duplicate notifications do reach mentors, add a post-dispatch dedup check and include a 'you may receive this notification again' disclaimer until the constraint is deployed.