high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test file is located at `test/features/certification/bloc/certification_bloc_test.dart` and runs with `flutter test` without errors
All three event handlers (LoadCertification, RecordRenewal, EnrolInCourse) have explicit test cases for: (a) success path, (b) ServiceException path, (c) unexpected exception path
State sequences in each test are asserted using `blocTest`'s `expect` parameter with exact state lists — no `anything` matchers on state types
CertificationManagementService is mocked using Mockito's `@GenerateMocks` annotation or mocktail; the generated mock file is committed and up to date
Concurrent event tests: LoadCertification droppable — rapid double-fire results in exactly one service call. RecordRenewal sequential — two sequential renewal events both complete in order
Auto-reload test: after RecordRenewal success, assert that getCertification is called a second time and CertificationLoaded reflects the new data
Pull-to-refresh test: calling `bloc.refresh(id)` produces `[CertificationLoading, CertificationLoaded]` state sequence
Equality test: when reload returns the same Certification, assert no duplicate CertificationLoaded state is emitted
Branch coverage reported by `flutter test --coverage` is 80% or higher for the BLoC module files
All tests are deterministic — no `sleep`, `Future.delayed`, or real timers; use `fakeAsync` or `bloc_test`'s built-in async handling

Technical Requirements

frameworks
flutter_test
bloc_test
Mockito (build_runner generated mocks) or mocktail
apis
CertificationManagementService (mock)
data models
Certification (test fixture factory)
performance requirements
Full test suite must complete in under 10 seconds on a developer machine
No real network calls — all Supabase interactions are behind the service mock
security requirements
Test fixtures must not use real certification IDs, real peer mentor UUIDs, or PII — use generated fake UUIDs

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use `mocktail` if the project already uses it (check pubspec.yaml); otherwise use Mockito with `@GenerateMocks([CertificationManagementService])` and run `flutter pub run build_runner build`. Prefer `mocktail` for zero-codegen ergonomics. `blocTest` signature reminder: `blocTest('description', build: () => CertificationBLoC(service: mockService), act: (bloc) => bloc.add(LoadCertification(certificationId: 'id-1')), expect: () => [isA(), isA()])`. For the droppable concurrency test, use `act: (bloc) { bloc.add(LoadCertification(certificationId: 'id-1')); bloc.add(LoadCertification(certificationId: 'id-1')); }` — the droppable transformer ensures the second is dropped.

Verify with `verify(() => mockService.getCertification(any())).called(1)`. Create a `certificationFixture({String? id, String status = 'active'})` factory method to keep test data DRY.

Testing Requirements

This task IS the testing requirement. All tests use flutter_test and bloc_test packages. Create a `CertificationFixtures` helper class in `test/fixtures/certification_fixtures.dart` that returns pre-built `Certification` objects for reuse across tests. Use `setUp` to initialise a fresh BLoC and mock before each test.

Use `tearDown` to call `bloc.close()`. Group tests by event type using `group()`. Run `flutter test --coverage` and verify lcov report shows ≥80% line coverage on `lib/features/certification/bloc/`. Include a brief comment above each test group explaining what scenario is covered.

Regression: after implementing all tests, run them against the unimplemented stub BLoC to confirm they all fail (red phase), then run against the real implementation to confirm they all pass (green phase).

Component
Certification BLoC
service medium
Epic Risks (2)
medium impact low prob technical

Supabase Edge Functions can have cold-start latency that causes the nightly cron to time out when processing large cohorts of expiring certifications, resulting in partial reminder dispatches.

Mitigation & Contingency

Mitigation: Batch the cron processing in chunks of 50 mentors per iteration. Use pagination with a cursor to resume processing if the function is re-invoked. Keep total invocation time well under the Edge Function timeout limit.

Contingency: If timeouts occur in production, split the cron into two separate functions: one for reminders and one for auto-pauses, each with its own schedule offset to reduce peak load.

low impact medium prob technical

Certification BLoC covers three distinct workflows (view, renew, enrol) which may lead to an overly complex state machine that is hard to test and maintain, particularly when error states from multiple concurrent operations need to be differentiated in the UI.

Mitigation & Contingency

Mitigation: Use separate sealed state classes per workflow (CertificationViewState, RenewalState, EnrolmentState) composed into a single BLoC state wrapper. Follow the existing BLoC patterns established in the codebase for consistency.

Contingency: If the BLoC grows too complex, split into two BLoCs: CertificationBLoC (view/load) and CertificationActionBLoC (mutations), connected via a shared stream.