import XCTest @testable import Rosetta @MainActor final class MigrationHarnessTests: XCTestCase { private var ctx: DBTestContext! override func setUpWithError() throws { ctx = DBTestContext() } override func tearDownWithError() throws { ctx.teardown() ctx = nil } func testLegacySyncOnlyMigrationReconcilesWithoutSQLiteUpsertSyntaxFailure() async throws { try await ctx.bootstrap() DatabaseManager.shared.close() let sqlite = try ctx.openSQLite() let rerunMigrations = DatabaseManager.migrationIdentifiers.dropFirst(3) let deleteList = rerunMigrations.map { "'\($0)'" }.joined(separator: ",") try sqlite.execute("DELETE FROM grdb_migrations WHERE identifier IN (\(deleteList))") try sqlite.execute("DROP TABLE IF EXISTS accounts_sync_times") try sqlite.execute("DELETE FROM sync_cursors") try sqlite.execute("INSERT INTO sync_cursors(account, timestamp) VALUES ('\(ctx.account)', 1234567890123)") try DatabaseManager.shared.bootstrap(accountPublicKey: ctx.account) let cursor = DatabaseManager.shared.loadSyncCursor(account: ctx.account) XCTAssertEqual(cursor, 1_234_567_890_123) } func testPartialReconcileBackfillsNullIds() async throws { try await ctx.bootstrap() DatabaseManager.shared.saveSyncCursor(account: ctx.account, timestamp: 9_001) DatabaseManager.shared.close() let sqlite = try ctx.openSQLite() try sqlite.execute("UPDATE accounts_sync_times SET id = NULL WHERE account = '\(ctx.account)'") try sqlite.execute("DELETE FROM grdb_migrations WHERE identifier = '\(DatabaseManager.migrationV7SyncCursorReconcile)'") try DatabaseManager.shared.bootstrap(accountPublicKey: ctx.account) let check = try ctx.openSQLite() let rows = try check.query( "SELECT id, last_sync FROM accounts_sync_times WHERE account = ? LIMIT 1", [.text(ctx.account)] ) XCTAssertEqual(rows.count, 1) XCTAssertNotEqual(rows.first?["id"], "") XCTAssertNotEqual(rows.first?["id"], "0") XCTAssertEqual(rows.first?["last_sync"], "9001") } func testMonotonicSyncCursorNeverDecreases() async throws { try await ctx.bootstrap() DatabaseManager.shared.saveSyncCursor(account: ctx.account, timestamp: 1_700_000_005_000) DatabaseManager.shared.saveSyncCursor(account: ctx.account, timestamp: 1_700_000_004_999) DatabaseManager.shared.saveSyncCursor(account: ctx.account, timestamp: 1_700_000_006_500) XCTAssertEqual(DatabaseManager.shared.loadSyncCursor(account: ctx.account), 1_700_000_006_500) } func testCompatibilityMirrorWritesAccountsSyncTimesAndSyncCursors() async throws { try await ctx.bootstrap() DatabaseManager.shared.saveSyncCursor(account: ctx.account, timestamp: 77_777) let sqlite = try ctx.openSQLite() let accountsRows = try sqlite.query( "SELECT last_sync, id FROM accounts_sync_times WHERE account = ? LIMIT 1", [.text(ctx.account)] ) let legacyRows = try sqlite.query( "SELECT timestamp FROM sync_cursors WHERE account = ? LIMIT 1", [.text(ctx.account)] ) XCTAssertEqual(accountsRows.first?["last_sync"], "77777") XCTAssertNotEqual(accountsRows.first?["id"], "") XCTAssertNotEqual(accountsRows.first?["id"], "0") XCTAssertEqual(legacyRows.first?["timestamp"], "77777") } }