high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

ZIP archive contains exactly the files listed in the manifest, with no extra or missing entries
ZIP internal directory structure matches the prescribed layout (e.g., attachments/{activity_id}/filename)
Filename collisions within the same directory are resolved by appending a numeric suffix (e.g., receipt_1.pdf, receipt_2.pdf) without overwriting
Missing attachments (Supabase Storage 404) are logged as warnings and listed in the manifest under 'missing_attachments' — bundler does not throw
Inaccessible attachments (Supabase Storage auth error) are handled gracefully, logged, and excluded from ZIP without crashing
Manifest JSON file is present at the ZIP root as 'manifest.json' with correct structure including file paths, activity IDs, and missing file list
Empty attachment list produces a valid ZIP containing only the manifest
Supabase Storage is fully mocked — no network calls made during test execution
Test suite achieves 90%+ line coverage of BufdirAttachmentBundler class

Technical Requirements

frameworks
flutter_test
mocktail or mockito
archive (Dart ZIP package)
apis
Supabase Storage API (mocked)
data models
BufdirAttachmentRecord
BufdirBundleManifest
BufdirExportAttachment
performance requirements
Unit tests complete in under 1 second each with mocked storage
Bundler must process 50 attachments without holding all bytes in memory simultaneously (streaming)
security requirements
Mocked storage responses must not use real signed URLs or storage bucket names
Manifest must not include Supabase storage tokens in test output

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Inject the Supabase Storage client via constructor injection into BufdirAttachmentBundler to enable clean mocking. The bundler's collision resolution logic is likely stateful (tracking seen filenames per directory) — test isolation requires fresh bundler instances per test case. For ZIP structure validation, use the Archive class from the archive package to decode the output bytes and iterate entries: assert entry.name values match expected paths. Manifest validation: decode manifest.json entry content as JSON and assert keys (total_files, missing_attachments, generated_at, entries[].

{activity_id, path, original_filename}). The 'streaming' note in performance_requirements means the bundler should pipe Supabase Storage download streams into the ZIP encoder rather than buffering all bytes — test this by mocking storage to return a Stream> and verifying the bundler does not accumulate in memory.

Testing Requirements

Unit tests with full Supabase Storage mocking using mocktail. Define a SupabaseStorageClientMock that returns configurable responses (success with bytes, 404, auth error) per file path. Test groups: (1) happy path — N attachments all present, verify ZIP entries and manifest; (2) partial missing — some 404s, verify manifest 'missing_attachments' field and ZIP integrity; (3) auth failure — storage returns 403, verify graceful exclusion; (4) filename collision — two attachments with same original filename under same activity, verify suffix resolution; (5) empty attachments — verify minimal valid ZIP with manifest only. Parse the output ZIP using Dart's archive package within tests to introspect entries.

Assert manifest JSON using json.decode and map key checks.

Component
Document Attachment Bundler
service medium
Epic Risks (3)
high impact medium prob technical

NHF contacts can belong to up to five local chapters simultaneously. If the deduplication logic in the activity query service incorrectly attributes cross-chapter activities, organisations will either under-report or over-report to Bufdir, which could trigger grant clawback or compliance investigations.

Mitigation & Contingency

Mitigation: Implement deduplication using the existing multi-chapter membership service as the source of truth for chapter affiliation. Write test fixtures covering all known multi-chapter edge cases and validate outputs against manually prepared reference exports from NHF.

Contingency: If deduplication cannot be made deterministic for complex hierarchies before release, gate the export behind an org-level feature flag and require NHF to validate a preview export against their manual Excel before enabling in production.

medium impact medium prob dependency

Server-side Dart libraries for Excel generation are less mature than equivalents in Node.js or Python. The chosen library may lack support for Bufdir-required formatting features (merged cells, data validation, specific date formats), requiring significant workaround effort or a library switch mid-implementation.

Mitigation & Contingency

Mitigation: Evaluate the top two Dart xlsx libraries (excel, spreadsheet_decoder) against a Bufdir template sample file before committing. Identify all required formatting features and verify library support in a spike.

Contingency: If no Dart library meets requirements, implement the Excel generation as a Supabase Edge Function in TypeScript using the well-supported ExcelJS library, exposing it to the Dart backend via an internal RPC call.

medium impact medium prob integration

The attachment bundler must retrieve documents from Supabase Storage that were uploaded by the document attachments feature. If storage paths, RLS policies, or signed URL expiry have not been standardised across features, the bundler may fail to retrieve attachments at export time.

Mitigation & Contingency

Mitigation: Audit the document attachments feature's storage schema and RLS policies before implementing the bundler. Agree on a stable internal service-account access pattern for cross-feature storage reads.

Contingency: If cross-feature storage access cannot be made reliable, implement the bundler to include only attachments that can be retrieved successfully and produce a manifest listing any attachments that could not be bundled, rather than failing the entire export.