Skip to content

Sync apply / conflict-resolution unit tests (#43/#44 regression surface) - #46

Merged
mayeack merged 1 commit into
mainfrom
claude/sync-layer-tests
Jun 2, 2026
Merged

mayeack merged 1 commit into
mainfrom
claude/sync-layer-tests

Conversation

@mayeack

@mayeack mayeack commented Jun 2, 2026

Copy link
Copy Markdown
Owner

Summary

Acts on finding #6 of docs/CODE_REVIEW_2026-06-02.md: the sync-apply layer that caused this week's drift (#44) and launch-crash (#43) incidents had zero unit coverage.

ContactSyncApply.applyRemoteContact and ModelSyncApply.applyRemoteInteraction are pure functions (payload [String: Any] + ModelContext), so this needs no protocol seam or network mock — "remote" payloads are built with the real contactToDict/interactionToDict serializers so ISO8601 formatting matches the parser exactly.

SyncApplyTests (11 tests)

  • Insert path: absent record inserted + marked synced; round-trip preserves name/emails/phones/score/priority; idempotent re-apply is a UUID upsert (no duplicate).
  • Conflict resolution: newer remote overwrites; newer + pending local is protected from a stale remote; newer-but-synced local is clobbered by an older remote — the regression guard that documents exactly why edits must flip syncStatus to .pending (markLocallyEdited), i.e. the Fix sync drift: mark every Contact edit as locally edited + filter merged-away from iMessage lookup #44 root cause.
  • Malformed payloads: missing id / missing updatedAt ignored without throwing.
  • Interactions: link to resolved contact; unknown contactId persists with nil contact (heals on a later pull) instead of crashing; newer pending local survives a stale remote.

Not in scope (noted for later)

Full network-path integration (URLSession injection into LocalServerSyncService) — the further half of finding #6.

Test plan

  • iOS Simulator: 217 XCTest + 13 Swift Testing pass (+11)
  • xcodegen generate registered the new file
  • CI green

🤖 Generated with Claude Code

#43/#44)

The sync-apply layer — ContactSyncApply.applyRemoteContact and
ModelSyncApply.applyRemoteInteraction — is the exact code behind the recent drift
(#44) and launch-crash (#43) incidents and had zero unit coverage. These functions
are pure (payload dict + ModelContext), so no protocol seam or network mock is
needed; "remote" payloads are built with the real contactToDict/interactionToDict
serializers so ISO8601 formatting matches the parser.

SyncApplyTests (11):
- insert path: absent record inserted + marked synced; round-trip preserves
  name/emails/phones/score/priority; idempotent re-apply is a UUID upsert (no dup).
- conflict resolution: newer remote overwrites; NEWER+PENDING local is protected
  from a stale remote; NEWER-but-SYNCED local IS clobbered by an older remote
  (regression guard documenting exactly why edits must flip syncStatus to .pending
  via markLocallyEdited — the #44 root cause).
- malformed payloads: missing id / missing updatedAt are ignored without throwing.
- interactions: link to resolved contact; unknown contactId persists with nil
  contact (heals later) instead of crashing; newer pending local survives stale remote.

Network-path integration (URLSession injection into LocalServerSyncService) is the
remaining step noted in docs/CODE_REVIEW_2026-06-02.md finding #6.

Test count 206 -> 217 XCTest (+ 13 Swift Testing), all green iOS + macOS.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@mayeack
mayeack merged commit 82d2ab1 into main Jun 2, 2026
2 checks passed
@mayeack
mayeack deleted the claude/sync-layer-tests branch June 2, 2026 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant