high priority low complexity testing pending testing specialist Tier 2

Acceptance Criteria

All tests use sqflite_common_ffi with inMemoryDatabasePath — no real file I/O
Each test case runs with a freshly initialized database (setUp creates schema, tearDown closes DB)
Test covers save() → findBySessionId() round-trip with 100% field value fidelity
Test covers session isolation: two sessions with overlapping field names do not leak records
Test covers stale cleanup: records older than threshold are deleted, newer ones are preserved
Concurrent write test: 10 simultaneous save() calls with Future.wait complete without error and all 10 records are persisted
Crash-recovery simulation: close DB, reopen with same path, verify previously saved records are still present
All RepositoryException error paths are tested (e.g., inserting a record with a duplicate id when upsert is not used)
Test file follows flutter_test conventions and all tests pass with `flutter test`
Test coverage for the repository class is 100% line coverage

Technical Requirements

frameworks
Flutter
flutter_test
apis
sqflite_common_ffi
data models
PartialTranscription
performance requirements
Full test suite must complete in under 10 seconds
security requirements
Test data must not include real personal data — use generated fixture strings

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Create a `test/fixtures/partial_transcription_fixture.dart` helper that returns a valid `PartialTranscription` with all fields populated. Parameterize `sessionId`, `createdAt`, and `persistenceStatus` to make scenario setup concise. For the stale test, construct records with `createdAt` set to `DateTime.now().subtract(Duration(days: 8))` and verify `clearStale(Duration(days: 7))` removes them. Do not use `mockito` or `mocktail` for the repository itself — test the real implementation against an in-memory DB for maximum fidelity.

If drift is used instead of sqflite, use drift's built-in `NativeDatabase.memory()` test setup.

Testing Requirements

All tests are unit tests (no widget tree, no Flutter engine required beyond flutter_test). Use a `PartialTranscriptionFixture` factory class to generate test instances with sensible defaults, overridable via named parameters. Group tests by method name using `group()`. Use `setUp`/`tearDown` for DB lifecycle.

For the concurrent test, use `Future.wait(List.generate(10, (_) => repo.save(fixture())))` and then assert `findBySessionId` returns 10 records. For crash-recovery, simulate by closing and reopening the database with the same in-memory key (sqflite_common_ffi supports named in-memory DBs that persist across open/close within the same process).

Epic Risks (3)
high impact medium prob technical

iOS 15 on-device speech recognition has a 1-minute session limit and requires network fallback for longer sessions. Peer mentor way-forward dictation may routinely exceed this limit, causing silent truncation of transcribed content without user feedback.

Mitigation & Contingency

Mitigation: Implement session-chunking logic in NativeSpeechApiBridge that automatically restarts recognition before the limit is reached, preserving continuity via partial concatenation. Document the iOS 15 vs iOS 16 on-device recognition behaviour difference in code comments.

Contingency: If chunking causes user-visible interruptions, surface a non-blocking informational banner on iOS 15 devices informing users that very long dictation sessions may need to be broken into segments, and use PartialTranscriptionRepository to persist each chunk immediately.

high impact medium prob scope

On iOS, speech recognition permission can only be requested once. If the user denies the permission, the app cannot re-request it. A poor first-impression permission flow will permanently disable dictation for those users, impacting the Blindeforbundet blind-user base who rely on dictation most.

Mitigation & Contingency

Mitigation: Design the NativeSpeechApiBridge permission flow to show a clear pre-permission rationale screen before the OS dialog. Implement a graceful degradation path that hides the microphone button and shows a settings deep-link when permission is permanently denied.

Contingency: If users have already denied permission before the rationale screen is added, provide a settings deep-link in DictationScopeGuard's denial message directing users to iOS Settings > Privacy > Speech Recognition to re-enable manually.

medium impact low prob integration

The approved field IDs and screen routes configuration in DictationScopeGuard may fall out of sync with the actual report form schema as new fields are added by org administrators, silently blocking dictation on legitimately approved fields.

Mitigation & Contingency

Mitigation: Source the approved field configuration from the same org-field-config-loader used by the report form, rather than a hardcoded list. Add a developer-time assertion that logs a warning when a dictation-eligible field type is rendered but not in the approved routes map.

Contingency: Provide a runtime override mechanism in the scope guard that coordinators or admins can use to temporarily whitelist a field ID while the config is updated, with an automatic expiry.