high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

Integration test file exists at test/features/receipts/integration/receipt_upload_flow_integration_test.dart
Test: happy path — pick bytes → compress → upload → persist — completes without throwing
Test: after happy path, the SQLite (local cache) record for the claim contains the storage path returned by ReceiptStorageRepository
Test: the storage path stored in SQLite matches the format expected by signed URL generation (e.g., org/{orgId}/receipts/{claimId}/{filename}.jpg)
Test: ReceiptThresholdValidator is consulted before upload and the test asserts requiresReceipt returns true for the test amount (150 NOK with 100 NOK threshold)
Test: when ReceiptStorageRepository upload fails (mock returns error), the SQLite record is NOT created (transactional rollback or skip)
Test: the signed URL generated for the storage path is a non-empty string matching a URL pattern
Test: uploading a second receipt for the same claim creates a second SQLite record without overwriting the first
Test: the compressed image bytes uploaded to mock storage are smaller than the original fixture bytes
All assertions use expect() with descriptive failure messages

Technical Requirements

frameworks
Flutter
flutter_test
Riverpod
apis
Supabase Storage API (mocked)
Supabase Database/PostgREST (mocked)
data models
ClaimReceiptRecord
ReceiptStoragePath
OrgCompressionConfig
OrgThresholdConfig
performance requirements
Integration test must complete in under 20 seconds
Mocked Supabase responses must not introduce artificial delays unless testing timeout handling
security requirements
Test must not use real Supabase credentials — all storage and DB calls are mocked
Fixture images used in tests must not contain real personal data

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Structure the test with a setUp() that builds the ProviderContainer with all mocked dependencies and seeds the SQLite in-memory DB with a test claim record. Use mocktail for all mocked services. The critical assertion is that the storage path in SQLite matches the path returned by the mock ReceiptStorageRepository — this validates the handoff between the upload and persistence layers. For the rollback test, configure the mock ReceiptStorageRepository to throw a StorageException on the first call, then assert that ClaimReceiptRepository.getReceiptsForClaim() returns an empty list.

Document each test with a comment explaining what integration contract it validates. Keep the test self-contained — no shared state between test cases.

Testing Requirements

Use flutter_test with a ProviderContainer and full dependency override for all external services. Mock ReceiptImagePickerIntegration to return a known fixture JPEG byte array. Mock ReceiptStorageRepository to simulate a successful upload returning a deterministic storage path, and a second variant that simulates upload failure. Mock Supabase signed URL generation to return a valid-format URL string.

Use an in-memory SQLite database (or a temp file) for ClaimReceiptRepository to avoid polluting real data. Assert the full data flow by querying the in-memory SQLite after each step. Cover both the success and upload-failure scenarios in separate test cases. This test serves as a contract test for the integration between all five components.

Component
Receipt Storage Repository
data medium
Dependencies (5)
Write unit tests for the ReceiptImagePickerIntegration using a mock implementation of the abstract interface. Verify that pickFromGallery and pickFromCamera return the expected ReceiptImageResult model, that null is returned on user cancellation, and that the Riverpod provider resolves to the correct platform implementation. Use mockito for the image_picker dependency. epic-receipt-capture-and-attachment-foundation-task-010 Write flutter_test unit tests for all ClaimReceiptRepository methods using an in-memory SQLite database. Cover insert, query by claim_id, delete, and update sync status. Assert correct row counts, returned model fields, and error handling for missing claim references. epic-receipt-capture-and-attachment-foundation-task-003 Write integration tests for ReceiptStorageRepository against a Supabase test environment. Cover upload of a small JPEG byte array, verify the file exists at the expected scoped path, generate a signed URL and assert it is non-empty, then delete the file and confirm 404 on subsequent signed URL generation. Use flutter_test with a dedicated test Supabase project. epic-receipt-capture-and-attachment-foundation-task-007 Write comprehensive unit tests for ReceiptThresholdValidator covering: claim exactly at threshold (boundary condition), claim below threshold returns false, claim above threshold returns true, threshold loaded from mocked org config, and offline fallback to cached default of 100 NOK when config unavailable. Use flutter_test with mocked Supabase responses. epic-receipt-capture-and-attachment-foundation-task-013 Write flutter_test unit tests for ReceiptImageCompressor using bundled fixture JPEG images of known dimensions and file sizes. Assert that output bytes are smaller than input, that the output max dimension does not exceed the configured limit, that compression ratio is within expected bounds, and that a zero-byte or corrupt input throws a typed CompressionException. epic-receipt-capture-and-attachment-foundation-task-016
Epic Risks (3)
high impact medium prob security

Supabase Storage RLS policies using org/user/claim path scoping may not enforce correctly if claim ownership is not present in the JWT or if path segments are constructed differently at upload vs. read time, leading to data leakage or access denial for legitimate users.

Mitigation & Contingency

Mitigation: Define and test RLS policies in isolation before wiring to app code. Write integration tests that assert cross-org and cross-user access is denied. Use service-role key only in edge functions, never in client code.

Contingency: If client-side RLS proves insufficient, route all storage reads through a Supabase Edge Function that validates ownership before generating signed URLs, adding a controlled server-side enforcement layer.

high impact medium prob technical

Aggressive image compression may reduce receipt legibility below the threshold required for financial auditing, causing claim rejections or compliance failures despite technically successful uploads.

Mitigation & Contingency

Mitigation: Define minimum legibility requirements with HLF finance team before implementation. Set compression targets conservatively (e.g., max 1MB, min 80% JPEG quality) and validate with sample receipt images. Provide compression statistics in verbose/debug mode.

Contingency: If post-compression quality is disputed by auditors, increase the quality floor at the cost of larger file sizes, and add a manual override allowing users to skip compression for PDFs and high-quality scans.

medium impact medium prob dependency

The Flutter image_picker package behaves differently on iOS 17+ (PHPicker) vs older Android (Intent-based), particularly for file types, permission flows, and PDF selection, which may cause platform-specific failures not caught in development.

Mitigation & Contingency

Mitigation: Test image picker integration on physical devices for both platforms early in the sprint. Pin the image_picker package version and review changelogs before updates. Write widget tests using mock file results for each platform branch.

Contingency: If PHPicker or Android Intent differences cause blocking issues, implement separate platform-specific picker delegates behind the unified interface, allowing platform-specific fixes without breaking the shared API.