high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

Test suite contains at minimum 12 test cases covering all CRUD operations and error paths
fetchActiveByOrganisation returns only records matching the provided org_id and where is_active == true
fetchActiveByOrganisation returns an empty list (not an exception) when the org has no active activity types
create() inserts a record with all required fields and returns the created ActivityType with a server-assigned UUID
update() sends only the changed fields to Supabase and returns the updated ActivityType
softDelete() sets is_active = false on the target record and does not physically delete the row
PostgrestException with code '42501' (RLS violation) maps to ActivityTypePermissionException
PostgrestException with code 'PGRST116' (row not found) maps to ActivityTypeNotFoundException
Network timeout exception maps to ActivityTypeNetworkException
All tests run in CI without any live Supabase connection — mock client used throughout
Test file is co-located with or in a parallel test directory to the repository implementation
Tests use descriptive names following the pattern: 'given_[state]_when_[action]_then_[outcome]'

Technical Requirements

frameworks
flutter_test
mocktail (for mocking SupabaseClient and PostgrestBuilder chain)
Dart
apis
Supabase PostgREST client mock (from(), select(), eq(), insert(), update(), delete())
ActivityTypeRepository public interface (fetchActiveByOrganisation, create, update, softDelete)
data models
activity_type (id, organization_id, name, description, is_travel_expense_eligible, is_active, created_at, updated_at)
performance requirements
Full test suite must complete in under 10 seconds in CI
No real I/O operations — all Supabase calls intercepted by mocks
security requirements
Test fixtures must not contain real UUIDs, personal data, or organisation credentials
Mock responses must simulate RLS enforcement to validate that repository handles permission errors correctly

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Mocking the Supabase Flutter client builder chain is the main technical challenge. Use mocktail's when().thenReturn() to stub each builder method. Create a reusable helper function buildMockSupabaseChain(response) that sets up the full chain mock to avoid repetition across tests. For error mapping tests, throw PostgrestException directly from the mock and verify the repository catches it and wraps it in the correct typed exception.

Test the softDelete implementation carefully: it should call .update({'is_active': false}).eq('id', id) — not .delete(). Use test fixtures (const maps) for sample activity type JSON to keep tests readable. Run tests with: flutter test test/repositories/activity_type_repository_test.dart --coverage.

Testing Requirements

flutter_test unit tests only — no widget tests, no integration tests in this task. Use mocktail to mock the SupabaseClient. Because the Supabase Flutter client uses a builder chain (from().select().eq()...), create a MockSupabaseQueryBuilder chain stub. Structure tests in a group per method: group('fetchActiveByOrganisation', ...), group('create', ...), group('update', ...), group('softDelete', ...), group('error mapping', ...).

Each group should have a happy-path test, an empty-result test, and at least two error-path tests. Aim for 100% line coverage on the repository class.

Component
Activity Type Repository
data medium
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.