diff --git a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift index 0c857a07..a863f612 100644 --- a/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift +++ b/Tests/SQLiteDataTests/CloudKitTests/RecordTypeTests.swift @@ -441,7 +441,8 @@ ) #expect( recordTypes[(remindersTableIndex + 1)...] - == recordTypesAfterMigration[(remindersTableIndex + 1)...]) + == recordTypesAfterMigration[(remindersTableIndex + 1)...] + ) assertInlineSnapshot(of: recordTypesAfterMigration[remindersTableIndex], as: .customDump) { #""" @@ -514,6 +515,102 @@ """# } } + + // * Stop sync engine + // * Migrate to add new table + // * Create new sync engine but forget to add table + // * Create new sync engine but this time add the table + // * Confirm that new table records are uploaded to CloudKit + @available(iOS 17, macOS 14, tvOS 17, watchOS 10, *) + @Test func migrationAddTableForgetToAddToSyncEngine() async throws { + let recordTypes = try await syncEngine.metadatabase.read { db in + try RecordType.order(by: \.tableName).fetchAll(db) + } + syncEngine.stop() + try syncEngine.tearDownSyncEngine() + + try await userDatabase.userWrite { db in + try #sql( + """ + CREATE TABLE "foos" ( + "id" INTEGER PRIMARY KEY NOT NULL ON CONFLICT REPLACE DEFAULT (uuid()) + ) + """ + ) + .execute(db) + try Foo + .insert { Foo(id: 1) } + .execute(db) + } + + // Relaunch sync engine but forget to add new table to sync engine. + do { + let relaunchedSyncEngine = try await SyncEngine( + container: syncEngine.container, + userDatabase: syncEngine.userDatabase, + tables: syncEngine.tables, + privateTables: syncEngine.privateTables + ) + let recordTypesAfterMigration = try await syncEngine.metadatabase.read { db in + try RecordType.order(by: \.tableName).fetchAll(db) + } + expectNoDifference(recordTypesAfterMigration, recordTypes) + relaunchedSyncEngine.stop() + try relaunchedSyncEngine.tearDownSyncEngine() + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [] + ) + ) + """ + } + } + + // Relaunch sync engine and remember this time to add new table to sync engine. + do { + let relaunchedSyncEngine = try await SyncEngine( + container: syncEngine.container, + userDatabase: syncEngine.userDatabase, + tables: syncEngine.tables + [SynchronizedTable(for: Foo.self)], + privateTables: syncEngine.privateTables + ) + try await relaunchedSyncEngine.processPendingRecordZoneChanges(scope: .private) + assertInlineSnapshot(of: container, as: .customDump) { + """ + MockCloudContainer( + privateCloudDatabase: MockCloudDatabase( + databaseScope: .private, + storage: [ + [0]: CKRecord( + recordID: CKRecord.ID(1:foos/zone/__defaultOwner__), + recordType: "foos", + parent: nil, + share: nil, + id: 1 + ) + ] + ), + sharedCloudDatabase: MockCloudDatabase( + databaseScope: .shared, + storage: [] + ) + ) + """ + } + } + } } } + + @Table + private struct Foo { + let id: Int + } #endif