high priority low complexity testing pending testing specialist Tier 2

Acceptance Criteria

Test file `test/core/utils/url_launcher_util_test.dart` exists
Test case: valid HTTPS URL with `canLaunchUrl` returning true and `launchUrl` returning true → result is `UrlLaunchSuccess`
Test case: URL with unsupported scheme (e.g., `xyz://`) where `canLaunchUrl` returns false → result is `UrlLaunchUnsupported`
Test case: `launchUrl` (package) throws `PlatformException` → result is `UrlLaunchError` with a non-empty message, no exception propagated to caller
Test case: malformed URL string that fails `Uri.tryParse` → result is `UrlLaunchError` before any async platform call
Test case: `onFailure` callback is invoked exactly once when result is `UrlLaunchUnsupported`
Test case: `onFailure` callback is invoked exactly once when result is `UrlLaunchError`
Test case: `onFailure` callback is NOT invoked when result is `UrlLaunchSuccess`
All tests pass via `flutter test` with zero analyzer warnings

Technical Requirements

frameworks
Flutter
flutter_test
mocktail or mockito
apis
url_launcher_platform_interface (mocked)
data models
UrlLaunchResult
UrlLauncherUtil
performance requirements
All unit tests complete in under 300ms total
security requirements
No real platform channels invoked—all url_launcher calls mocked

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

To mock `url_launcher` without brittle platform-channel patching, rely on the constructor-injected interface introduced in task-005. With `mocktail`, create a `MockUrlLauncherInterface extends Mock implements UrlLauncherInterface` and stub `canLaunchUrl` / `launchUrl` with `when(() => mock.canLaunchUrl(any())).thenAnswer(...)`. For the `PlatformException` scenario, use `.thenThrow(PlatformException(code: 'ACTIVITY_NOT_FOUND'))`. The callback invocation tests should use a simple closure that appends to a list: `final calls = []; final util = UrlLauncherUtil(platform: mock, onFailure: calls.add);` then assert `calls.length == 1`.

Testing Requirements

Pure unit tests using `flutter_test` and `mocktail`. Mock the `UrlLauncherPlatform` (or the injected interface from task-005) at the constructor level. Use a captured `onFailure` callback (a simple `bool callbackInvoked` flag or a `List captured` list) to assert callback invocation. Group tests by scenario using `group()` blocks for readability.

No widget pumping required—this is a plain Dart class test. Target ≥95% line coverage on `url_launcher_util.dart`.

Component
URL Launcher Utility
infrastructure low
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.