high priority high complexity testing pending testing specialist Tier 6

Acceptance Criteria

Integration test suite connects to a dedicated Supabase test project or supabase CLI local stack — never the production database
Test: org A user cannot read activity types belonging to org B (RLS enforcement) — query returns empty list or throws RLS error
Test: on first provider access, repository.fetchActiveByOrganisation is called exactly once against the live test database
Test: on second provider access within the same ProviderContainer session, no additional Supabase network call is made (cache serves from memory)
Test: after repository.create() inserts a new activity type, ref.invalidate() triggers a re-fetch and the new type appears in the provider state
Test: soft-deleted activity type (is_active = false) does not appear in the provider's cached list after cache refresh
Test: resolver accessors on cached items return correctly resolved values matching the test database records
Test suite cleans up all test data created during the run (tearDown/tearDownAll)
Integration tests are tagged with @Tags(['integration']) and excluded from the standard unit test run — run separately with flutter test --tags integration
CI pipeline has a dedicated job that provisions a local Supabase stack and runs the integration test suite

Technical Requirements

frameworks
flutter_test
Riverpod (ProviderContainer for headless testing)
supabase_flutter SDK
Supabase CLI (local stack) or dedicated test Supabase project
apis
Supabase PostgREST (live, against test database)
Supabase Auth (test user JWTs with org-scoped claims)
ActivityTypeRepository (real implementation, no mocks)
ActivityTypeCacheProvider (real implementation)
ActivityTypeMetadataResolver (real implementation)
data models
activity_type (full schema including RLS policies)
organisation (required for multi-org RLS tests)
performance requirements
Full integration test suite must complete in under 60 seconds with local Supabase stack
Each individual test must complete within 10 seconds including database round-trips
security requirements
Test database credentials stored in CI secrets — never committed to source control
Test users created with minimum required permissions — no service role key used in tests
All test data seeded and cleaned up within the test run — no persistent test data left in test database
Cross-org RLS test must use two distinct authenticated test users with separate org JWT claims
Test project URL and anon key stored in environment variables (SUPABASE_TEST_URL, SUPABASE_TEST_ANON_KEY)

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

The most complex aspect is authenticating as two different test users within the same test suite to verify RLS. Use Supabase's Admin Auth API to create test users server-side in setUpAll(), obtain JWTs for each, then construct separate SupabaseClient instances with each JWT injected as the auth token — do not use the singleton supabase.auth.signIn() as it mutates global state. For the cache verification test (second read hits no DB), instrument the repository with a call counter (thin wrapper or spy pattern) rather than relying on network inspection. For CI integration, add a GitHub Actions job that runs supabase start, waits for health check, runs flutter test --tags integration, then runs supabase stop.

Store test credentials in GitHub Secrets. Never run integration tests against production — add a guard assertion in setUpAll() that checks the Supabase URL contains 'test' or 'local'.

Testing Requirements

flutter_test integration tests using real Supabase connections. Structure: setUpAll() provisions two test organisations (org_a, org_b) and two test users with distinct org claims via Supabase Auth Admin API; setUp() seeds known activity types for each org; tearDown() deletes seeded records; tearDownAll() deletes test users and orgs. Test groups: group('RLS isolation', ...) — cross-org reads return no data; group('cache population', ...) — first access hits DB, subsequent reads do not; group('cache invalidation', ...) — write operations trigger re-fetch; group('resolver integration', ...) — cached items have correct resolved metadata. Use ProviderContainer with overrides to inject org-scoped auth for each test.

Run with: flutter test integration_test/activity_type_data_layer_test.dart --tags integration.

Component
Supabase Client
infrastructure 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.