high priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

Happy path test: toggle a valid expense type → BLoC emits a state with that type selected, correct calculation result, and no error
Calculation integration test: selecting kilometre type with a known distance emits a state with the expected reimbursement amount matching ExpenseCalculationService output
Draft write integration test: after toggling a valid type, the in-memory Hive store contains a draft matching the BLoC's current selection state
Final submit integration test: calling the submit event on the BLoC triggers repository.persistFinalSelection; the mock Supabase client records one upsert call with the correct payload
Mutual exclusion rejection test: toggling a type that conflicts with the current selection causes the BLoC to emit an error state (not throw); the selection remains unchanged; no draft write occurs for the invalid intermediate state
Draft recovery test: initialise a new BLoC instance over the same in-memory Hive store that contains a previous draft; the BLoC emits a state restoring the saved selection on initialisation
Repository double-guard test: directly call repository.persistFinalSelection with an invalid selection (bypassing BLoC); confirm MutualExclusionViolationException is thrown and Supabase mock records zero writes
All tests use real Hive in-memory adapter (Hive.init with a temp directory or MockAdapter) and mock Supabase client
Tests use bloc_test's emitsInOrder / expectLater for state sequence assertions
All tests pass in isolation and in suite without shared state leaking between cases

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
bloc_test
Hive (in-memory)
Dart
apis
Supabase upsert (mocked)
data models
ExpenseType
ExpenseTypeSelection
ExpenseCalculationResult
ExpenseSelectionState
performance requirements
Full integration test suite must complete in under 10 seconds
Each test case must complete in under 2 seconds
security requirements
No real Supabase credentials in test environment
In-memory Hive adapter must be torn down between test cases to prevent state bleed

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

This test file is the highest-confidence verification for the epic. Wire all real components except Supabase (mocked). The key test setup is: (1) Hive.init with a temporary directory per test (use Directory.systemTemp.createTempSync()), (2) register all Hive type adapters for ExpenseType and related models, (3) inject the mock Supabase client into the repository, (4) inject the repository and calculation service into the BLoC. For state sequence tests with bloc_test, use act to send multiple events in sequence and expect to assert intermediate states.

The mutual exclusion rejection test is critical: assert that the BLoC state after the invalid toggle is identical to the state before (selection unchanged), not just that an error flag is set. Clean up temp directories in tearDownAll. Consider extracting a TestDependencies helper class that wires the full graph to avoid repetition across test cases.

Testing Requirements

Integration tests using flutter_test and bloc_test. Wire together the real BLoC, real ExpenseCalculationService, real ExpenseTypeRepository, real in-memory Hive adapter, and mock Supabase client. Use setUp() to initialise Hive with Hive.init(tempDir) and registerAdapter, tearDown() to clear the Hive box. Use blocTest() for BLoC event/state assertions.

For the draft recovery test, create the BLoC, add events, close it, then create a new BLoC instance over the same Hive box and verify initial state. Run with: flutter test test/integration/expense_selection_flow_test.dart.

Component
Expense Selection BLoC
service medium
Dependencies (4)
In ExpenseSelectionBloc, add an initialise event that calls ExpenseTypeRepository.loadDraft on first load. If a draft exists, hydrate the state with the persisted selection and immediately trigger recalculation to restore a valid ExpenseCalculationResult. If no draft exists, start with empty selection. This prevents the data loss scenario described in the epic where peer mentors abandon mid-flow registrations. epic-expense-type-selection-core-services-task-011 Write unit tests for all three formula paths in ExpenseCalculationService: per-km with standard and capped rates, flat receipt with and without threshold triggers, and transit zone single and multi-zone trips. Include edge cases: zero distance, missing config, maximum amount scenarios, and the aggregation method producing correct totals and auto-approval flags. Target 100% branch coverage on the service class. epic-expense-type-selection-core-services-task-014 Write unit tests for ExpenseTypeRepository covering: local draft write and read round-trip, draft cleared after final submission, Supabase upsert called with correct payload structure, mutual exclusion violation exception thrown and no write executed on invalid selection, and transaction rollback on partial failure. Use mock Supabase client and fake local storage for test isolation. epic-expense-type-selection-core-services-task-015 Add a server-side guard in ExpenseTypeRepository.persistFinalSelection that validates the submitted selection set against the mutual exclusion rules before executing the Supabase upsert. This is the safety net required by the epic spec — even if a client-side bug passes an invalid combination through the BLoC, the repository rejects it with a typed MutualExclusionViolationException before any write reaches the database. Log the violation for audit purposes. epic-expense-type-selection-core-services-task-013
Epic Risks (2)
high impact medium prob dependency

The per-km reimbursement rate and transit zone amounts must be read from org-specific configuration stored in Supabase. If the rate configuration table or RLS policies are not yet deployed when this epic runs, the calculation service cannot be completed and integration tests will fail.

Mitigation & Contingency

Mitigation: Define a RateConfigRepository interface and inject a stub implementation with default HLF rates from day one; write the real Supabase adapter in parallel and swap via dependency injection before merge.

Contingency: If org rate config is delayed beyond this epic's window, ship with the default-rate stub and log a prominent warning; calculate with defaults and surface a 'rates not confirmed' notice in the UI preview.

medium impact low prob technical

If the peer mentor opens an expense claim on two devices simultaneously, the local draft and the Supabase record may diverge. The repository's last-write-wins strategy could silently overwrite a valid selection with a stale one.

Mitigation & Contingency

Mitigation: Add an updated_at timestamp to the draft record and reject saves where the server timestamp is newer than the local copy; surface a conflict resolution prompt rather than silently overwriting.

Contingency: If conflict resolution UI is out of scope, fall back to server-authoritative reads on app foreground resume and discard local draft, notifying the user that their draft was refreshed from the server.