critical priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

100% branch coverage on all FSM transition methods: submitForApproval, approveClaim, rejectClaim, markAsExported
Happy-path test for each of the 4 state transitions verifies correct status update, audit event write, and notification invocation
Guard failure tests: submitting a non-draft claim, approving a non-submitted claim, rejecting a non-submitted claim, exporting a non-approved claim — each throws the correct typed domain exception
Double-export guard test: calling markAsExported() on an already-exported claim throws CLAIM_ALREADY_EXPORTED
Role guard test: peer mentor attempting to approve/reject throws an authorisation exception
Auto-approval branch test: claim below threshold auto-approves without coordinator notification, writes correct audit events
Manual-approval branch test: claim above threshold remains in submitted state and triggers coordinator notification
Notification emission test: verify() confirms each notification method is called exactly once per transition, zero times on failure paths
Notification failure isolation test: mock notification service throws — state transition still returns successfully
All tests are deterministic, hermetic, and run in under 200ms each with no real network or database calls

Technical Requirements

frameworks
Flutter
flutter_test
mocktail (for mocking)
data models
claim_event
assignment
performance requirements
Full test suite must complete in under 30 seconds
Each individual test must complete in under 200ms — no real async delays
security requirements
No real Supabase credentials or network calls in unit tests
Auth context mocked with controlled user IDs and roles to test role-based guards

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Organise the test file as `approval_workflow_service_test.dart` mirroring the source file location. Create a shared `_buildService()` factory helper that wires up all mocks to reduce boilerplate across test cases. For the auto-approval branching tests, parameterise with `@TestParameters` or use a local test data table (Map of threshold values to expected outcomes) to avoid duplicated test code. Ensure all mocks are reset between tests using `reset(mock)` in tearDown().

The 100% branch coverage target is achievable because the FSM is a closed set of conditions — every if/switch branch must have an explicit test for both the true and false outcomes. Run `flutter test --coverage && genhtml coverage/lcov.info` locally to visualise gaps before committing.

Testing Requirements

This task IS the testing work. Use flutter_test with mocktail for all repository and service mocks. Structure tests in a describe-style group() hierarchy: one group per transition method, with sub-groups for happy paths and failure paths. Use setUp() to initialise the service and mocks before each test.

Use verify() and verifyNever() for notification assertion. Use throwsA(isA()) matchers for guard failure assertions. Add a coverage enforcement step in CI using `flutter test --coverage` with a 100% branch threshold on the approval_workflow_service.dart file specifically.

Component
Approval Workflow Service
service high
Epic Risks (3)
high impact high prob technical

The ThresholdEvaluationService is described as shared Dart logic used both client-side and in the Edge Function. Supabase Edge Functions run Deno/TypeScript, not Dart, meaning the threshold logic must be maintained in two languages and can diverge, causing the server to reject legitimate client submissions.

Mitigation & Contingency

Mitigation: Implement the threshold logic as a single TypeScript module in the Edge Function and call it via a thin Dart HTTP client wrapper for client-side preview feedback only. The server is always authoritative; the client version is purely for UX (showing the user whether their claim will auto-approve before they submit).

Contingency: If dual-language maintenance is unavoidable, create a shared golden test file (JSON fixtures with inputs and expected outputs) that is run against both implementations in CI to detect divergence immediately.

medium impact medium prob technical

A peer mentor could double-tap the submit button or a network retry could trigger a duplicate submission, causing the ApprovalWorkflowService to attempt two concurrent state transitions from draft→submitted for the same claim, potentially resulting in two audit events or conflicting statuses.

Mitigation & Contingency

Mitigation: Implement idempotency in the ApprovalWorkflowService using a database-level unique constraint on (claim_id, from_status, to_status) per transition, combined with a UI-level submission lock (disable button after first tap until response returns).

Contingency: Add a deduplication check at the start of every state transition method that returns the existing state if an identical transition is already in progress or completed within the last 10 seconds.

high impact medium prob scope

Claims with multiple expense lines (e.g., mileage + parking) must have their combined total evaluated against the threshold. If individual lines are added asynchronously or the evaluation runs before all lines are persisted, the auto-approval decision may be computed on an incomplete set of expense lines.

Mitigation & Contingency

Mitigation: The Edge Function always fetches all expense lines from the database (not from the client payload) before computing the threshold decision. Define a clear claim submission contract that requires all expense lines to be persisted before the submit action is called.

Contingency: Add a validation step in ApprovalWorkflowService that counts expected vs. persisted expense lines before allowing the transition, returning a validation error if lines are missing.