Integration test HLF Dynamics Sync against sandbox
epic-certification-management-foundation-task-012 — Write integration tests for HLFDynamicsSyncService against the HLF Dynamics sandbox environment. Cover: successful certification status push, token expiry refresh mid-sync, retry on 503, circuit breaker trip after 3 consecutive failures, and idempotency for duplicate webhook deliveries. Tests must run in CI with sandbox credentials stored in secure environment variables.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Create a `DynamicsSandboxTestHelper` class that provides: `createTestCertification()`, `deleteTestCertification(id)`, and `assertCertificationStatusInSandbox(id, expectedStatus)` — this encapsulates sandbox API calls and keeps test bodies clean. For the token expiry test: inject a `DynamicsAuthProvider` with a `TokenCacheOverride` that allows setting the cached token's expiry to a past timestamp. For the circuit breaker trip test: reset the circuit breaker to CLOSED state in `setUp()` using a `reset()` method on `DynamicsCircuitBreaker`. For the idempotency test: capture the `event_id` from the first delivery and replay the exact same payload.
Ensure CI step uses `--exclude-tags unit` when running integration tests and `--exclude-tags integration` when running unit tests so the two suites remain independent. Add a `.github/workflows/integration-tests.yml` step or equivalent that runs only on `main` branch pushes to avoid burning sandbox quota on every PR.
Testing Requirements
Integration tests using flutter_test with real (sandbox) HTTP connections. Use test tags to separate from unit tests: `@Tags(['integration'])`. Structure in a single test file `test/integration/hlf_dynamics_sync_integration_test.dart`. Use `setUp()` to initialise the service with sandbox credentials from `Platform.environment`.
Use `tearDown()` to delete test records created in the sandbox. For the retry/circuit breaker tests, either: (a) use a WireMock or similar local HTTP proxy that can be configured to return specific error codes, or (b) use a dedicated fault-injection endpoint on the sandbox if available. Document the required environment variables in a `test/integration/README.md` file. Tests must be guarded with `if (Platform.environment['DYNAMICS_SANDBOX_CLIENT_ID'] == null) skip('Sandbox credentials not configured')` so they fail gracefully in environments without credentials.
HLF Dynamics portal webhook API contract may be undocumented, subject to change, or require a separate authentication flow not yet agreed upon with HLF. If the contract changes post-implementation, the sync service silently fails and expired peer mentors remain on public listings.
Mitigation & Contingency
Mitigation: Obtain the official Dynamics webhook specification and test credentials from HLF before starting HLFDynamicsSyncService implementation. Agree on a versioned webhook contract and request a staging endpoint for integration testing.
Contingency: If the contract is unavailable, stub the sync service behind a feature flag and ship without Dynamics sync initially. Queue sync events locally and replay once the contract is confirmed.
Supabase RLS policies for certifications must correctly scope data to the coordinator's chapter without leaking cross-organisation data, particularly complex in multi-chapter membership scenarios. A misconfigured policy could expose peer mentor PII to wrong coordinators.
Mitigation & Contingency
Mitigation: Write RLS policies against the established org-hierarchy schema used by other tables. Peer review all policies before migration deployment. Add integration tests that assert cross-organisation data isolation using test accounts with different org scopes.
Contingency: If a policy gap is discovered post-merge, immediately disable the affected query endpoint and apply a hotfix migration. Audit access logs in Supabase for any cross-org data access events.
Storing renewal history as a JSONB field rather than a normalised table simplifies queries but makes retrospective schema changes (adding fields to history entries) harder and could cause issues if history grows very large for long-tenured mentors.
Mitigation & Contingency
Mitigation: Define a versioned JSONB entry schema (include a schema_version field in each entry) so future migrations can transform old entries. Add a size guard in the repository to warn if renewal_history exceeds 500 entries.
Contingency: If JSONB approach proves limiting, add a normalised certification_renewal_events table and migrate history entries in a background job, keeping the JSONB field as a read cache.