high priority low complexity testing pending testing specialist Tier 2

Acceptance Criteria

Test file `no_access_config_repository_test.dart` exists under `test/` mirroring the `lib/` directory structure
Test case: successful remote fetch returns a populated NoAccessConfig object with all expected fields (adminPortalUrl, supportEmail, etc.)
Test case: a second call within the cache TTL returns the cached value and the Supabase client's `.from()` method is called exactly once across both calls
Test case: a call made after cache TTL has expired triggers a new network call, verified by mock invocation count
Test case: when Supabase client throws a `PostgrestException` or network error, the repository returns the local fallback constants without rethrowing
All tests pass via `flutter test` with zero warnings in strict mode
Mock Supabase client is injected via constructor—no static singletons patched
Test coverage for the repository file is ≥90% as reported by `flutter test --coverage`

Technical Requirements

frameworks
Flutter
flutter_test
mockito or mocktail
apis
Supabase PostgREST (mocked)
data models
NoAccessConfig
NoAccessConfigRepository
performance requirements
All unit tests complete in under 500ms total
No real network calls—100% mocked Supabase interactions
security requirements
No real Supabase credentials or URLs in test files
Use constant placeholder values for any URL or key fields in test fixtures

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Inject the Supabase client (or a thin `NoAccessConfigDataSource` abstraction) through the constructor so tests can substitute a mock without touching global state. Model the cache as a private field holding a nullable `(NoAccessConfig, DateTime)` tuple; the TTL check is a simple `DateTime.now().isAfter(cachedAt.add(ttl))`. For the expiry test, either expose a `@visibleForTesting` clock parameter or use a `FakeClock` approach. The fallback constants should live in a separate `no_access_config_defaults.dart` file so they are easily importable by tests without pulling in the full Supabase dependency.

Avoid using `setUp`/`tearDown` for mock state if it obscures which test is asserting which behaviour—prefer self-contained `group` blocks.

Testing Requirements

Unit tests only (no widget or integration tests in this task). Use `flutter_test` with `mocktail` (preferred for null-safety ergonomics) or `mockito` with code generation. Cover four distinct branches: happy path fetch, cache hit, cache miss after expiry, and network error fallback. Assert both return values and mock interaction counts (e.g., `verify(() => mockClient.from('no_access_config')).called(1)`).

Aim for ≥90% line coverage on the repository class.

Epic Risks (2)
high impact medium prob technical

Supabase remote config may be unavailable at app startup (network error, cold start), causing the repository to return no blocked-role list. If the fallback is empty, blocked users could access the app.

Mitigation & Contingency

Mitigation: Define a local constants fallback list of blocked roles compiled into the app binary. Remote config enriches or overrides this list when available.

Contingency: If remote config repeatedly fails in production, pin the blocked-role list to local constants only and disable remote override until the Supabase config endpoint is stabilised.

medium impact low prob dependency

url_launcher package behaviour differs between iOS and Android (e.g. canLaunchUrl returning false on some Android configurations), leading to silent failures when the admin portal link is tapped.

Mitigation & Contingency

Mitigation: Run canLaunchUrl check before every launch attempt and surface a descriptive inline error message (e.g. 'Could not open link — visit admin.example.org manually') when the check fails.

Contingency: If canLaunchUrl is consistently unreliable on a target platform, replace the tap-to-open pattern with a copyable text field showing the URL as a fallback.