high priority low complexity testing pending testing specialist Tier 3

Acceptance Criteria

Test: fetchPage(page: 1, pageSize: 20, orgId: 'org-1') invokes Supabase query builder with `.eq('organization_id', 'org-1')` and `.range(0, 19)`
Test: successful fetch response is deserialized into a non-empty List<ReportHistoryRecord> with all fields correctly mapped
Test: insertReportRecord serializes the domain model to JSON including organization_id, activity_type, activity_date, duration_minutes, and submitted_by fields
Test: updateReportStatus calls `.update({'status': newStatus}).eq('id', recordId).eq('organization_id', orgId)` — verifying both filter conditions
Test: PostgrestException from Supabase is caught and re-thrown as a typed RepositoryException (or equivalent domain exception)
Test: network timeout or null response is handled and throws a typed exception rather than propagating unhandled
Test: organization_id is always included as a filter in every query method — no method omits the scope
All tests are pure unit tests with zero network calls — verified by absence of real HTTP in test output
Mockito-generated mocks are committed as `.mocks.dart` files produced by `build_runner`
Test file is under `test/unit/repositories/` and all tests pass with `flutter test`

Technical Requirements

frameworks
flutter_test
mockito (^5.x)
build_runner (for mock generation)
apis
Supabase PostgREST query builder interface (SupabaseQueryBuilder)
ReportHistoryRepository public interface (fetchPage, insertReportRecord, updateReportStatus)
data models
ReportHistoryRecord (domain model)
bufdir_report_history (table schema, for JSON mapping assertions)
performance requirements
All unit tests complete in under 3 seconds total
security requirements
organization_id scoping must be verified as present in every query — missing scope is a test failure, not a warning

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Supabase Flutter's query builder uses a fluent API that returns the same builder object from filter methods, making it chainable. To mock this correctly, each chained method (`.from()`, `.select()`, `.eq()`, `.range()`, `.order()`) must return the mock builder itself: `when(mockBuilder.eq(any, any)).thenReturn(mockBuilder)`. The final terminal call (`.execute()` or awaited result) returns the `PostgrestResponse`. If the repository uses the newer `supabase_flutter` API where `.select()` directly returns a `Future>`, adjust mocking accordingly — check the exact version used in `pubspec.yaml`.

Organize test file as `report_history_repository_test.dart`. Use `const` fixture maps for expected JSON payloads to keep assertions readable. Add a dedicated test group `'organization_id scoping'` that runs the same check across all public methods to make the security invariant explicit.

Testing Requirements

Pure unit tests using Mockito mocks. Use `@GenerateMocks([SupabaseClient, SupabaseQueryBuilder, PostgrestFilterBuilder])` annotations and run `flutter pub run build_runner build` to generate `.mocks.dart` files. Structure: one `group()` per repository method, with sub-cases for success and each error scenario. Use `when(mockClient.from('bufdir_report_history')).thenReturn(mockQueryBuilder)` to chain mock responses.

Assert both the return value (correct deserialization) and the call arguments (correct query construction). Verify `organization_id` filter presence using `verify(mockBuilder.eq('organization_id', any)).called(greaterThanOrEqualTo(1))`. Test error mapping by having the mock throw `PostgrestException` and asserting the repository re-throws the correct domain exception type.

Component
Report History Repository
data low
Epic Risks (3)
high impact medium prob security

Incorrectly authored RLS policies could silently allow cross-organization data reads, exposing sensitive report history of one organization to coordinators of another in a multi-tenant environment.

Mitigation & Contingency

Mitigation: Write integration tests that explicitly authenticate as a user from organization A and assert zero rows are returned for organization B's history records. Use Supabase's built-in RLS testing utilities and review policies with a second developer.

Contingency: If a cross-tenant leak is discovered post-deployment, immediately revoke all active sessions for affected organizations, audit query logs for unauthorized access, and patch the RLS policy in a hotfix migration before re-enabling access.

medium impact medium prob technical

The 5-year retention policy for report files may conflict with Supabase Storage's lack of native lifecycle rules, requiring a custom pg_cron job that could fail silently and either delete files prematurely or never clean up.

Mitigation & Contingency

Mitigation: Implement the retention cleanup as a documented pg_cron job with explicit logging to a separate audit_jobs table. Add a Supabase Edge Function health check that verifies the cron job ran within the last 25 hours.

Contingency: If the cron job fails, files accumulate in storage (non-critical for compliance — over-retention is safer than under-retention). Alert the ops team via monitoring and manually trigger the cleanup function once the cron issue is resolved.

medium impact low prob integration

Signed URL generation depends on the requesting user's Supabase session being valid at the time of the call. If sessions expire during a long screen interaction, URL generation will fail with an authorization error and confuse the coordinator.

Mitigation & Contingency

Mitigation: Wrap signed URL generation in the service layer with a session-refresh check before calling Supabase Storage. Generate URLs on demand (tap-to-download) rather than pre-generating them for all list items on screen load.

Contingency: If a URL generation fails due to session expiry, surface a clear error message prompting the coordinator to re-authenticate, then automatically retry URL generation after session refresh completes.