diff --git a/Sources/SQLiteData/CloudKit/SyncEngine.swift b/Sources/SQLiteData/CloudKit/SyncEngine.swift index ec6c5455..890fdd78 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 { @@ -1892,8 +1892,12 @@ db: Database ) { withErrorReporting(.sqliteDataCloudKitFailure) { - guard let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey - else { return } + guard + let recordPrimaryKey = serverRecord.recordID.recordPrimaryKey, + serverRecord.encryptedValues[CKRecord.userModificationTimeKey] != nil + else { + return + } try SyncMetadata.insert { SyncMetadata( diff --git a/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift b/Tests/SQLiteDataTests/CloudKitTests/CloudKitTests.swift index eb9f2aa0..d7332fc9 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 d0a82cb3..60242f49 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/FetchRecordZoneChangesTests.swift @@ -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/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 diff --git a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift index 54357da9..e2b96c39 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/SchemaChangeTests.swift @@ -846,6 +846,7 @@ saving: [imageRecord] ) .notify() + syncEngine.stop() inMemoryDataManager.storage.withValue { $0.removeAll() } @@ -870,16 +871,73 @@ ) 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) + """ + } + } + + @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() + assertQuery(SyncMetadata.all, database: syncEngine.metadatabase) { + """ + (No results) + """ } } }