high priority low complexity testing pending testing specialist Tier 4

Acceptance Criteria

Test file `access_denial_service_test.dart` exists under `test/` mirroring the source path
Test case: `globalAdmin` role → `isBlocked` returns `true`
Test case: `peerMentor` role → `isBlocked` returns `false`
Test case: `coordinator` role → `isBlocked` returns `false`
Test case: `adminPortalUrl` is fetched from mocked `NoAccessConfigRepository` and not hardcoded
Test case: stream emits `AccessDenialState(isBlocked: true)` when role changes to `globalAdmin`
Test case: stream emits `AccessDenialState(isBlocked: false)` when role changes to `peerMentor`
All tests pass with `flutter test` with zero failures
No real network calls are made — all repository dependencies are mocked via `Mockito` or `mocktail`
Test coverage for `AccessDenialService` is at least 90% lines

Technical Requirements

frameworks
Flutter
flutter_test
mocktail or mockito
data models
UserRole
AccessDenialState
NoAccessConfig
performance requirements
All unit tests complete in under 2 seconds total
No async timeouts — use fake async or stream matchers
security requirements
No real BankID or Supabase credentials used in tests
Mock all external dependencies to prevent accidental data access

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

The service under test likely wraps a stream from an auth/role provider and maps role values to `isBlocked` booleans. Ensure the mock repository's `getAdminPortalUrl()` is stubbed to return a test URL string so the assertion is verifiable. When testing stream events, use `expect(service.stateStream, emitsInOrder([...]))` before triggering role changes. Be careful not to leave open `StreamController`s — call `addTearDown(() => service.dispose())` in each test group.

If the service uses Riverpod internally, use `ProviderContainer` with overrides instead of mocking the Riverpod layer.

Testing Requirements

Unit tests only using `flutter_test`. Use `StreamController` or `BehaviorSubject` fakes to simulate role-change events. Use `mocktail` (preferred in Flutter BLoC ecosystem) to mock `NoAccessConfigRepository`. Each role variant must be its own `test()` block.

Use `expectLater` with `emitsInOrder` for stream assertions. Group related tests under `group('AccessDenialService', ...)`. No widget or integration tests in this file.

Component
Access Denial Service
service low
Epic Risks (2)
high impact medium prob technical

If the GoRouter redirect callback evaluates the no-access route itself as a blocked destination, it will trigger an infinite redirect loop, crashing the navigator.

Mitigation & Contingency

Mitigation: Add an explicit guard condition in the redirect callback: return null (no redirect) when the current location is already the no-access route or the logout route. Write a dedicated unit test covering this exact scenario.

Contingency: If the redirect loop is detected in production, deploy a hotfix that adds the null-return guard; the feature can be toggled off via the existing feature-flag infrastructure while the fix is prepared.

medium impact low prob integration

The access-denial-service may read role state before authentication completes (e.g. during app resume), causing a temporary false-positive block that redirects valid peer-mentor users to the no-access screen.

Mitigation & Contingency

Mitigation: Subscribe to the role-state-manager's loading/ready lifecycle and only evaluate role-based access once the RBAC state is confirmed as loaded. Return a 'pending' state that causes the guard to defer rather than redirect.

Contingency: Add a retry mechanism: if a user lands on the no-access screen but their role subsequently resolves as non-blocked, automatically navigate them to the role-based home screen.