Integration test: ContactDetailService parallel fetch and state streams
epic-contact-detail-and-edit-service-layer-task-013 — Write integration tests for ContactDetailService covering: successful parallel fetch emitting ContactDetailLoaded with correct profile, history, and assignment data; network failure emitting typed error state; retry logic recovering on second attempt; and encrypted field key list correctness for two different organisation configurations.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 3 - 413 tasks
Can start after Tier 2 completes
Implementation Notes
Use the `fake_async` package to control time when testing retry delays — avoid real `await Future.delayed` in tests as it causes slowness and flakiness. Structure fakes as in-memory implementations of the data source interfaces rather than using a mocking library like Mockito, which requires code generation and adds build overhead. For verifying parallel execution, inject a Completer-based fake that records invocation timestamps; assert the delta between first and last call start is less than the sum of individual durations. For encrypted field key tests, define org configurations in fixture maps at the top of the test file — this makes the expected key lists easy to update when configs change.
Ensure the service under test is constructed fresh in a `setUp()` callback to guarantee isolation between tests. If ContactDetailService exposes a stream (typical in BLoC-style services), use `expectLater(stream, emitsInOrder([...]))` for ordered assertions.
Testing Requirements
Integration tests only (no unit tests for individual methods in this task). Use flutter_test with fake/stub Supabase data sources rather than live connections. Structure tests in three groups: (1) happy-path parallel fetch, (2) error and retry scenarios, (3) encrypted field key configuration per org. Use StreamController or StreamQueue from async package to assert emitted states in order.
Verify call counts on mocks to confirm parallel invocation. Aim for ≥90% line coverage on ContactDetailService. All tests must be deterministic — use FakeAsync if timers or delays are involved in retry logic.
Parallel fetching of profile, activity history, and assignment status from contact-detail-service may produce race conditions where partial state is emitted to the UI before all fetches complete, resulting in flickering or incorrect loading indicators.
Mitigation & Contingency
Mitigation: Use Future.wait or a single composed BLoC event that only emits a loaded state once all three futures resolve. Define a strict state machine: initial → loading → loaded/error with no intermediate partial-loaded states emitted to the UI.
Contingency: If parallelism proves unreliable in testing, fall back to sequential fetching with a combined loading indicator. The 500ms target may need to be renegotiated with stakeholders if sequential fetching exceeds it on slow connections.
The partial-field update pattern in contact-edit-service assumes the contact record has not changed between when the edit screen was loaded and when the save is submitted. Concurrent edits by another coordinator could cause the earlier editor's save to silently overwrite the later one.
Mitigation & Contingency
Mitigation: Include an updated_at timestamp in the PATCH request and configure Supabase to reject updates where the server-side timestamp differs from the client's version. Return a 409-equivalent error that the service maps to a user-readable conflict message.
Contingency: If optimistic locking is too complex for initial delivery, implement a simple 'reload and retry' flow: on save error, reload the contact detail and prompt the coordinator to re-apply their changes manually.