Write unit tests for ContactCacheSyncRepository
epic-contact-search-data-layer-task-011 — Write unit tests for the ContactCacheSyncRepository using mocked Supabase and in-memory Drift. Cover: initial cache population from empty state, upsert of updated records, last_synced_at timestamp update, isCacheStale returning correct boolean at boundary values, clearCache wiping all rows, and sync error handling when Supabase throws.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Use NativeDatabase.memory() (drift) as the test database — pass it via constructor injection so production code always uses the real DB but tests pass a memory instance. For Supabase mocking, create a MockSupabaseClient that implements the minimal interface ContactCacheSyncRepository depends on; do not mock the entire Supabase class. For timestamp control, inject a DateTime provider (typedef or interface) so fake_async can override DateTime.now(). The isCacheStale boundary test is the most important edge case — test at TTL-1ms (false), exactly TTL (true), and TTL+1ms (true).
For the error propagation tests, verify that the cache rows written before the error are NOT rolled back only if a transaction was not started, or ARE rolled back if the sync runs inside a transaction — decide and document the intended behaviour before writing the test.
Testing Requirements
Pure unit tests using flutter_test. Each test case must use setUp/tearDown to initialise and dispose the in-memory Drift database. Use mockito/mocktail to stub Supabase responses. Use fake_async to control DateTime.now() for TTL boundary cases.
Group tests under descriptive describe blocks: 'populateCache', 'upsertRecord', 'isCacheStale', 'clearCache', 'errorHandling'. Verify no real async timers leak between tests. Run with --coverage and confirm 100% coverage on the repository class.
Supabase RLS policies may not correctly scope ilike search results to the authenticated user's organisation and chapter, causing data leakage across organisations or empty result sets for valid queries.
Mitigation & Contingency
Mitigation: Reuse and extend existing RLS query builder patterns from the contact-list-management feature. Write integration tests against a seeded multi-organisation test database to verify cross-org isolation before merging.
Contingency: If RLS scoping is insufficient, add an explicit organisation_id filter in the Dart query builder layer as a defence-in-depth measure while the Supabase policy is corrected.
Adding new Drift tables for the contact cache may conflict with existing migrations or schema versions in the contact-list-management feature if both features cache the same contacts table, causing migration failures on user devices.
Mitigation & Contingency
Mitigation: Audit existing Drift schema versions from contact-list-management before writing new migrations. Reuse existing cache tables if the schema already covers required fields; only add missing fields via ALTER or new version.
Contingency: If schema conflict occurs, consolidate into a single shared cache table owned by contact-list-management and expose a DAO interface to the search feature, avoiding duplicated schema ownership.
The offline cache may surface significantly stale contact data if sync has not run recently, leading coordinators to act on outdated information (wrong phone numbers, changed assignments).
Mitigation & Contingency
Mitigation: Store and surface the last-sync timestamp prominently in the UI layer. Trigger a background cache refresh on app foreground when connectivity is detected.
Contingency: If staleness becomes a reported UX issue, implement a maximum-age threshold that shows a warning banner when the cache is older than a configurable limit (e.g. 24 hours).