high priority low complexity testing pending testing specialist Tier 1

Acceptance Criteria

Test file `test/stats/stats_models_test.dart` exists and runs cleanly with `flutter test`
Round-trip test for `StatsSnapshot`: `StatsSnapshot.fromJson(snapshot.toJson()) == snapshot` passes for a fully-populated fixture and a minimal fixture (all optional fields null/zero)
Round-trip test for `PeerMentorStatRow`: same round-trip assertion, including `null reimbursement_amount` (must deserialise to null, not 0 or throw)
Round-trip test for `ChartDataPoint`: round-trip passes; `DateTime` fields survive serialisation with UTC timezone preserved
`TimeWindow` enum: `TimeWindow.fromString('last_30_days')` returns the correct enum value; unknown strings throw `ArgumentError` with a descriptive message
Equality check: two `StatsSnapshot` instances constructed from the same JSON are `==` (requires `operator ==` / `Equatable` on models)
Fixture JSON files in `test/fixtures/stats/` exactly mirror column names from the Supabase materialized view SQL (verified by a comment in each fixture file referencing the view name)
Running `flutter test --coverage` on the models file shows 100% line coverage
No test uses `dynamic` — all parsed values are asserted with typed matchers
Tests complete in under 2 seconds total (pure Dart, no async I/O)

Technical Requirements

frameworks
flutter_test
Flutter (dart:convert)
data models
StatsSnapshot
PeerMentorStatRow
ChartDataPoint
TimeWindow
performance requirements
All tests must complete in under 2 seconds — no network calls, no Hive, no Supabase client
security requirements
Fixture files must not contain real user data — use synthetic names and zeroed IDs

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Load fixture JSON from files rather than inlining maps — this keeps tests readable and forces the fixture to stay in sync with the actual view schema. Use `package:equatable` on all model classes if not already present; without `==` override, round-trip tests will always pass vacuously. For `DateTime` round-trip, ensure `toJson` outputs ISO 8601 UTC strings and `fromJson` parses with `DateTime.parse(...).toUtc()` — mismatched timezones are a common source of flaky round-trip failures. For the `TimeWindow.fromString` unknown-string test, use `expect(() => TimeWindow.fromString('bogus'), throwsA(isA()))`.

Keep fixture files under version control — they serve as the schema contract. Add a comment at the top of each fixture file: `// Mirrors columns from mv_peer_mentor_stats as of migration V005` so reviewers know which migration version the fixture reflects.

Testing Requirements

This task IS the testing task. Structure tests in groups: `group('StatsSnapshot', ...)`, `group('PeerMentorStatRow', ...)`, `group('ChartDataPoint', ...)`, `group('TimeWindow', ...)`. Each group covers: (1) fromJson with full data; (2) fromJson with all nullable fields null; (3) toJson produces expected map keys; (4) round-trip equality; (5) field-level type assertions (e.g., `isA()` for date fields). Use `test/fixtures/stats/snapshot_full.json`, `snapshot_minimal.json`, `peer_mentor_row.json`, `chart_data_point.json` as fixture files loaded via `File(...).readAsStringSync()` in a `setUpAll` block.

Run `flutter test --coverage` and fail CI if line coverage on `lib/stats/models/` drops below 100%.

Component
Stats Data Models
data low
Epic Risks (3)
medium impact medium prob technical

Materialized views over large activity tables may have refresh latency exceeding the 2-second SLA under high insert load, causing stale data to appear on the dashboard immediately after a peer mentor registers an activity.

Mitigation & Contingency

Mitigation: Design the materialized view refresh trigger to run asynchronously via a Supabase Edge Function rather than a synchronous trigger, and set a maximum staleness tolerance of 5 seconds documented in the feature spec. Add a CONCURRENTLY refresh strategy so reads are never blocked.

Contingency: If refresh latency cannot meet SLA, fall back to a regular (non-materialized) view for the dashboard and accept slightly higher query cost per request. Revisit materialized approach once Supabase pg_cron or background workers are available.

high impact medium prob integration

The aggregation counting rules for the dashboard may diverge from those used in the Bufdir export pipeline (e.g., which activity types count, how duplicate registrations are handled), creating a reconciliation burden for coordinators at reporting time.

Mitigation & Contingency

Mitigation: Run the BufDir Alignment Validator against a shared reference dataset before any view is merged to main. Encode the counting rules as a shared Supabase function called by both the stats views and the export query builder so there is a single source of truth.

Contingency: If divergence is discovered post-launch, ship a visible banner on the dashboard stating that numbers are indicative and may differ from the export until the reconciliation fix is deployed. Prioritize the fix as a P0 defect.

high impact low prob security

Multi-chapter coordinators (up to 5 chapters per NHF requirement) require RLS policies that filter on an array of chapter IDs, which is more complex than single-value RLS and could be misconfigured, leaking data across chapters or blocking legitimate access.

Mitigation & Contingency

Mitigation: Write integration tests that verify cross-chapter isolation for a coordinator assigned to chapters A and B cannot see data from chapter C. Use parameterized RLS policies with auth.uid()-based chapter lookup to avoid hardcoded values.

Contingency: If RLS misconfiguration is detected in testing, temporarily restrict coordinator queries to single-chapter scope (coordinator's primary chapter) and ship multi-chapter support as a fast-follow patch once RLS logic is verified.