high priority low complexity testing pending testing specialist Tier 5

Acceptance Criteria

Test file is placed at test/features/certificate_expiry_notifications/widgets/expiry_notification_banner_test.dart and all tests pass with flutter test
Tests cover expiring-soon visual state: certificate name visible, 'X days remaining' label, AppButton with renewal copy, ExpiryStatusIndicator present
Tests cover expired visual state: 'Certificate expired' label present, correct CTA copy, ExpiryStatusIndicator in compact mode
Test confirms banner is not rendered (SizedBox.shrink or absent from widget tree) when record.isAcknowledged == true for both states
Test covers BLoC state transitions: pumping Loading state shows a loading indicator (or no banner), pumping Loaded state shows banner, pumping Loaded state with isAcknowledged=true hides banner
Test verifies tapping the CTA button dispatches exactly one onRenewTap callback invocation (counter-based tap tracking)
Accessibility test: tester.getSemantics() confirms a Semantics node with a non-empty label exists for the banner in both visual states
Tests use MockBloc (via bloc_test or manual MockBloc) — no real Supabase or network calls
All tests are independent and deterministic (no shared mutable state between test cases)
flutter test passes with zero failures and dart analyze reports zero issues on the test file

Technical Requirements

frameworks
Flutter
flutter_test
bloc_test (MockBloc)
flutter_bloc
data models
ExpiryNotificationRecord
CertificateExpiryNotificationLoaded
CertificateExpiryNotificationLoading
performance requirements
Each individual test case must complete in under 2 seconds
security requirements
Tests must not contain hardcoded credentials or API keys
ui components
ExpiryNotificationBanner
ExpiryStatusIndicator (stub/mock acceptable)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Create a reusable test helper function buildTestWidget(ExpiryNotificationRecord record, {VoidCallback? onRenewTap}) that wraps the banner in MaterialApp + BlocProvider to reduce boilerplate across test cases. For BLoC state transition tests, use tester.pumpWidget then update MockBloc state via whenListen and call tester.pump() to trigger rebuild. Do not test color values directly in widget tests (colors can change with theme; leave visual regression for golden tests if required).

Focus on structure, labels, button presence, and event dispatch. Use a FakeExpiryNotificationRecord factory (using default values with named param overrides) to make test data setup readable.

Testing Requirements

All tests are widget tests using flutter_test. Use bloc_test's MockBloc to inject controlled BLoC states. Structure tests in groups: group('expiring-soon state', ...), group('expired state', ...), group('BLoC state transitions', ...), group('persistence', ...), group('accessibility', ...). Use testWidgets for each case.

For Semantics checks use tester.getSemantics(find.byType(ExpiryNotificationBanner)) or find semantics by label. Avoid testing internal implementation details — test observable behavior (what is visible to the user and what events are dispatched).

Epic Risks (2)
medium impact medium prob technical

The persistent banner must remain visible across app sessions and only disappear when a specific backend condition is met (renewal or coordinator acknowledgement). If the BLoC state is not properly sourced from the notification record repository on every app launch, the banner may disappear prematurely or fail to reappear after a session restart.

Mitigation & Contingency

Mitigation: Drive the banner's visibility exclusively from a Supabase real-time subscription on the notification records table filtered by mentor_id and acknowledged_at IS NULL. Never persist banner visibility state locally. Write an integration test that restarts the BLoC and verifies the banner reappears from the database source.

Contingency: If real-time subscriptions introduce latency or connection reliability issues in offline-first scenarios, add a local cache flag that is only cleared when the repository confirms the acknowledgement write succeeded, with a cache TTL of 24 hours as a fallback.

high impact low prob security

The notification detail view must conditionally render coordinator-specific actions based on the authenticated user's role. Incorrect role resolution could expose the 'Acknowledge Lapse' action to peer mentors or hide it from coordinators, breaking the workflow and potentially allowing unauthorised state changes.

Mitigation & Contingency

Mitigation: Source the role check from the existing role_state_manager BLoC that is already authenticated against Supabase role claims. Do not rely on a local flag. The coordinator acknowledgement service backend also validates role server-side, providing defence in depth. Add widget tests that render the detail view with mentor and coordinator role fixtures and assert the presence or absence of coordinator actions.

Contingency: If a role resolution bug is found in production, immediately disable the acknowledge action via a feature flag and patch the role check in a hotfix release. The server-side validation in the coordinator acknowledgement service ensures no actual state change can occur even if the button is incorrectly rendered.