high priority low complexity testing pending testing specialist Tier 4

Acceptance Criteria

Successful Supabase fetch: fetchLabels() returns Right(labelMap), and cacheAdapter.write() is called exactly once with the correct orgId and returned map
Network failure with warm cache: fetchLabels() returns Right(cachedMap) and cacheAdapter.write() is NOT called (no overwrite with error data)
Network failure with empty cache: fetchLabels() returns Left(TerminologyFailure) — the specific failure type is asserted (e.g. networkAndCacheEmpty)
TTL expired, cache warm: fetchLabels() returns Right(staleMap) immediately, AND a background Supabase call is verified to have been triggered asynchronously
TTL not expired, cache warm: fetchLabels() returns Right(freshMap) with zero Supabase calls
invalidateCache(orgId): cacheAdapter.clear(orgId) is called, then a new Supabase fetch is performed, and the result is written to cache
invalidateCache with network failure: returns Left(TerminologyFailure), cache remains cleared (not restored to old value)
All mocks are verified (no unexpected interactions with Supabase or cache adapter)
Tests are deterministic — fake clock injected, no real async delays
All tests pass with `flutter test` and no real network connections are made

Technical Requirements

frameworks
Flutter
flutter_test
mockito or mocktail
apis
Supabase PostgREST (mocked — no real connection)
TerminologyCacheAdapter (mocked interface)
data models
TerminologyRepository
TerminologyCacheAdapter (mock)
SupabaseClient (mock)
TerminologyFailure (sealed)
Either<TerminologyFailure, Map<String,String>>
performance requirements
All integration tests must complete without real async delays — use fake clocks and synchronous mock responses
Total test suite runtime under 5 seconds
security requirements
Mock Supabase client must never open a real network socket
Test fixtures must not contain real org IDs or production label data

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The key challenge is testing the background refresh (fire-and-forget Future). After calling fetchLabels() with a stale cache, insert `await Future.microtask(() {})` or `await Future.delayed(Duration.zero)` before asserting that the mock Supabase was called — this yields to the event loop and allows the unawaited Future to begin execution. Do not use real `Duration` delays in tests. For the Either assertion pattern, prefer `expect(result.isRight(), true)` and `result.fold((l) => fail('Expected Right'), (r) => expect(r, expectedMap))` for readable failure messages.

Ensure mock setup covers the full call chain: Supabase mock → `.from()` → `.select()` → `.eq()` → `.single()` returning a Future.

Testing Requirements

Use mockito's `@GenerateMocks([SupabaseClient, TerminologyCacheAdapter])` or mocktail's `class MockTerminologyCacheAdapter extends Mock implements TerminologyCacheAdapter`. Inject a fake clock (`DateTime Function() clock`) into TerminologyRepository to control TTL expiry without real time passing. For the background refresh test (TTL expired): use a Completer or verify(mockSupabase...).called(1) after a `await Future.microtask(() {})` to allow the unawaited Future to run. Group tests into: 'happy path', 'offline fallback', 'TTL behavior', 'cache invalidation'.

Each group independently resets mock state in setUp(). Assert both return values AND mock interaction counts to catch silent failures.

Component
Terminology Repository
data low
Epic Risks (2)
high impact medium prob integration

The labels JSONB column in organization_configs may lack a consistent schema across organizations, causing deserialization failures or silent missing keys when a new organization is onboarded with a differently structured map.

Mitigation & Contingency

Mitigation: Define and enforce a canonical JSONB schema via a Supabase check constraint and a migration script. Validate the schema in TerminologyRepository at parse time and emit structured errors for any key-type mismatches.

Contingency: If schema drift is discovered post-deployment, LabelKeyResolver's fallback logic ensures the app continues to function with English defaults while a data migration is prepared to normalize the offending organization's config.

medium impact low prob technical

Device local storage corruption or platform-specific SharedPreferences serialization bugs could render a cached terminology map unreadable, causing the app to fall back to English defaults unexpectedly for an organization with custom terminology.

Mitigation & Contingency

Mitigation: Wrap all cache reads in try/catch, validate the deserialized map against a minimum-required-keys check, and evict corrupted entries automatically before re-fetching from Supabase.

Contingency: Surface a non-blocking in-app warning to the coordinator that terminology may be in default English until the next sync completes; trigger an immediate background sync.