high priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

Integration test file exists at integration_test/contact_search_offline_fallback_test.dart or test/integration/contact_search_offline_fallback_test.dart and passes
Step 1 — Online population: ContactCacheSyncRepository.sync() is called with a mocked Supabase returning 10 seeded contact records; Drift cache contains all 10 records after sync
Step 2 — Offline simulation: Supabase client mock is reconfigured to throw SocketException on any subsequent call
Step 3 — Offline search: OfflineSearchRepository.search('test query') returns a non-empty List<ContactSearchResult> sourced entirely from the Drift cache
ContactSearchResult fields (id, displayName, organisationId, role) returned from offline search match exactly the fields that would be returned by an online search against the same seed data
No ContactSearchResult field is null or empty when the corresponding source record has a value
The connectivity routing layer (ContactSearchService or equivalent) is NOT bypassed — the test exercises real routing logic that detects offline state and delegates to OfflineSearchRepository
When cache is empty and device is offline, OfflineSearchRepository returns an empty list (not an exception)
Test is deterministic — seed data is fixed, results are asserted by exact value not just count
Test completes without real network I/O — all Supabase calls are mocked

Technical Requirements

frameworks
flutter_test (integration_test package or standard test runner)
mockito or mocktail
drift (in-memory database)
connectivity_plus or equivalent (mocked)
apis
Supabase PostgREST client (mocked, reconfigurable mid-test)
Drift query API
connectivity_plus stream (mocked)
data models
contact
ContactSearchResult (value object)
performance requirements
Integration test must complete in under 15 seconds
All Supabase calls replaced with synchronous mock responses to avoid real latency
security requirements
No real credentials or PII in seed data — use synthetic names and UUIDs
In-memory Drift instance isolated per test run

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

The key challenge is reconfiguring the Supabase mock mid-test to transition from online to offline. Use a configurable fake: a FakeSupabaseClient class with a shouldThrowNetwork flag that can be toggled between test phases. For connectivity detection, inject a StreamController so the test can push an offline event programmatically. Ensure the ContactSearchResult equality check uses Equatable or a custom == override — if using plain Dart objects, add an equatable mixin before writing the assertion.

The test should explicitly assert the routing decision: after the offline event is pushed, verify that OfflineSearchRepository.search was called (via a spy) and SupabaseSearchRepository.search was NOT called. This validates that the routing layer responded correctly to the connectivity change.

Testing Requirements

Integration test targeting the data layer only — no widget tree, no Flutter engine required unless testing through a widget. Use standard flutter test runner (not integration_test package) if the test does not require a device. Seed 10 contact records with distinct first_name, last_name, organization_id. Assert results using deep equality on ContactSearchResult.

Include a negative case: assert that when Supabase is available (online), the same query routes to the Supabase repository (not the cache). Include a boundary case: empty cache + offline = empty list, not error. Run as part of CI with flutter test --coverage.

Epic Risks (3)
high impact medium prob security

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.

medium impact medium prob integration

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.

medium impact medium prob scope

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).