Unit test LocalDistanceCache persistence and TTL expiry
epic-mileage-reimbursement-entry-infrastructure-task-011 — Write unit tests for LocalDistanceCache covering: write and read roundtrip, cache miss returns null, TTL expiry removes stale entry, route hash collision safety, and invalidation clears the correct entry. Use a mocked SharedPreferences in-memory backend for test isolation.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
If LocalDistanceCache (implemented in task-004) does not yet accept a clock injection parameter, add a @visibleForTesting clock parameter to the constructor with a default of () => DateTime.now(). This is the standard Flutter pattern for time-dependent unit testing. Route hash keys should be tested with values that include special characters (spaces, unicode, long strings) to verify the hashing function handles them without collision. Verify that stale entries are lazily evicted on get() (not proactively cleaned up by a timer) — the test for expiry should call get() after advancing the clock and expect null.
Do not use Mockito or any third-party mocking package — the SharedPreferences mock and clock injection cover all test isolation needs.
Testing Requirements
Pure unit tests using flutter_test. Use SharedPreferences.setMockInitialValues({}) in setUp() to provide an in-memory backend. Inject a clock abstraction (e.g., a DateTime Function() getter defaulting to DateTime.now) into LocalDistanceCache to allow tests to control the current time without real sleeps. TTL expiry is tested by advancing the injected clock past the TTL rather than sleeping.
No platform channels, no network. Group tests: group('read/write', ...), group('TTL', ...), group('invalidation', ...).
Supabase Row Level Security policies for mileage_claims may require complex join conditions to distinguish peer mentor (own claims only) from coordinator (chapter-scoped claims) access. If the RLS policy is misconfigured, coordinators could see claims outside their chapter scope or peer mentors could read other users' data, causing a data privacy incident.
Mitigation & Contingency
Mitigation: Write RLS policy SQL as part of this epic with explicit test cases for each role. Use Supabase's built-in policy testing tools and add integration tests that assert cross-user data isolation before merging.
Contingency: If RLS configuration proves too complex to test reliably within the epic, add an application-layer guard in the adapter that filters query results by authenticated user ID as a defence-in-depth measure while the policy is corrected.
Norwegian tax authority reimbursement rounding rules may change or may not be publicly documented in machine-readable form. Using the wrong rounding convention could cause systematic over- or under-payment, leading to compliance issues for the organisation.
Mitigation & Contingency
Mitigation: Source the exact rounding specification from HLF's finance team before implementing MileageCalculationService. Document the rule as a comment in the source code and link to the authoritative reference.
Contingency: If the rule is ambiguous, implement both truncation and half-up rounding behind a configuration flag so the organisation can switch without a code release.
SharedPreferences reads and writes are asynchronous. If the distance prefill service (built in the next epic) calls LocalDistanceCache concurrently with a post-submission write, a race condition could result in the cache returning a stale or partially written value, causing the next form load to show an incorrect default.
Mitigation & Contingency
Mitigation: Wrap all SharedPreferences access in LocalDistanceCache with sequential async operations and document the non-concurrent usage contract in the class API.
Contingency: If race conditions are observed in testing, introduce a simple mutex pattern using a Completer to serialise cache operations.