Unit tests for calculation service formulas
epic-expense-type-selection-core-services-task-014 — 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.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Before writing tests, read the full ExpenseCalculationService implementation to map every branch (if/else, switch, ternary). Create a test fixture factory that builds ExpenseTypeConfig objects with controlled FormulaParameters — this avoids brittle hardcoded magic numbers spread across tests. For boundary value tests, use a data-driven table approach: final cases = [('zero distance', 0, 0.0), ('standard rate', 10, 45.0), ('at cap', 51, 229.50), ('above cap', 100, 229.50)]. For the auto-approval flag, confirm the exact threshold value from FormulaParameters (e.g., 50 km or NOK threshold) and test both sides of the boundary.
If the service currently has any untestable static dependencies, refactor those to constructor-injected parameters as part of this task.
Testing Requirements
Pure unit tests using flutter_test. No mocking frameworks needed — ExpenseCalculationService should be instantiable with injected config. Use group() blocks to organise by formula path. Use parametrised test patterns (for loops over test tables) for boundary value cases.
Run with: flutter test --coverage test/unit/expense_calculation_service_test.dart. Generate LCOV report and verify 100% branch coverage before marking task complete. Include at least 25 test cases total across all paths.
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.
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.