high priority high complexity testing pending testing specialist Tier 6

Acceptance Criteria

Unit tests cover all OrganizationUnitRepository public methods: create, read (getById, getByOrganization), update, softDelete, restore, and getSubtree RPC call
Unit tests cover UnitAssignmentRepository: create (enforces uniqueness), delete, getPrimaryAssignment, and the primary-assignment enforcement logic (only one primary per contact)
Unit tests cover HierarchyCache: getNode (hit and miss), getChildren (leaf and non-leaf), getSubtree (root and subtree), getAllNodes, invalidate + re-populate, loadFromLocalStorage (valid, stale, corrupt, version mismatch), persistToLocalStorage
All unit tests mock the Supabase client — no real network calls in the unit test suite
Integration tests run against a local Supabase instance (Docker or supabase CLI) and cover: create + read round-trip, soft-delete makes unit invisible to RLS-filtered queries, restore makes unit visible again, getSubtree RPC returns correct descendant set, UnitAssignment uniqueness constraint is enforced at DB level
Integration tests cover the RLS policies: a user from organization A cannot read units from organization B
Test suite achieves minimum 85% line coverage on OrganizationUnitRepository, UnitAssignmentRepository, and HierarchyCache classes
All tests are deterministic — no flaky async timing dependencies
CI pipeline runs unit tests on every PR; integration tests run on merge to main
Test file names follow the convention {class_name}_test.dart and are located in test/repositories/

Technical Requirements

frameworks
Flutter
flutter_test
bloc_test
Riverpod
apis
Supabase PostgreSQL — local Docker instance for integration tests
Supabase Auth — mocked for unit tests, real for integration tests (test user seeding)
data models
contact_chapter (OrganizationUnit — primary entity under test)
assignment (UnitAssignment — uniqueness and primary assignment constraints)
activity (scoped by organization unit — referenced in RLS policy tests)
performance requirements
Full unit test suite must complete in under 30 seconds
Integration test suite must complete in under 3 minutes against local Supabase
security requirements
Integration tests must include at least one RLS cross-tenant rejection test — assert that organization A's user cannot read organization B's units
Test credentials (local Supabase anon key, service role key) must not be committed to version control — use .env.test loaded via flutter_dotenv or environment variables in CI

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Create a SupabaseTestHelper class in test/helpers/ that: (1) initializes Supabase with local test URL, (2) provides seedOrganizationUnit() and seedUnitAssignment() helpers, (3) provides cleanup(). For mocking SupabaseClient in unit tests, use mocktail's when().thenAnswer() to return controlled PostgrestResponse objects. Key pattern for mocking Supabase query chains: create a MockSupabaseQueryBuilder that returns itself on .from(), .select(), .eq(), .is_(), .single(), .execute() calls, then configure the final .execute() to return the desired data. For HierarchyCache local-storage tests, use SharedPreferences.setMockInitialValues().

Run integration tests only when the INTEGRATION_TESTS environment variable is set to avoid slowing down every CI run. Document the local Supabase setup steps in a test/README.md.

Testing Requirements

This task IS the testing task. Structure: (1) Unit tests in test/repositories/organization_unit_repository_test.dart, unit_assignment_repository_test.dart, hierarchy_cache_test.dart. (2) Integration tests in integration_test/repositories/. Use Mockito (or mocktail) to generate mock SupabaseClient.

For integration tests, use supabase_flutter with a local Supabase URL pointing to Docker. Seed test data in setUp() and clean up in tearDown() using the service role key. Use group() to organize tests by method. Use expect(find.text(...)) for error message assertions where applicable.

Test edge cases: empty organization (no units), single root unit, deeply nested hierarchy (5+ levels), softDelete of a parent (children should be unaffected in DB, absent from active tree view).

Component
Organization Unit Repository
data high
Epic Risks (3)
high impact medium prob technical

Recursive CTE queries for large hierarchies (1,400+ nodes) may exceed Supabase query timeouts or produce unacceptably slow responses, degrading tree load time beyond the 1-second target.

Mitigation & Contingency

Mitigation: Implement Supabase RPC functions for subtree fetches rather than client-side recursive calls. Use materialized path or closure table as a supplemental index for depth-first traversal. Benchmark with realistic NHF data volumes during development.

Contingency: Fall back to a pre-computed flat unit list stored in the hierarchy cache with client-side tree reconstruction, trading freshness for speed. Add a background refresh job to keep the cache warm.

medium impact low prob technical

Concurrent writes from multiple admin sessions could cause cache staleness, leading to stale tree views and incorrect ancestor path computations that corrupt aggregation results.

Mitigation & Contingency

Mitigation: Use optimistic versioning on cache entries with a short TTL (5 minutes) as a safety net. Subscribe to Supabase Realtime on the organization_units table to push invalidation events to all connected clients.

Contingency: Provide a manual 'Refresh Hierarchy' action in the admin portal that forces a full cache bust, and display a staleness warning banner when the cache age exceeds the TTL.

high impact low prob security

Persisting the flat unit list to local storage may expose organization structure data if the device is compromised or the storage is not properly encrypted, violating data protection requirements.

Mitigation & Contingency

Mitigation: Use flutter_secure_storage (AES-256 backed by Keychain/Keystore) for the local unit list cache rather than SharedPreferences. Include only unit IDs, names, and types — no member PII.

Contingency: Disable local-storage persistence entirely and rely on in-memory cache only. Accept the trade-off of no offline hierarchy access for the security guarantee.