From 9a0bda9cac23f662bffcdc57e3b40b8fe4fafdfe Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 23 Jan 2026 09:46:29 -0600 Subject: [PATCH 1/4] Add a test for what happens with outside records. --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 10 ++-- .../CloudKitTests/SchemaChangeTests.swift | 55 ++++++++++++++++--- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 878e832f..8486f549 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -38,7 +38,7 @@ private let notificationsObserver = LockIsolated<(any NSObjectProtocol)?>(nil) private let activityCounts = LockIsolated(ActivityCounts()) private let startTask = LockIsolated?>(nil) - #if canImport(DeveloperToolsSupport) + #if DEBUG && canImport(DeveloperToolsSupport) private let previewTimerTask = LockIsolated?>(nil) #endif @@ -423,7 +423,7 @@ /// You must start the sync engine again using ``start()`` to synchronize the changes. public func stop() { guard isRunning else { return } - #if canImport(DeveloperToolsSupport) + #if DEBUG && canImport(DeveloperToolsSupport) previewTimerTask.withValue { $0?.cancel() $0 = nil @@ -503,7 +503,7 @@ } ) - #if canImport(DeveloperToolsSupport) + #if DEBUG && canImport(DeveloperToolsSupport) @Dependency(\.context) var context @Dependency(\.continuousClock) var clock if context == .preview { @@ -1893,7 +1893,9 @@ ) { withErrorReporting(.sqliteDataCloudKitFailure) { guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey - else { return } + else { + return + } try SyncMetadata.insert { SyncMetadata( diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 735d3cb4..3cc41fa2 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -632,6 +632,7 @@ saving: [imageRecord] ) .notify() + syncEngine.stop() inMemoryDataManager.storage.withValue { $0.removeAll() } @@ -656,16 +657,56 @@ ) defer { _ = relaunchedSyncEngine } - let images = try await userDatabase.read { db in - try Image.order(by: \.id).fetchAll(db) + try await userDatabase.read { db in + expectNoDifference( + try Image.order(by: \.id).fetchAll(db), + [ + Image(id: 1, image: Data("image".utf8), caption: "A good image") + ] + ) } - expectNoDifference( - images, - [ - Image(id: 1, image: Data("image".utf8), caption: "A good image") - ] + assertInlineSnapshot(of: relaunchedSyncEngine.container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:images/zone/__defaultOwner__), + recordType: "images", + parent: nil, + share: nil, + caption: "A good image", + id: "1", + image: Data(5 bytes) + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [] + ) + ) + """ + } + } + } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func outsideRecord() async throws { + let customRecord = CKRecord( + recordType: "customRecord", + recordID: CKRecord.ID( + recordName: "customRecord", + zoneID: SyncEngine.defaultTestZone.zoneID ) + ) + try await syncEngine.modifyRecords(scope: .private, saving: [customRecord]).notify() + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { + """ + (No results) + """ } } } From f2095422529c7ae377a8e201e2c3f529c71f7f22 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 23 Jan 2026 09:51:14 -0600 Subject: [PATCH 2/4] wip --- .../CloudKitTests/SchemaChangeTests.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 3cc41fa2..9e43ae71 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -709,6 +709,25 @@ """ } } + + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func outsideRecordWithColon() async throws { + let customRecord = CKRecord( + recordType: "customRecord", + recordID: CKRecord.ID( + recordName: "1:customRecord", + zoneID: SyncEngine.defaultTestZone.zoneID + ) + ) + try await syncEngine.modifyRecords(scope: .private, saving: [customRecord]).notify() + withKnownIssue("We should have a way to not sync this record's metadata") { + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { + """ + (No results) + """ + } + } + } } } From 4bd0cbe464adb7fcbc4cf1b1a523bc4d0e3590a6 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 23 Jan 2026 21:36:26 -0600 Subject: [PATCH 3/4] fix tests --- Tests/SQLiteDataTests/CloudKitTests/PreviewTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/SQLiteDataTests/CloudKitTests/PreviewTests.swift b/Tests/SQLiteDataTests/CloudKitTests/PreviewTests.swift index 10dffa67..384ed47d 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/PreviewTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/PreviewTests.swift @@ -1,4 +1,4 @@ -#if canImport(CloudKit) +#if DEBUG && canImport(DeveloperToolsSupport) && canImport(CloudKit) import DependenciesTestSupport import InlineSnapshotTesting import SnapshotTestingCustomDump From a19c18330329ed69d10eb347ef8d281843d8958d Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 27 Jan 2026 16:06:20 -0600 Subject: [PATCH 4/4] Skip records that don't have a userModificationTime. --- Sources/SQLiteData/CloudKit/SyncEngine.swift | 4 +++- .../SQLiteDataTests/CloudKitTests/CloudKitTests.swift | 2 +- .../CloudKitTests/FetchRecordZoneChangesTests.swift | 6 +++--- .../CloudKitTests/ForeignKeyConstraintTests.swift | 6 +++++- .../CloudKitTests/SchemaChangeTests.swift | 10 ++++------ 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index 8486f549..03dcf19a 100644 --- a/Sources/SQLiteData/CloudKit/SyncEngine.swift +++ b/Sources/SQLiteData/CloudKit/SyncEngine.swift @@ -1892,7 +1892,9 @@ db: Database ) { withErrorReporting(.sqliteDataCloudKitFailure) { - guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey + guard + let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey, + serverRecord.encryptedValues[CKRecord.userModificationTimeKey] != nil else { return } diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index 5c0a9347..9b49a3c6 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift @@ -860,7 +860,7 @@ } let record = try syncEngine.private.database.record(for: ModelA.recordID(for: 1)) - record.encryptedValues["isEven"] = false + record.setValue(false, forKey: "isEven", at: 0) try await syncEngine.modifyRecords(scope: .private, saving: [record]).notify() assertInlineSnapshot(of: container, as: .customDump) { diff --git a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift index 207728c7..3a5464f8 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift @@ -405,7 +405,7 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func receiveRecord_SingleFieldPrimaryKey() async throws { let tagRecord = CKRecord(recordType: "tags", recordID: Tag.recordID(for: "weekend")) - tagRecord.encryptedValues["title"] = "weekend" + tagRecord.setValue("weekend", forKey: "title", at: 0) try await syncEngine.modifyRecords(scope: .private, saving: [tagRecord]).notify() try await userDatabase.read { db in @@ -508,7 +508,7 @@ recordType: Tag.tableName, recordID: Tag.recordID(for: "tag") ) - tagRecord.encryptedValues["title"] = "tag" + tagRecord.setValue("tag", forKey: "title", at: 0) try await syncEngine.modifyRecords(scope: .private, saving: [tagRecord]).notify() assertQuery(Tag.all, database: userDatabase.database) { @@ -583,7 +583,7 @@ recordType: Tag.tableName, recordID: Tag.recordID(for: "tag") ) - tagRecord.encryptedValues["title"] = "tag" + tagRecord.setValue("tag", forKey: "title", at: 0) let modifications = try syncEngine.modifyRecords(scope: .private, saving: [tagRecord]) try await userDatabase.userWrite { db in diff --git a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift index cc2883d6..23974bb8 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/ForeignKeyConstraintTests.swift @@ -119,10 +119,13 @@ @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) @Test func remoteCreatesRecordABC_localReceivesAC_remoteDeletesBC() async throws { let modelARecord = CKRecord(recordType: ModelA.tableName, recordID: ModelA.recordID(for: 1)) + modelARecord.setValue(1, forKey: "id", at: 0) let modelBRecord = CKRecord(recordType: ModelB.tableName, recordID: ModelB.recordID(for: 1)) + modelBRecord.setValue(1, forKey: "id", at: 0) modelBRecord.setValue(1, forKey: "modelAID", at: now) modelBRecord.parent = CKRecord.Reference(record: modelARecord, action: .none) let modelCRecord = CKRecord(recordType: ModelC.tableName, recordID: ModelC.recordID(for: 1)) + modelCRecord.setValue(1, forKey: "id", at: 0) modelCRecord.setValue(1, forKey: "modelBID", at: now) modelCRecord.parent = CKRecord.Reference(record: modelBRecord, action: .none) @@ -205,7 +208,8 @@ recordID: CKRecord.ID(1:modelAs/zone/__defaultOwner__), recordType: "modelAs", parent: nil, - share: nil + share: nil, + id: 1 ) ] ), diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 9e43ae71..da4659d4 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -720,12 +720,10 @@ ) ) try await syncEngine.modifyRecords(scope: .private, saving: [customRecord]).notify() - withKnownIssue("We should have a way to not sync this record's metadata") { - assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { - """ - (No results) - """ - } + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { + """ + (No results) + """ } } }