medium priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Unit test: when both query paths return identical totals for all activity types, AlignmentReport.isPassing is true and discrepancies is empty
Unit test: when one path returns 47 and the other returns 45 for the same activity type, AlignmentReport.isPassing is false, discrepancies contains one entry with delta = 2
Unit test: discrepancy entry has correct activityTypeId, activityTypeName, dashboardTotal, exportTotal, and delta fields
Unit test: when multiple activity types diverge, all discrepancies are reported — not just the first
Unit test: when one query path throws an exception, AlignmentReport.isPassing is false and the error field is populated
At least one test fixture (`knownDivergentFixture`) exists as a static constant with pre-defined divergent data representing a real-world scenario (e.g., 47 vs 45 for 'Home Visit' activity type)
The `BufdirAlignmentValidator` class has a Dart doc comment block (///) that explains: purpose, how to invoke from tests, how to access from developer menu, what discrepancies it detects, and why accuracy matters for Bufdir grant funding
The doc comment includes a code example showing how to instantiate and call the validator in a test
All tests are grouped under 'BufdirAlignmentValidator' describe block
All tests pass without real Supabase network connection

Technical Requirements

frameworks
Flutter
flutter_test
data models
activity
activity_type
bufdir_column_schema
bufdir_export_audit_log
performance requirements
All unit tests complete in under 3 seconds — no async delays needed beyond Future.value stubs
security requirements
Test fixtures must not contain real organisation IDs, personnummer, or PII — use clearly fake UUIDs and names
Documentation must note that the validator is restricted to debug builds and test environments only

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The doc comment should use /// triple-slash Dart doc format and include a `@example` section with a minimal usage snippet. Explain in the doc comment that Bufdir grant funding is calculated from activity aggregates, so a discrepancy between the dashboard view and the export path means the organisation could be over- or under-reporting to the government, which has financial and legal consequences. The fixture should use realistic activity type names from the Norwegian context (e.g., 'Hjemmebesøk' / 'Home Visit', 'Telefon' / 'Phone Call') translated to English for the code. Keep stubs as simple anonymous functions returning pre-defined maps — avoid complex mock setup for a low-complexity test file.

The documentation is as valuable as the tests here — Bufdir alignment is critical to the organisations' grant funding integrity.

Testing Requirements

Use flutter_test. Use manual stubs (no Mockito required given low complexity) for StatsRepository and ExportAggregationRepository — return Future.value(Map) with pre-defined totals. Define a `knownDivergentFixture` static map constant at the top of the test file for the divergent scenario. Group all tests under `group('BufdirAlignmentValidator', ...)`.

Each test uses a fresh validator instance. Test names must be descriptive sentences: 'returns passing report when all totals match', 'returns failing report with correct delta when totals diverge for one activity type', etc.

Component
Bufdir Alignment Validator
service medium
Epic Risks (2)
medium impact medium prob technical

Supabase realtime channel subscriptions that are not properly disposed on screen close can accumulate in memory across navigation events, causing duplicate invalidation calls, ghost fetches, and eventual memory leaks on long sessions.

Mitigation & Contingency

Mitigation: Implement StatsCacheInvalidator as a Riverpod provider with an explicit ref.onDispose callback that cancels the realtime channel subscription. Write a widget test that navigates away and back multiple times and asserts that only one subscription is active at any given time.

Contingency: If subscription leaks are found in production, add a global subscription registry that enforces at-most-one subscription per channel key, and schedule a dispose sweep on app background events.

medium impact low prob scope

Debouncing rapid inserts may swallow the invalidation signal if the debounce window outlasts the Supabase realtime event delivery window, resulting in the dashboard showing stale totals after a bulk registration completes.

Mitigation & Contingency

Mitigation: Set the debounce window to 800ms (shorter than the typical Supabase realtime delivery latency of 1-2s for batched events) and ensure the leading-edge invalidation fires immediately while trailing duplicates are suppressed. Integration-test with a 20-record bulk insert.

Contingency: If debounce timing proves unreliable, replace debounce with a trailing-edge timer reset on each event and add a guaranteed invalidation 5 seconds after the last event regardless of subsequent events.