high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

All public methods of CertificationRepository have at least one happy-path test and one error-path test
Mock Supabase client is used for all tests — no real database connections are established
RLS context: tests verify that the repository passes the correct `organization_id` filter on all queries (no cross-org data leakage in mock assertions)
CRUD tests: createCertification, getCertificationById, updateCertificationStatus, deleteCertification all tested with expected Supabase query builder call sequences
Expiry query tests: getExpiringCertifications(withinDays: 30) returns only records with expiry_date between now and now+30 days
Boundary condition: same-day expiry (expiry_date == today midnight UTC) is included in expiry results
Boundary condition: expiry at 23:59:59 UTC on the last day is included; expiry at 00:00:00 UTC the next day is excluded
JSONB renewal_history: a renewal record serialised to JSON and deserialised back produces an object equal to the original (no field loss, no type coercion errors)
Error path: Supabase returns PostgrestException → repository throws a typed `CertificationRepositoryException` with the original error wrapped
Error path: network timeout → repository throws `CertificationRepositoryException` with `errorCode: networkTimeout`
Test coverage: ≥ 90% line coverage on `certification_repository.dart` measured by `flutter test --coverage`

Technical Requirements

frameworks
Flutter
Dart
flutter_test
apis
Supabase PostgreSQL 15
data models
certification
performance requirements
All unit tests must complete in under 5 seconds total (mocked I/O, no real network calls)
Test file should not exceed 500 lines — split into multiple test groups if needed
security requirements
Test fixtures must not contain real personnummer, real user UUIDs, or production credentials
Mock responses should use generated UUIDs (e.g., from uuid package) not sequential integers

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use `mocktail` (preferred over Mockito for Dart null-safety ergonomics) to create `MockSupabaseClient`, `MockSupabaseQueryBuilder`, etc. The Supabase client has a deeply chained query builder API — you will need to mock the full chain (`.from().select().eq().gte().execute()`). Consider creating a `FakeSupabaseClient` test double that records calls and returns pre-configured fixtures, as this is often more readable than chaining mock expectations. For JSONB tests: construct a `List` fixture, call `repository.toJsonb()`, call `repository.fromJsonb()`, and assert deep equality using `expect(result, equals(original))`.

For boundary date tests: use `DateTime.utc(2026, 3, 26, 0, 0, 0)` explicitly — never `DateTime.now()` in test assertions. Place tests in `test/repositories/certification_repository_test.dart`. Run with `flutter test test/repositories/certification_repository_test.dart --coverage`.

Testing Requirements

Unit tests only — no integration tests in this task. Use flutter_test with Mockito or mocktail to mock the Supabase client. Structure tests in groups: 'CertificationRepository.create', 'CertificationRepository.read', 'CertificationRepository.update', 'CertificationRepository.delete', 'CertificationRepository.expiry queries', 'CertificationRepository.JSONB serialisation', 'CertificationRepository.error handling'. Use `setUp()` to initialise fresh mocks before each test.

For date boundary tests, inject a fixed `DateTime` via a `ClockProvider` or similar abstraction — avoid `DateTime.now()` in repository implementation. Run `flutter test --coverage` and verify lcov report shows >= 90% line coverage on the repository file.

Component
Certification Repository
data 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.