high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test: updateStatus(claimId, newStatus, currentVersion) succeeds when Supabase update returns 1 affected row — new version is currentVersion + 1
Test: updateStatus() throws OptimisticLockException when Supabase update returns 0 affected rows (simulating concurrent modification)
Test: WHERE clause in update includes BOTH eq('id', claimId) AND eq('version', currentVersion) — verified by capturing the mock call arguments
Test: statusStream(claimId) emits ClaimStatus values on each Realtime update event
Test: statusStream() emits current status immediately on subscribe (initial fetch before Realtime events)
Test: all 6 valid status transitions pass — draft→submitted, submitted→pending_approval, pending_approval→approved, pending_approval→rejected, approved→exported, any→draft (reset)
Test: invalid status transition (e.g., exported→submitted) throws InvalidStatusTransitionError
Test: concurrent update scenario — two simultaneous updateStatus() calls, only the first succeeds, second throws OptimisticLockException
All 8 test cases pass with flutter_test runner
Mock Supabase client captures and verifies the exact SQL filter arguments for optimistic lock WHERE clause

Technical Requirements

frameworks
flutter_test
mocktail
fake_async (for stream timing tests)
apis
Supabase PostgREST — mocked update with .eq() chaining on claim_status table
Supabase Realtime — mocked channel for statusStream() tests
data models
claim_event (from_status, to_status for transition validation)
assignment (claim ownership context)
performance requirements
All tests complete in < 10 seconds — fake_async for stream tests avoids real timers
security requirements
Test UUIDs must be syntactically valid UUIDs but non-real — use Uuid().v4() in test setup

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The optimistic lock test is the most critical: simulate the concurrent conflict by configuring the mock to return PostgrestResponse with count=0 (no rows updated). The repository must interpret 0-row updates as a conflict — document this as the implicit lock signal. For status transition tests, consider using a table-driven approach: define a const List<(ClaimStatus from, ClaimStatus to, bool valid)> and loop through it to generate parameterized tests. This ensures all transitions are covered without repetitive code.

The stream test requires careful setup: statusStream() likely starts with an initial fetch, then subscribes to Realtime — mock both the initial select() and the channel subscription in sequence. Use StreamMatcher (emitsInOrder, emitsError) for stream assertions.

Testing Requirements

Unit tests in flutter_test using mocktail and fake_async. Key challenge: mock the Supabase update chain and capture the .eq() filter arguments to verify the WHERE clause contains both claim_id AND version. Use a captor pattern (mocktail verify(mock.from('claim_status').update(any).eq('id', captureAny).eq('version', captureAny))) to assert both filter values. For stream tests, use StreamController in the mock to emit simulated Realtime events and verify they appear in statusStream().

Use fake_async for any timeout-related stream behavior. Structure in group('ExpenseClaimStatusRepository', ...) with nested groups for 'updateStatus', 'statusStream', and 'statusTransitions'. Place at test/infrastructure/repositories/expense_claim_status_repository_test.dart.

Component
Expense Claim Status Repository
data medium
Epic Risks (3)
medium impact medium prob technical

Optimistic locking in ExpenseClaimStatusRepository may produce excessive concurrency exceptions in high-volume coordinator sessions where multiple coordinators process the same queue simultaneously, causing confusing UI errors and coordinator frustration.

Mitigation & Contingency

Mitigation: Design the locking strategy with a short retry window (1-2 automatic retries with 200ms back-off) before surfacing the error to the UI. Document the concurrency model clearly so the UI layer can display a contextual 'claim was already actioned' message rather than a generic error.

Contingency: If contention remains high under load testing, switch to a last-writer-wins update with a conflict notification rather than a hard block, and log all concurrent edits for audit purposes.

medium impact medium prob integration

FCM device tokens stored for peer mentors may be stale (app reinstalled, token rotated) causing push notifications for claim status changes to silently fail, leaving submitters unaware their claim was approved or rejected.

Mitigation & Contingency

Mitigation: Implement token refresh on every app launch and store updated tokens in Supabase. ApprovalNotificationService should fall back to in-app Realtime delivery when FCM returns an invalid-token error and should queue a token refresh request.

Contingency: If FCM delivery rates fall below acceptable thresholds in production monitoring, add a polling fallback in the peer mentor claim list screen that checks status on foreground resume.

high impact low prob dependency

Supabase Realtime has per-project channel and connection limits. If many coordinators and peer mentors are simultaneously subscribed across multiple screens, the project may hit quota limits causing subscription failures.

Mitigation & Contingency

Mitigation: Design RealtimeApprovalSubscription to use a single shared channel per user session rather than per-screen subscriptions. Implement subscription reference counting so channels are only opened once and reused across screens.

Contingency: Upgrade the Supabase plan tier if limits are reached, and implement graceful degradation to polling with a 30-second interval when Realtime is unavailable.