high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test: processAll([claim1, claim2, claim3]) with service returning success for all → BulkApprovalResult.successCount == 3, failureCount == 0
Test: processAll([claim1, claim2, claim3]) with service failing for claim2 → successCount == 2, failureCount == 1, failed list contains claim2.id
Test: claim1 succeeding must not be rolled back when claim2 fails — verify service.approve(claim1.id) was called and succeeded in partial failure scenario
Test: processAll([]) with empty list → returns BulkApprovalResult with successCount == 0, failureCount == 0, no service calls made
Test: processAll with service throwing exception for all records → failureCount == total input count, successCount == 0, no exception propagates to caller
Test: each record in input list results in exactly one service.approveExpenseClaim call — verify via mock interaction count
Test: BulkApprovalProcessor does not call service.rejectExpenseClaim during an approve-all batch
All tests use Mockito or mocktail to mock ApprovalWorkflowService — no real Supabase calls
Test file follows flutter_test conventions and runs in under 2 seconds total
Test descriptions are written in English and clearly state the scenario being tested

Technical Requirements

frameworks
Flutter
flutter_test
data models
claim_event
activity
performance requirements
All unit tests complete in under 2 seconds — no async delays or real I/O
security requirements
Mock service must not contain real credentials or claim data — use generated UUIDs for test fixtures

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

BulkApprovalProcessor should process records sequentially (not in parallel) by default to avoid overwhelming the Supabase connection pool and to produce deterministic test behavior. Each record's success or failure is captured independently — use a try/catch per iteration. Return a BulkApprovalResult value object: { successCount, failureCount, failedClaimIds, errors }. The processor should accept ApprovalWorkflowService as a constructor parameter (dependency injection) to enable mocking.

Test fixture factory: create a helper makeTestClaim({String? id}) that returns a minimal ExpenseClaim with a generated UUID ID. Avoid using real models with required fields that are tedious to populate — use copyWith or factory constructors.

Testing Requirements

Use flutter_test with mocktail (or mockito with build_runner). Structure test file with group() blocks per scenario: 'all success', 'partial failure', 'complete failure', 'empty input'. Each test uses setUp to reinitialize mock and processor. Use verify(mockService.approveExpenseClaim(any)).called(N) for interaction counts.

For async tests use async/await with expect(await processor.processAll(claims), isA()). Aim for 100% branch coverage of BulkApprovalProcessor logic.

Component
Bulk Approval Processor
service medium
Epic Risks (2)
medium impact medium prob scope

If a bulk approval batch partially fails (some claims approved, some failed), the UI must communicate which specific claims failed without overwhelming the coordinator. A poorly designed error display could cause coordinators to re-approve already-approved claims or miss claims that still need attention.

Mitigation & Contingency

Mitigation: Design the BulkApprovalResult display to show a clear summary (e.g., '14 approved, 2 failed') with a collapsible list of failed claims including their IDs and submitter names. Failed claims should remain selected in the queue so the coordinator can retry them individually.

Contingency: If the summary UI proves insufficient, add a dedicated 'bulk action history' sheet showing the last bulk operation result, accessible from the queue screen header.

medium impact low prob technical

If the app is backgrounded or the network drops while the coordinator has the ApprovalActionSheet open mid-decision, the typed comment could be lost and the transition state could be ambiguous, potentially causing a coordinator to believe they approved a claim that was never submitted.

Mitigation & Contingency

Mitigation: Persist the in-progress action sheet state (selected action + comment text) to a local draft store keyed on claim ID. On sheet re-open for the same claim, restore the draft. After confirmed submission, verify the resulting claim status from the server before dismissing the sheet.

Contingency: On network error during submission, display a persistent retry banner within the sheet rather than dismissing it, so the coordinator can resubmit without re-entering their comment.