medium priority low complexity testing pending testing specialist Tier 1

Acceptance Criteria

Test file located at test/features/mileage/infrastructure/local_distance_cache_test.dart
All tests pass with flutter test — no skipped or pending tests
Write + read roundtrip: put('A→B', 12.5) then get('A→B') returns 12.5
Cache miss: get('X→Y') on empty cache returns null without throwing
TTL expiry: entry written with TTL of 1 second returns null when get() is called after TTL elapsed (use a fake clock / time injection)
TTL not expired: entry written with TTL of 10 minutes returns the value when get() is called immediately
Invalidation: put('A→B', 12.5), then invalidate('A→B'), then get('A→B') returns null
Invalidation specificity: put('A→B', 12.5) and put('C→D', 7.0), then invalidate('A→B'), then get('C→D') still returns 7.0
SharedPreferences is mocked using SharedPreferences.setMockInitialValues({}) — no real disk I/O
No real DateTime.now() calls — TTL logic must be testable via injected clock abstraction
Test suite completes in under 400 ms (no real sleep/delay)

Technical Requirements

frameworks
Flutter
flutter_test
Dart
data models
LocalDistanceCache
performance requirements
No real time.sleep() in tests — use fake clock injection
Full suite completes in < 400 ms

Execution Context

Execution Tier
Tier 1

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', ...).

Component
Local Distance Cache
infrastructure low
Epic Risks (3)
high impact medium prob security

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.

medium impact low prob scope

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.

low impact low prob technical

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.