high priority low complexity testing pending testing specialist Tier 4

Acceptance Criteria

Every public accessor method on ActivityTypeMetadataResolver has at least one corresponding test
Each accessor is tested with: (1) field present and true, (2) field present and false, (3) field absent from JSONB (default value expected), (4) field explicitly null, (5) field with wrong type (e.g. string where bool expected)
No accessor throws an uncaught exception for any of the above input variants — all edge cases return a safe default
Default values returned when fields are absent match the documented specification (e.g. isTravelExpenseEligible defaults to false, not null)
Boolean accessors return Dart bool, not dynamic — verified by expect(..., isA<bool>())
String accessors return Dart String or null as documented — never throw
Tests pass with null metadata (resolver constructed with ActivityType where metadata JSONB is null)
Tests pass with an empty metadata map ({})
Tests pass with a completely malformed metadata string (e.g. non-JSON string if the column allows it)
Test file achieves 100% statement coverage on the ActivityTypeMetadataResolver class

Technical Requirements

frameworks
flutter_test
Dart
apis
ActivityTypeMetadataResolver public accessor interface
data models
activity_type metadata JSONB structure (all documented keys and their expected types/defaults)
performance requirements
Test suite completes in under 5 seconds — pure Dart, no I/O
security requirements
Test fixtures must not contain sensitive metadata values or real org configurations

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

This task is intentionally low complexity but high value — exhaustive tests of the resolver prevent subtle bugs in downstream features (expense eligibility, Bufdir export filtering). Focus on completeness over creativity. If the resolver uses a pattern like (metadata?['key'] as bool?) ?? defaultValue, you need tests for: metadata is null, metadata['key'] is absent, metadata['key'] is bool true, metadata['key'] is bool false, metadata['key'] is wrong type (String).

Create a top-level const sampleMetadata fixture with all fields set to non-default values so you can verify the accessor reads from metadata, not from defaults, in the happy path. Run with: flutter test test/resolvers/activity_type_metadata_resolver_test.dart --coverage.

Testing Requirements

flutter_test unit tests — pure Dart, no mocks required. Organise with one group per accessor method. Use a factory helper createResolver({Map? metadata}) to reduce boilerplate.

Test matrix per accessor: absent key → default, explicit true/false → correct bool, null value → default (not exception), wrong type → default (not exception). Parameterised tests using test.each or a simple loop are acceptable to cover the matrix efficiently. Aim for 100% branch coverage: every if/else and null-check in the resolver must have a test hitting both branches.

Component
Activity Type Metadata Resolver
service low
Epic Risks (3)
high impact medium prob technical

The JSONB metadata column has no enforced schema at the database level. If the Dart model and the stored JSON diverge (e.g., a field is renamed or a new required flag is added without a migration), the metadata resolver will silently return null or throw at parse time, breaking conditional wizard logic for all organisations.

Mitigation & Contingency

Mitigation: Define a versioned Dart Freezed model for ActivityTypeMetadata and add a Supabase check constraint or trigger that validates the JSONB structure on write. Document the canonical metadata schema in a shared constants file and require schema review for any metadata field additions.

Contingency: Implement a lenient parse path in ActivityTypeMetadataResolver that returns safe defaults for missing fields and logs a structured warning to Supabase edge logs, allowing the app to degrade gracefully rather than crash.

high impact low prob security

If RLS policies on the activity_types table are misconfigured, a coordinator from one organisation could read or mutate activity types belonging to another organisation, violating data isolation guarantees required by all three client organisations.

Mitigation & Contingency

Mitigation: Write integration tests against the Supabase local emulator that explicitly assert cross-org isolation: a token scoped to org A must receive zero rows when querying org B activity types, and upsert attempts must return permission-denied errors.

Contingency: Apply an emergency RLS policy patch via Supabase dashboard without a code deploy. Audit all activity_type rows for cross-org contamination and restore from backup if any data leakage is confirmed.

medium impact medium prob integration

If the cache invalidation call in ActivityTypeService is not reliably triggered after an admin creates, edits, or archives an activity type, peer mentors on the same device will see stale data in the registration wizard until the next app restart, leading to confusion and potential misregistrations.

Mitigation & Contingency

Mitigation: Enforce a strict pattern: ActivityTypeService always calls cacheProvider.invalidate() inside the same try block as the successful Supabase mutation, before returning to the caller. Write a widget test that verifies the cache notifier emits an updated list after a service mutation.

Contingency: Add a background Supabase Realtime subscription on the activity_types table that triggers cache invalidation automatically, providing an independent safety net independent of the service call path.