critical priority low complexity testing pending testing specialist Tier 2

Acceptance Criteria

Write-then-read round-trip: writing value V for key K, then reading K, returns V
Overwrite semantics: writing value V2 for key K after writing V1, reading K returns V2 (not V1)
Missing-key read: reading a key that was never written returns null without throwing
Delete idempotency: deleting a key twice does not throw on the second call
Delete effect: after deleting key K, reading K returns null
exists() returns true for a key that has been written and false for one that has not
exists() returns false after a key has been deleted
Platform exception from flutter_secure_storage is caught and re-thrown as SecureStorageEncryptionException
PlatformException with a known 'unavailable' code re-throws as SecureStoragePlatformUnavailableException
flutter_test coverage report shows 100% line and branch coverage on FlutterSecureStorageAdapter
Tests run without any platform channel — flutter_secure_storage is mocked via mocktail or a fake

Technical Requirements

frameworks
Flutter
flutter_test
mocktail
data models
SecureStorageKey
SecureStorageException
performance requirements
Full test suite completes in under 5 seconds (no I/O, all in-memory)
security requirements
Tests must not write to any real platform storage — all storage backed by mock/fake only

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Since FlutterSecureStorage is a class (not an interface), you must either use mocktail's ability to mock concrete classes (requires registering a fallback value) or create a thin wrapper interface around FlutterSecureStorage that can be substituted. The wrapper approach is cleaner: define a private _FlutterSecureStorageWrapper interface with the 3-4 methods used, inject it into FlutterSecureStorageAdapter, and mock the wrapper in tests. To test PlatformException mapping, throw a PlatformException inside the mock and assert the catch block converts it correctly. For the 100% coverage requirement, ensure there are no dead branches — if the implementation has a guard clause like `if (value == null) return null`, there must be a test that hits the null branch.

Testing Requirements

All tests are pure unit tests using flutter_test with no platform channel dependencies. Use mocktail to mock FlutterSecureStorage (the underlying class) and stub its read/write/delete methods. Alternatively, inject a FakeFlutterSecureStorage. Cover every branch: the happy path for each method, the null-return path for missing keys, the idempotent-delete path, and all three exception subtypes.

Run `flutter test --coverage` and confirm the LCOV report shows 100% for the adapter file. Tests are organized in a single file: `test/data/adapters/flutter_secure_storage_adapter_test.dart`.

Component
Secure Storage Adapter
infrastructure low
Epic Risks (3)
high impact medium prob technical

iOS Keychain and Android Keystore have meaningfully different failure modes and permission models. The secure storage plugin may throw platform-specific exceptions (e.g., biometric enrollment required, Keystore wipe after device re-enrolment) that crash higher-level flows if not caught at the adapter boundary.

Mitigation & Contingency

Mitigation: Wrap all storage plugin calls in try/catch at the adapter layer and expose a typed StorageResult<T> instead of throwing. Write integration tests on real device simulators for both platforms in CI using Fastlane. Document the exception matrix during spike.

Contingency: If a platform-specific failure cannot be handled gracefully, fall back to in-memory-only storage for the current session and surface a non-blocking warning to the user; log the event for investigation.

high impact medium prob integration

Setting a session-level Postgres variable (app.current_org_id) via a Supabase RPC requires that RLS policies on every table reference this variable. If the Supabase project schema has not yet defined these policies, the configurator will set the variable but queries will return unfiltered data, giving a false sense of security.

Mitigation & Contingency

Mitigation: Include a smoke-test RPC in the SupabaseRLSTenantConfigurator that verifies the variable is readable from a policy-scoped query before marking setup as complete. Coordinate with the database migration task to ensure RLS policies reference app.current_org_id before the configurator is shipped.

Contingency: If RLS policies are not in place at integration time, gate all data-fetching components behind a runtime check in SupabaseRLSTenantConfigurator.isRlsScopeVerified(); block data access and surface a developer warning until policies are confirmed.

medium impact medium prob technical

Fetching feature flags from Supabase on every cold start adds network latency before the first branded screen renders. On slow connections this may cause a perceptible blank-screen gap or cause the app to render with default (unflagged) state before flags arrive.

Mitigation & Contingency

Mitigation: Persist the last-known flag set to disk in the FeatureFlagProvider and serve stale-while-revalidate on startup. Gate flag refresh behind a configurable TTL (default 15 minutes) so network calls are not made on every launch.

Contingency: If stale flags cause a feature to appear that should be hidden, add a post-load re-evaluation pass that reconciles the live flag set with the rendered widget tree and triggers a targeted rebuild where needed.