high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Tests are runnable with `flutter test` β€” no device, emulator, or live database required
Each test sets up an in-memory Drift database using NativeDatabase.memory() (or LazyDatabase equivalent) and tears it down after the test
Test: seeded contact whose first_name contains the query string is returned by searchContacts()
Test: seeded contact whose last_name contains the query string is returned
Test: seeded contact whose organisation_name contains the query string is returned
Test: seeded note whose body contains the query keyword β€” the associated contact is returned
Test: empty cache (no rows seeded) returns an empty list for any query
Test: query matching is case-insensitive β€” 'ANNA' matches a contact with first_name 'anna'
Test: a query that matches multiple contacts returns all of them, not just the first
Test: a query that matches no rows returns an empty list, not null
Test: the returned ContactSearchResult fields (id, displayName, organisationId, matchedOn) match the same structure returned by SupabaseSearchRepository to confirm interface parity
Test file is located in test/data/repositories/ alongside the online repository tests

Technical Requirements

frameworks
drift/native for NativeDatabase.memory() in-memory test database
flutter_test
drift_dev for generated Drift code (must be generated before tests run)
data models
contact
assignment
performance requirements
All unit tests complete in under 3 seconds total
In-memory database setup and teardown must not cause test memory leaks β€” call db.close() in tearDown
security requirements
In-memory test database must not be shared between tests β€” instantiate a fresh NativeDatabase.memory() per test or test group to prevent state leakage

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

SQLite LIKE is case-insensitive for ASCII characters but case-sensitive for non-ASCII (e.g. Norwegian ΓΈ, Γ¦, Γ₯). Since contact names may include Norwegian characters, the Drift query should use `LOWER(field) LIKE LOWER('%$query%')` or configure the SQLite connection with `PRAGMA case_sensitive_like = OFF`. Verify this behaviour explicitly in a test with a Norwegian name (e.g.

'Γ…se') to catch the edge case early. The cross-repository parity test is the most valuable test in this suite β€” it acts as a contract enforcing that offline and online search results are interchangeable, which is the core guarantee of the dual-repository pattern. Run `dart run build_runner build` before running tests to ensure Drift-generated code is up to date; document this in the test file's header comment.

Testing Requirements

Pure unit tests using a real in-memory Drift database (not mocked) β€” this validates the actual SQL LIKE query logic. setUp() inserts seed data via the DAO upsert methods from task-005. tearDown() calls db.close(). Use parameterized test cases where possible to test multiple query variants without duplicating boilerplate.

Include a cross-repository parity test that instantiates both OfflineSearchRepository and SupabaseSearchRepository (with mocked Supabase client returning the same data as the seeded Drift DB), runs the same query against both, and asserts the returned ContactSearchResult lists are structurally identical β€” this is the contract test that guarantees the two repositories are interchangeable.

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