high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test suite connects to the HLF Dynamics sandbox environment using credentials sourced from CI environment variables (`DYNAMICS_SANDBOX_CLIENT_ID`, `DYNAMICS_SANDBOX_CLIENT_SECRET`, `DYNAMICS_SANDBOX_TENANT_ID`)
Test: successful certification status push — sends a valid outbound webhook payload and asserts HTTP 200 response and matching record in the sandbox Dynamics portal
Test: token expiry refresh mid-sync — test artificially expires the cached token (set expiry to past), triggers a sync, and asserts a new token was fetched and the sync completed successfully
Test: retry on 503 — sandbox returns 503 on first two attempts (via a controlled fault injection endpoint or WireMock proxy), third attempt succeeds; asserts sync eventually completes and retry_count=2 is logged
Test: circuit breaker trip — sandbox returns 503 for 3 consecutive attempts; asserts circuit transitions to OPEN and a `dynamics_sync_retry_queue` entry is created
Test: idempotency for duplicate webhook delivery — same `event_id` delivered twice; asserts second delivery returns HTTP 200 and only one `cert_notification_log` row exists for that event_id
All tests are tagged `@Tags(['integration', 'dynamics-sandbox'])` and skipped in unit test runs via `flutter test --exclude-tags integration`
CI pipeline step is documented (or a GitHub Actions step is provided) showing how to inject sandbox credentials as masked environment variables
Tests clean up sandbox data after each test run (delete test certification records created during tests)
No production Dynamics tenant credentials or production mentor IDs are used in any test

Technical Requirements

frameworks
Flutter
Dart
flutter_test
apis
Microsoft Dynamics 365 REST API
Supabase PostgreSQL 15
Supabase Edge Functions (Deno)
data models
certification
performance requirements
Full integration test suite must complete within 3 minutes in CI
Each individual test must have a timeout of 30 seconds to prevent CI hangs on network failures
security requirements
Sandbox credentials must be stored in CI secrets manager (e.g., GitHub Actions secrets) — never committed to source code
Test output must not log raw access tokens — log only masked values or expiry timestamps
Test certification IDs must use a dedicated test prefix (e.g., 'TEST-') to prevent accidental production data mutation
CI runner must not have network access to production Dynamics tenant — sandbox-only network policy enforced via environment variable gating

Execution Context

Execution Tier
Tier 3

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.

Component
HLF Dynamics Sync Service
service medium
Epic Risks (3)
high impact medium prob integration

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.

high impact medium prob security

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.

medium impact low prob technical

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.