high priority low complexity testing pending testing specialist Tier 2

Acceptance Criteria

Tests are runnable with `flutter test` — no device, emulator, or live Supabase project required
Test: query matching a contact's first_name via ilike returns that contact in results
Test: query matching a contact's last_name via ilike returns that contact in results
Test: query matching a contact's organisation_name via ilike returns that contact in results
Test: query matching a keyword in a note's body returns the associated contact in results
Test: query that matches no contacts or notes returns an empty list (not null, not an error)
Test: mock returns contacts from two different organisations but results only contain contacts matching the user's organisationId (RLS simulation via mock filter)
Test: Supabase client throws PostgrestException — repository method returns a Failure result or throws a typed domain exception (not a raw PostgrestException)
Test: Supabase client throws SocketException (no network) — repository method returns a Failure result without crashing
All returned items are of type ContactSearchResult with the same field structure regardless of whether they matched on name, organisation, or notes
Test file is located in test/data/repositories/ and follows the project's existing test naming conventions

Technical Requirements

frameworks
flutter_test
mockito or mocktail for mocking Supabase client
supabase_flutter SDK (mocked)
apis
Supabase PostgREST REST API — contacts table ilike query
Supabase PostgREST REST API — notes table keyword query
data models
contact
assignment
performance requirements
All unit tests complete in under 2 seconds total — no real network calls
security requirements
Tests must not contain real Supabase URLs, API keys, or organisation IDs — use placeholder constants
RLS behaviour is simulated by configuring the mock to return only org-scoped records, not by connecting to a real RLS-enabled database

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Supabase's Dart client uses a fluent builder pattern that is notoriously hard to mock because intermediate builder objects are returned. Use mocktail's `when(client.from(any)).thenReturn(mockQueryBuilder)` pattern and stub each chained method. Alternatively, wrap the Supabase query in an abstract SearchDataSource interface and mock the interface instead of the Supabase client directly — this is the recommended approach as it decouples tests from Supabase's internal builder API and makes tests resilient to SDK upgrades. The ContactSearchResult model returned by both online and offline repositories must be identical — include an assertion in the test that verifies the model fields match the expected schema to catch regressions if the model is changed.

Testing Requirements

Pure unit tests (no widget tree, no Supabase connection). Use mocktail or mockito to create a MockSupabaseClient and stub the query builder chain (from().select().ilike().execute() pattern). Each test case should: arrange the mock response, act by calling searchContacts(query, organisationId), assert on the returned List. Group tests with `group('SupabaseSearchRepository', () { ...

})` and label each with a descriptive name matching the scenario. Aim for 100% branch coverage of the repository class — every conditional path (empty result, error, RLS filter) must have at least one test.

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