high priority medium complexity testing pending testing specialist Tier 6

Acceptance Criteria

Cache hit scenario: when SummaryOfflineCache.isCached returns true, the repository returns the cached AnnualSummary and the Supabase client's select() method is never called
Cache miss scenario: when isCached returns false, the repository calls Supabase select(), maps the response to AnnualSummary, calls SummaryOfflineCache.put() to cache the result, and returns the fresh data
Network failure with stale cache: when Supabase throws a network exception and isCached returns true, the repository returns the cached data alongside a staleness flag (AnnualSummaryResult.isStale == true)
Network failure without cache: when Supabase throws a network exception and no cached data exists, the repository throws an AnnualSummaryFetchException with the original error cause preserved
forceRefresh=true bypasses isCached check, always calls Supabase select(), calls SummaryOfflineCache.put() with the fresh result, and returns fresh data
forceRefresh=true with network failure throws AnnualSummaryFetchException even when cached data exists (force refresh means the caller explicitly needs fresh data)
All mock interactions are verified with Mockito's verify() / verifyNever() to ensure exact call counts
Tests use fake async (fakeAsync / async test) to handle Future-based interactions without real timers
Test file achieves 95%+ branch coverage of AnnualSummaryRepository as reported by flutter test --coverage
No real Supabase network calls or Hive file I/O occur during any test run

Technical Requirements

frameworks
flutter_test
mockito (^5.x) with build_runner generated mocks
fake_async (if needed for timeout simulation)
apis
MockSupabaseClient (generated via @GenerateMocks)
MockSummaryOfflineCache (generated via @GenerateMocks)
AnnualSummaryRepository.getSummary(peerId, year, {forceRefresh})
Supabase PostgREST .from().select().eq().single() chain
data models
annual_summary
performance requirements
All tests complete in under 1 second with no real async delays
security requirements
Mock responses must not include real personnummer, national IDs, or production Supabase project URLs
Test Supabase URLs and anon keys must use clearly fake values (e.g., 'https://test.supabase.co', 'test-anon-key')

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Generate mocks by adding `@GenerateMocks([SupabaseClient, PostgrestFilterBuilder, SummaryOfflineCache])` to a test file and running `dart run build_runner build`. Stub the Supabase query chain: `when(mockClient.from('annual_summaries')).thenReturn(mockQueryBuilder)`. For the network failure path, use `thenThrow(PostgrestException(message: 'Network error', code: '500'))` or a SocketException. The repository should be designed to accept its dependencies via constructor injection (not service locator) to facilitate mocking — verify this in the implementation before writing tests.

For the staleness flag, the repository should return a result wrapper class (e.g., `AnnualSummaryResult(data, isStale)`) rather than throwing; confirm the production API contract with task-006 implementer. Use `expectLater(repo.getSummary(...), throwsA(isA()))` for exception scenarios.

Testing Requirements

Unit tests only using flutter_test and mockito. Generate mocks with `@GenerateMocks([SupabaseClient, SummaryOfflineCache])` and run build_runner once. Organise tests in named groups matching each scenario: 'cache hit', 'cache miss', 'stale cache on network failure', 'no cache on network failure', 'forceRefresh'. Use `when(mockCache.isCached(any)).thenReturn(true/false)` and `when(mockClient.from(...).select()...).thenAnswer(...)` patterns.

Verify with `verifyNever(mockClient.from(any))` in the cache-hit scenario. Target 95%+ branch coverage. Use fixture builders for AnnualSummary and the Supabase response map.

Component
Annual Summary Repository
data medium
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.