high priority low complexity testing pending testing specialist Tier 6

Acceptance Criteria

All tests reside in test/milestone_detection_service_test.dart and pass with `flutter test`
Threshold crossing — hours: stat of 99 h does not trigger 100 h milestone; stat of 100 h triggers it; stat of 101 h also triggers it
Threshold crossing — contacts: same boundary logic verified for unique contacts metric
Threshold crossing — streak: same boundary logic verified for consecutive-day streak metric
Newly-unlocked distinction: a milestone present in previousMilestones is returned as Milestone(isNew: false); one absent from previousMilestones is returned as Milestone(isNew: true)
Significance ranking: returned list is sorted descending by significance score; equal significance preserves stable order
Description template: placeholders (e.g., {hours}, {contacts}) are replaced with correct numeric values from the input stats; no placeholder remains in the output string
Zero-value stats: passing AnnualStats.empty() returns an empty milestone list with no thrown exceptions
Exactly-at-threshold: stat value equal to threshold is treated as crossed (inclusive boundary)
Multiple simultaneous milestones: all applicable milestones are returned in the same call, not just the highest-significance one
Empty milestone config: if the config repository returns an empty list, detect() returns an empty list with no exceptions
Test suite achieves ≥85% branch coverage on MilestoneDetectionService

Technical Requirements

frameworks
Flutter
flutter_test
data models
Milestone
MilestoneConfig
AnnualStats
MilestoneMetricType (enum)
performance requirements
Full test suite completes in under 5 seconds
security requirements
No real user data in test fixtures

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Create a MilestoneConfigFactory test helper that produces MilestoneConfig objects for specific thresholds — this avoids coupling test setup to the internal config structure. For ranking order tests, construct at least 4 milestones with distinct significance scores and assert the entire returned list order using containsAllInOrder matcher. For template population tests, assert both that the expected value appears and that the raw placeholder string does NOT appear in the output. If MilestoneDetectionService is a pure function (stateless), tests will be straightforward; if it has repository dependencies, inject fakes via constructor rather than relying on service locators.

Testing Requirements

Pure unit tests using flutter_test. No network or database calls — inject all dependencies via constructor with fakes or Mockito mocks. Organise in group() blocks: 'threshold detection by metric type', 'newly-unlocked vs previously-held', 'significance ranking', 'description template population', 'zero-value and empty config edge cases'. Use parametrised-style tests (e.g., a list of (input, expected) pairs iterated in a loop) for threshold boundary checks to reduce duplication.

Verify ≥85% branch coverage via `flutter test --coverage`.

Component
Milestone Detection Service
service medium
Epic Risks (3)
high impact medium prob integration

Activity records may contain duplicate entries (as evidenced by the duplicate-detection feature dependency) or proxy-registered activities that should be attributed differently. Including duplicates or mis-attributed records would produce inflated stats, undermining trust in the summary.

Mitigation & Contingency

Mitigation: Implement the aggregation query to join against the deduplication-reviewed-flag on activity records and filter out unresolved duplicates. Coordinate with the duplicate-detection feature team to confirm the authoritative flag field before implementing the RPC. Include a data-quality warning in the summary when unresolved duplicates are detected.

Contingency: If deduplication state is unreliable at release time, add a prominent disclaimer in the summary UI noting that figures reflect all registered activities and may include duplicates pending review. Track a follow-up task to re-aggregate after deduplication runs.

medium impact high prob scope

Each organisation wants to define their own milestone thresholds (e.g., NHF's counting model differs from HLF's certification model). Implementing configurable thresholds may expand scope significantly if the configuration UI is expected in this epic.

Mitigation & Contingency

Mitigation: Scope this epic strictly to the evaluation engine and a hardcoded default threshold set. Define the MilestoneDefinition interface with an organisation_id discriminator so per-org configs can be loaded from the database in a later sprint. Build the admin configuration UI as a separate follow-on task outside this epic.

Contingency: If stakeholders require per-org milestone configuration before launch, deliver a JSON-based configuration file per org as an interim solution, loaded from Supabase storage, until a full admin UI is built.

medium impact medium prob technical

Android 13+ restricts access to media collections and requires READ_MEDIA_IMAGES permission for gallery saves, while older Android versions use WRITE_EXTERNAL_STORAGE. Handling both permission models correctly across the device matrix is error-prone.

Mitigation & Contingency

Mitigation: Use the permission_handler Flutter package with version-aware permission requests abstracted behind the summary-share-service interface. Write platform-specific unit tests for both Android API levels in the test harness. Test on a minimum of three Android versions (API 29, 32, 34) in CI.

Contingency: If gallery save is broken on specific Android versions at launch, disable the 'Save to gallery' option on affected API levels and surface only clipboard and system share sheet, which require no media permissions.