high priority medium complexity testing pending testing specialist Tier 7

Acceptance Criteria

Tests exist for all three UserRole filter paths in ContactListService: coordinator, org_admin, and peer_mentor, each asserting the correct Supabase filter expression is applied
Local search path test: when contact count is below the threshold, ContactSearchService filters the in-memory list and does NOT call the Supabase client
Remote search path test: when contact count is at or above the threshold, ContactSearchService issues an ILIKE query to Supabase and returns merged results
Threshold boundary test: at exactly the threshold value, the remote path is taken (not local)
Empty contact list test: service returns an empty list without error, no crash or null reference
Provider auto-invalidation test: changing the role provider value in ProviderContainer causes ContactListRiverpodProvider to emit a loading state followed by new data
Role context change test: after an organisation switch, the provider emits data scoped to the new organisation only
All tests pass with a mock Supabase client — no real network calls
Test suite runs in under 30 seconds total
Test file follows the existing test naming and organisation conventions in the project

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
apis
Supabase (mocked)
data models
UserRole
OrganisationContext
Contact
PeerMentor
performance requirements
Full test suite completes in under 30 seconds
No real network I/O in any test
security requirements
Mock Supabase client must not log or persist any PII contact data to disk during test runs

Execution Context

Execution Tier
Tier 7

Tier 7 - 84 tasks

Can start after Tier 6 completes

Implementation Notes

For mocking the Supabase query builder chain (which is fluent/chainable), create a stub that records which filter methods were called and in what order, then assert the call sequence in tests rather than mocking return values for every chain step. This makes assertions more readable and less brittle. For ProviderContainer tests, use container.read(provider) to trigger the initial build, then use container.updateOverrides() or override the dependency provider to simulate a role/org change. Verify that the old stream subscription was cancelled by checking that the mock client's subscribe() was called exactly once per context, and unsubscribe() was called before the new subscribe().

Document each test with a one-line comment explaining what scenario it covers.

Testing Requirements

All tests use flutter_test with ProviderContainer for provider-layer tests and direct service instantiation for service-layer tests. Create a MockSupabaseClient using Mockito or manual stub implementing the Supabase query builder interface. Group tests by class under descriptive group() blocks: 'ContactListService', 'ContactSearchService', 'ContactListRiverpodProvider'. Use setUp()/tearDown() to reset provider containers between tests.

Assert both the emitted AsyncValue sequence (loading, then data/error) and the actual content of the data payload. Aim for 100% branch coverage of the three role filter paths and both search paths.

Component
Contact List Riverpod Provider
data medium
Epic Risks (2)
medium impact medium prob technical

For organizations with large contact lists (NHF has 1,400 local chapters and potentially thousands of contacts), local in-memory filtering may be too slow and Supabase ILIKE queries without supporting indexes may exceed acceptable response times or accumulate excessive read costs, degrading search usability for power users.

Mitigation & Contingency

Mitigation: Define and document the list-size threshold in ContactSearchService before implementation. Confirm that indexes on name and notes columns exist in the Supabase schema before enabling server-side search. Profile ContactSearchService against realistic data volumes in the staging environment using the largest expected org.

Contingency: If response times are unacceptable in staging, introduce result-count pagination in ContactListService and add a user-visible 'showing top N results — refine your search' indicator, deferring full pagination to a follow-up task.

high impact low prob security

In NHF's multi-chapter context, when a user switches organization, Riverpod providers may emit a brief window of stale contact data scoped to the previous organization before the invalidation cycle completes, transiently exposing contacts from the wrong chapter.

Mitigation & Contingency

Mitigation: Model organization context as a Riverpod provider dependency so that any context change immediately marks contact providers as stale. Render a loading skeleton instead of the stale list during the re-fetch transition. Cover this scenario in integration tests with explicit org-switch sequences.

Contingency: If race conditions are observed during QA, add an explicit organization_id equality check in ContactListService that compares each fetched record's scope to the active session org, discarding any mismatched batch before returning results to the provider.