high priority high complexity testing pending testing specialist Tier 8

Acceptance Criteria

Integration test suite executes successfully on iOS simulator with Face ID mock returning success, failure, and lockout states
Integration test suite executes successfully on Android emulator with fingerprint mock returning success, failure, and not-enrolled states
Online scenario: biometric success → Supabase session refresh call is made → valid session stored in SecureSessionStorage → Riverpod AuthState transitions to authenticated
Offline scenario: biometric success → Supabase refresh skipped → cached session used if not expired → AuthState transitions to authenticated with offline indicator
Token expiry scenario: biometric success → Supabase refresh returns 401 → session cleared → user navigated to re-login screen
Role-based navigation: authenticated admin user is routed to admin home, coordinator to coordinator home, peer mentor to peer mentor home
Biometric failure (3 consecutive): AuthState transitions to locked, user is shown fallback password option
Biometric not-available: flow gracefully falls back to password authentication without crashing
All test scenarios produce deterministic, repeatable results with no flakiness across 5 consecutive runs
Test execution time for full suite is under 3 minutes on CI
Test coverage for the biometric auth flow chain reaches at least 80% branch coverage
All mocks are injected via Riverpod overrides — no production Supabase project is hit during tests

Technical Requirements

frameworks
flutter_test
Riverpod (ProviderContainer with overrides for test isolation)
BLoC test utilities (bloc_test) if auth uses BLoC
mocktail or mockito for LocalAuth and Supabase client mocking
apis
local_auth API (mocked: canCheckBiometrics, authenticate)
Supabase Auth API (mocked: refreshSession, signOut)
flutter_secure_storage API (mocked read/write for deterministic state)
data models
StoredSession (token, expiry, userId)
BiometricType enum
SecureStorageKey enum
AuthState (Riverpod state: unauthenticated, authenticated, locked, loading)
UserRole (admin, coordinator, peer_mentor)
performance requirements
Each individual test case completes within 30 seconds
Full integration suite completes within 3 minutes
No memory leaks — ProviderContainer disposed after each test
security requirements
Test Supabase project credentials must be stored in CI secrets, never in source code
Mocked sessions must use clearly fake tokens (e.g., 'test-token-do-not-use') to prevent accidental use of real credentials
flutter_secure_storage mocked in-memory — no real Keychain/Keystore writes during tests

Execution Context

Execution Tier
Tier 8

Tier 8 - 48 tasks

Can start after Tier 7 completes

Implementation Notes

Use mocktail for mocking platform channels since local_auth uses method channels — mock the LocalAuthentication class directly. For Supabase, use the Supabase Dart client's built-in test utilities or create a fake GoTrueClient. Structure tests with arrange-act-assert pattern: (1) arrange — override providers with mocked implementations, seed SecureSessionStorage with a pre-defined StoredSession, (2) act — call the biometric auth use case / trigger the auth event, (3) assert — verify Riverpod state, verify Supabase refresh was/was not called, verify navigation route. For offline detection, mock connectivity_plus to return ConnectivityResult.none.

For token expiry, seed SecureSessionStorage with a StoredSession where expiry is DateTime.now().subtract(Duration(hours:1)). Ensure each test is fully isolated — call container.dispose() in tearDown. Avoid using real timers; use FakeAsync where time-dependent logic is involved. TestFlight distribution applies to release builds, not test execution.

Testing Requirements

This task IS the testing task. Write flutter_test integration tests in test/integration/biometric_auth_flow_test.dart. Use a ProviderContainer with Riverpod overrides to inject mocked LocalAuth, mocked Supabase client, and an in-memory SecureSessionStorage. Group tests by scenario: (1) happy path online, (2) happy path offline with valid cache, (3) expired token, (4) biometric failure cascade, (5) role-based navigation assertions.

Each group should have setup/teardown managing ProviderContainer lifecycle. Verify state transitions explicitly using container.read() after each async step. Run tests on iOS simulator and Android emulator as part of CI pipeline using flutter test integration_test/. Assert navigation using GoRouter or Navigator state checks — do not rely on UI widget text alone.

Component
Supabase Session Manager
infrastructure medium
Epic Risks (3)
high impact medium prob technical

Multiple concurrent callers (e.g., SessionResumeManager and a background sync service) could simultaneously detect a near-expired token and each invoke SupabaseSessionManager.refreshSession(), causing duplicate refresh API calls and potentially a token invalidation race condition on the Supabase Auth server. This can result in one caller receiving a valid refreshed token while another receives a 401, causing intermittent authentication failures.

Mitigation & Contingency

Mitigation: Implement a single-flight pattern inside SupabaseSessionManager so that concurrent refresh calls coalesce into one in-flight request. Use a Dart Completer or AsyncMemoizer to ensure all waiters receive the same refreshed token. Write a concurrent integration test to validate the single-flight behaviour.

Contingency: If the single-flight pattern introduces deadlocks or timeout complexity, fall back to a mutex-based lock with a 10-second timeout, logging a warning if the lock is held longer than expected, and triggering a full re-login if the refresh ultimately fails.

high impact low prob security

Supabase Row-Level Security policies evaluate the JWT claims (user_id, role, org_id) on every query. If the refreshed token contains stale or changed claims — for example if a coordinator's role was updated server-side — RLS may silently block data access even though the session appears valid from the client's perspective, causing confusing empty screens rather than an authentication error.

Mitigation & Contingency

Mitigation: After every token refresh, decode the new JWT and compare key claims (role, org_id) with the cached values. If claims have changed, emit a session-claims-changed event that triggers a role re-resolution and navigation reset. Document this behaviour in the SupabaseSessionManager API contract.

Contingency: If claims drift is detected in production and causes data visibility issues, provide a force-refresh mechanism in the UI (pull-to-refresh on home screen) that clears cached role state and re-fetches from Supabase, accompanied by a user-visible toast indicating the session was refreshed.

medium impact medium prob security

Allowing session resumption from cached local token when offline introduces a window where a revoked or invalidated session can still grant app access. For example, if a coordinator deactivates a peer mentor's account while the mentor is offline, the mentor continues to have access until connectivity is restored and the token is validated server-side.

Mitigation & Contingency

Mitigation: Set a maximum offline grace period (e.g., 24 hours) stored alongside the token in SecureSessionStorage. If the grace period is exceeded, force a full credential re-login regardless of connectivity status. Scope offline access to read-only operations only, requiring connectivity for any write that reaches Supabase.

Contingency: If the offline grace period logic is found to be insufficient for compliance, implement remote session invalidation via a lightweight push notification that clears SecureSessionStorage even when the app is backgrounded, using FCM with a data-only message.