high priority high complexity testing pending testing specialist Tier 6

Acceptance Criteria

Test case: single-org user authenticates and is automatically navigated to the main app screen without seeing the org-selection screen
Test case: multi-org user (up to 5 chapters per NHF peer mentor) authenticates and is presented with the org switcher listing all their organizations
Test case: user selects a deactivated organization — a typed error screen is displayed with a clear message; navigation to main app is blocked
Test case: unauthenticated user attempts to navigate to a protected route — GDPR guard redirects to login screen
Test case: cold start with persisted valid session — org-selection screen is never displayed; main app loads directly
Test case: cold start with persisted session for a now-deactivated org — error screen shown, not a crash or blank screen
Test case: network failure during org membership fetch — user sees a retry option; no crash occurs
All test cases pass consistently (no flakiness) across 10 consecutive runs
Tests use flutter_test with mocktail-mocked Supabase client — no real network calls made
Test file is organized with clearly named test groups matching each scenario above
Test execution time for the full suite is under 60 seconds

Technical Requirements

frameworks
Flutter
flutter_test
mocktail (for Supabase client mocking)
Riverpod (ProviderContainer for test overrides)
BLoC (bloc_test for BLoC/Cubit assertions if applicable)
apis
Supabase Auth (mocked: signIn, currentSession, onAuthStateChange)
Supabase PostgREST (mocked: organization membership queries)
OrgSelectionService (under test)
OrgRouteGuard (under test)
TenantContextService (mocked)
data models
assignment (membership lookup mock data)
contact (authenticated user fixture data)
performance requirements
Each individual test case must complete within 5 seconds
No real I/O — all Supabase calls must be intercepted by mocks
security requirements
Test fixtures must not contain real personnummer, real user IDs, or real org credentials
Mocked JWT payloads must use clearly fake values (e.g., test-user-uuid-0001)
ui components
Org switcher widget (verified via widget finders in integration tests)
Deactivated org error screen (verified via widget finders)
GDPR/login redirect screen (verified via route assertions)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Structure the test file as: (1) shared fixtures and mock setup at the top, (2) a `setUp` that resets all mocks and creates a fresh `ProviderContainer`, (3) one `group` per named scenario. For navigation assertions, prefer asserting the current route path string over asserting widget presence where possible — it is more robust. Use `pumpAndSettle` with a timeout parameter to avoid indefinite waiting in case of animation loops. Mock the Supabase client at the interface boundary (repository layer), not at the raw HTTP level, to make tests less brittle to SDK changes.

For the multi-org scenario, create fixture data with exactly 2 and exactly 5 organizations to test both the minimum multi-org case and the NHF maximum (5 chapters). Document each test case with a one-line comment referencing the acceptance criterion it covers.

Testing Requirements

This task IS the testing deliverable. Use flutter_test with `testWidgets` for navigation and UI assertions and `test` for pure logic assertions. Organize tests in a `group()` block per scenario. Use `ProviderContainer` with Riverpod overrides to inject mocked services.

For BLoC components use `bloc_test` to verify state sequences. Assert final route using `GoRouter` current location or equivalent. For cold-start scenarios, simulate by pre-populating a mock TenantSessionStore before the widget tree mounts. Ensure each test resets all mocks and provider state in `setUp`/`tearDown`.

Add a CI tag so these integration tests run in a separate step from unit tests due to slightly longer execution time.

Component
Organization Selection Service
service low
Epic Risks (4)
high impact medium prob technical

TenantContextService must invalidate all downstream Riverpod providers when the org context changes (org switch scenario). If any provider caches org-specific data without subscribing to the tenant context, it will serve stale data from the previous org after a switch — which is both a UX failure and a potential GDPR violation.

Mitigation & Contingency

Mitigation: Define a single TenantContextProvider at the root of the Riverpod provider graph that all org-scoped providers depend on via ref.watch(). When TenantContextService.seedContext() runs, it invalidates TenantContextProvider which cascades invalidation to all dependents. Document this pattern in an architectural decision record so all developers follow it.

Contingency: Implement a post-switch integrity check that re-fetches a sample of each major data entity type and confirms the returned org_id matches the newly selected context; surface a reload prompt if any mismatch is detected.

medium impact medium prob security

MultiOrgMembershipResolver must query role assignments across potentially multiple tenant schemas. The anon or authenticated Supabase RLS policy may not permit cross-schema queries, making it impossible to return the full list of orgs a user belongs to in a single call.

Mitigation & Contingency

Mitigation: Design the membership query to use a dedicated Supabase edge function or a shared public schema view that aggregates role assignments across tenant schemas with a service-role key, returning only the org IDs the calling user is permitted to see. This keeps the client read-only.

Contingency: If cross-schema queries cannot be made safely, fall back to a per-org sequential membership check using the list of known org IDs and coalesce results client-side with appropriate timeout handling.

medium impact low prob technical

go_router redirect guards behave differently on web vs. mobile for deep links and browser back-button navigation. If the app is later deployed as a Progressive Web App (PWA) for admin use, the OrgRouteGuard may loop or fail to apply correctly on browser navigation events.

Mitigation & Contingency

Mitigation: Implement the guard as a GoRouter.redirect callback (not a ShellRoute redirect) following go_router best practices for platform-agnostic guards. Write widget tests that simulate navigation with and without auth/org context on both mobile and web target platforms in CI.

Contingency: If web-specific guard behaviour differs unacceptably, introduce a platform check in the guard and apply separate redirect logic branches for web vs. mobile until a unified solution is found.

medium impact medium prob scope

In Phase 2 the OrgSelectionService will need to coordinate the handoff to BankID/Vipps authentication after the org is selected, storing the returned personnummer against the correct tenant's member record. If the service is designed too narrowly for Phase 1 email/password flow, retrofitting Phase 2 will require invasive changes to an already-tested component.

Mitigation & Contingency

Mitigation: Design OrgSelectionService with an AuthHandoffStrategy interface from the start (Phase 1 implementation: email/password, Phase 2: BankID/Vipps). The strategy pattern makes the Phase 2 swap an additive change rather than a rewrite. Stub the interface in Phase 1 with a TODO comment referencing the Phase 2 epic.

Contingency: If Phase 2 requirements diverge significantly from Phase 1 assumptions, create a dedicated Phase2OrgSelectionService subclass that extends the base and overrides the auth handoff step, preserving Phase 1 behaviour unchanged.