high priority high complexity testing pending testing specialist Tier 7

Acceptance Criteria

All unit tests run via flutter test with zero external network calls — all Supabase and FCM interactions are mocked using mockito or mocktail
CertificationManagementService unit tests cover: valid lifecycle transitions (active→expiring, active→expired, active→paused), invalid transition attempts (expired→active without renewal), and auto-pause idempotency (calling auto-pause on an already-paused mentor does not create duplicate records or trigger duplicate coordinator notifications)
CertificationManagementService unit tests cover Microsoft Dynamics sync error handling: 401 Unauthorized, 500 Internal Server Error, and connection timeout scenarios — assert service degrades gracefully with correct error states and does not corrupt local certification record
CertificationReminderService unit tests cover threshold evaluation (task-008 scenarios), deduplication (task-009 scenarios), payload generation (task-010 scenarios), and dispatch with partial failures (task-011 scenarios) — all test scenarios from previous tasks are consolidated here into the test suite
Integration tests run against a Supabase local dev instance (supabase start); schema migrations applied before test run
Integration test: RLS verification — mentor user can only read their own certification rows; coordinator can read all mentors in their chapter; cross-organisation read returns empty result set (not error)
Integration test: transactional correctness — simulate a dispatch loop that succeeds for 3 of 5 candidates then throws; verify exactly 3 log records exist in cert_notification_log after the partial run
Integration test: deduplication end-to-end — seed cert_notification_log with 2 delivered records; run full pipeline; assert only 3 new dispatches occur for 5 total candidates
Test coverage report generated via flutter test --coverage; overall coverage for the two services must reach ≥90% line coverage
All tests pass in CI (GitHub Actions or equivalent) without manual configuration of local services beyond Docker

Technical Requirements

frameworks
flutter_test (Dart test framework)
mocktail (preferred over mockito for null-safety ergonomics)
Supabase local development stack (supabase CLI, Docker) for integration tests
lcov for coverage report generation
apis
Supabase PostgreSQL — local instance for integration tests
Microsoft Dynamics 365 REST API — mocked in unit tests; real endpoint not required for test suite
Firebase Cloud Messaging (FCM) API v1 — mocked via MockPushNotificationService
data models
certification (all fields — lifecycle state machine tests require full schema)
cert_notification_log (deduplication and log-write integration tests)
assignment (coordinator resolution tests)
device_token (dispatch tests — mock tokens)
CertificationExpiryCandidate (threshold evaluation and payload builder tests)
performance requirements
Full unit test suite must complete within 60 seconds on a standard CI runner
Integration tests (Supabase local) must complete within 3 minutes
Individual test files must not exceed 500 lines — split by service and concern if needed
security requirements
No real API keys, personnummer, or production device tokens in test fixtures — use clearly fake identifiers (e.g., 'test-mentor-id-001')
Integration test Supabase instance uses a separate .env.test file never committed to the main branch
Test seed data scripts must be idempotent (safe to re-run) to avoid polluting the local database between test runs

Execution Context

Execution Tier
Tier 7

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%.

Component
Certification Management Service
service high
Epic Risks (2)
high impact medium prob technical

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.

medium impact low prob technical

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.