high priority medium complexity testing pending testing specialist Tier 3

Acceptance Criteria

Diff test: when only two of ten contact fields are changed, the Supabase update call receives a payload containing exactly those two fields — no unchanged fields included
Diff test: when no fields are changed (identical before/after), no Supabase update call is made and the service emits a ContactEditNoChanges state
Error surfacing test: a Supabase PostgrestException is caught and re-emitted as a ContactEditError with a non-empty, user-readable localised message string
Refresh test: after a successful save, ContactDetailService.refresh() is called exactly once and emits updated ContactDetailLoaded state reflecting the new values
ReadReceiptService happy-path: after a field is viewed, a receipt row is written to Supabase containing the correct encrypted field key and the authenticated user's ID
ReadReceiptService confirmation: after successful write, the service emits ReadReceiptConfirmed state with the matching field key
ReadReceiptService retry: on first network failure the write is retried; on second success the service emits ReadReceiptConfirmed; total Supabase write call count equals 2
ReadReceiptService permanent failure: after exhausting all configured retries, the service emits ReadReceiptFailed state — it must not silently swallow the error
All tests use fakes/stubs for Supabase; no live connections made
Tests are fully deterministic and pass without flakiness across 3 consecutive runs

Technical Requirements

frameworks
Flutter
flutter_test
BLoC
Riverpod
apis
Supabase REST API (mocked)
Supabase Auth (mocked for user ID)
data models
ContactEditPayload
ContactEditState
ReadReceipt
ReadReceiptState
ContactProfile
performance requirements
Each test case completes within 2 seconds
Retry logic test completes within 5 seconds using FakeAsync — no real delays
security requirements
User ID in read receipt must come from mocked auth session — do not hard-code test user IDs that resemble real PII
Encrypted field keys in test fixtures must use clearly synthetic values (e.g., 'test_field_key_1') not real production key names
Verify audit write includes user ID to satisfy compliance traceability requirement

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

The diff computation logic is the most critical correctness concern — test it exhaustively with matrix-style cases: (a) one field changed, (b) multiple fields changed, (c) no fields changed, (d) field changed back to original value (should be treated as unchanged). For the Supabase fake, capture the last payload passed to the update method in a recorded variable so test assertions can inspect it directly. For ReadReceiptService retry tests, inject a configurable max-retry count so tests can use maxRetries=2 instead of production defaults — this keeps tests fast. The compliance audit requirement means ReadReceiptFailed state must never be silently swallowed: add an assertion that the error is also logged to a structured logger mock (if one exists in the app), not just emitted as a state.

After save, verify the refresh sequence is: save completes → receipt written → detail refreshed — if order matters for UX, assert it explicitly with sequential state inspection.

Testing Requirements

Integration tests covering ContactEditService and ReadReceiptService as a pair. Group tests into three sections: (1) diff computation and partial update correctness, (2) error surfacing and refresh orchestration, (3) ReadReceiptService audit write with retry and failure states. Use FakeAsync for all retry timing. Use call-count assertions on Supabase fake to verify partial update payload.

Use StreamQueue to assert ordered state emissions. Compliance requirement: ReadReceiptService tests must explicitly assert that the user ID field is present in the written row — add a comment referencing the Bufdir compliance audit requirement so future maintainers understand the purpose.

Component
Contact Edit Service
service medium
Epic Risks (2)
medium impact medium prob technical

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.

high impact low prob integration

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.