high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

All tests run with flutter test — no device or emulator required
FlutterSecureStorage is mocked using mockito or a hand-written fake that captures write/read/delete calls
writeSession: verifies that each StoredSession field (accessToken, refreshToken, userId, expiresAt, etc.) is written under its exact key constant
writeSession: verifies iOptions (with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) is passed on iOS
writeSession: verifies aOptions (with encryptedSharedPreferences: true) is passed on Android
readSession: when all keys are populated, returns a StoredSession with correctly deserialised field values including DateTime parsing
readSession: when any required key is absent, returns null (or Result.failure) without throwing
clearSession: calls delete for every key written by writeSession — no orphaned keys
writeBiometricPreference(true): stores the string 'true' under the biometric preference key
writeBiometricPreference(false): stores the string 'false'
readBiometricPreference: returns null when the key has never been written
readBiometricPreference: returns true when stored value is 'true', false when 'false'
All tests are isolated — no shared state between test cases

Technical Requirements

frameworks
Flutter
flutter_test
mockito (or manual fake)
apis
FlutterSecureStorage.write()
FlutterSecureStorage.read()
FlutterSecureStorage.delete()
FlutterSecureStorage.deleteAll()
data models
StoredSession
ISecureSessionStorage
SecureSessionStorageImpl
performance requirements
Each test case must complete in under 100ms
Test suite must complete in under 5 seconds total
security requirements
Tests must verify that Keychain accessibility option is set to afterFirstUnlockThisDeviceOnly, not afterFirstUnlock (which syncs to iCloud)
Tests must verify encryptedSharedPreferences is true on Android — not optional

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Create a manual fake FlutterSecureStorage that stores writes in an in-memory Map if mockito code generation is not yet set up in the project — this is simpler and avoids the build_runner dependency. The fake should implement all used methods: read, write, delete, deleteAll. For platform-specific option testing (iOptions/aOptions), use mockito argument captors: final captor = verify(mockStorage.write(key: anyNamed('key'), value: anyNamed('value'), iOptions: captureAnyNamed('iOptions'))).captured; then assert on the captured option object. If running on the test host (not an iOS device), platform checks may need to be abstracted — consider injecting a Platform abstraction or using defaultTargetPlatform overriding in tests.

Testing Requirements

Pure unit tests using flutter_test. Use mockito @GenerateMocks([FlutterSecureStorage]) or a manual fake. Group tests by method under describe-style group() blocks: writeSession group (5 cases), readSession group (3 cases), clearSession group (2 cases), biometric preference group (4 cases). Use verify() and verifyNever() from mockito to assert exact method calls and argument values.

Use setUp() to reset the mock before each test. Total: minimum 14 test cases. No integration tests in this task.

Component
Secure Session Storage
data low
Epic Risks (3)
high impact medium prob technical

iOS Keychain access requires correct entitlement configuration and provisioning profile setup. Misconfigured entitlements cause silent failures in CI/CD and on physical devices, where the plugin appears to work in the simulator but fails at runtime. This can delay foundation delivery and block all downstream epics.

Mitigation & Contingency

Mitigation: Add a dedicated integration test running on a physical iOS device early in the epic. Document required entitlements and provisioning steps in a developer runbook. Validate Keychain access in the CI pipeline using an iOS simulator with correct entitlements enabled.

Contingency: If Keychain entitlements cannot be resolved quickly, temporarily use in-memory storage behind the SecureSessionStorage interface to unblock downstream epics, then resolve the Keychain issue in a hotfix before release.

medium impact medium prob dependency

The Flutter local_auth plugin has a history of breaking API changes between major versions, and its Android implementation depends on BiometricPrompt which behaves differently across Android API levels (23-34). An incompatible plugin version or unexpected Android API behaviour can cause authentication failures on a significant portion of the target device fleet.

Mitigation & Contingency

Mitigation: Pin local_auth to a specific stable version in pubspec.yaml. Test against Android API levels 23, 28, and 33 in the CI matrix. Review the plugin changelog and migration guide before adopting any version bump.

Contingency: If the pinned version proves incompatible with target devices, evaluate flutter_local_auth_android as a replacement or fork the plugin adapter to isolate the breaking surface.

high impact low prob security

If users upgrade from a version of the app that stored session data in non-encrypted storage (SharedPreferences), a migration path is required. Failing to migrate silently leaves old tokens in plain storage, creating a security gap and potentially causing confusing authentication state on first launch of the new version.

Mitigation & Contingency

Mitigation: Audit existing storage usage across the codebase before writing SecureSessionStorage. If legacy plain storage keys exist, implement a one-time migration routine that reads from SharedPreferences, writes to Keychain/Keystore, and deletes the plain-text entry.

Contingency: If migration is discovered late, ship the migration as a mandatory patch release before the biometric feature is enabled for users, and add a startup check that blocks biometric opt-in until migration is confirmed complete.