import Foundation import Testing @testable import LocalData private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey { StorageKey( name: name, domain: domain, security: .none, owner: "Legacy", description: "Legacy string key" ) } private func makeModernStringKey( name: String, domain: StorageDomain, legacyKey: AnyStorageKey? ) -> StorageKey { StorageKey( name: name, domain: domain, security: .none, owner: "Modern", description: "Modern string key", migration: { destinationKey in guard let legacyKey else { return nil } return AnyStorageMigration( SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: legacyKey) ) } ) } private func makePhoneOnlyKey(name: String, domain: StorageDomain) -> StorageKey { StorageKey( name: name, domain: domain, security: .none, owner: "PhoneOnly", description: "Phone-only key", availability: .phoneOnly ) } private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey { StorageKey( name: name, domain: domain, security: .none, owner: "Source", description: "Source key" ) } private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey { StorageKey( name: name, domain: domain, security: .none, owner: "Destination", description: "Destination int key" ) } @Suite(.serialized) struct MigrationAdditionalTests { private let router: StorageRouter init() { let testBaseURL = FileManager.default.temporaryDirectory.appending(path: "MigrationAdditionalTests-\(UUID().uuidString)") router = StorageRouter( keychain: MockKeychainHelper(), encryption: EncryptionHelper(keychain: MockKeychainHelper()), file: FileStorageHelper(configuration: FileStorageConfiguration(baseURL: testBaseURL)), defaults: UserDefaultsHelper(defaults: UserDefaults(suiteName: "MigrationAdditionalTests.\(UUID().uuidString)")!) ) } @Test func migrationHistoryTrackingTest() async throws { let legacyKey = makeLegacyStringKey(name: "legacy.history", domain: .userDefaults(suite: nil)) let modernKey = makeModernStringKey( name: "modern.history", domain: .userDefaults(suite: nil), legacyKey: .key(legacyKey) ) try await router.set("history", for: legacyKey) _ = try await router.forceMigration(for: modernKey) let history = await router.migrationHistory(for: modernKey) #expect(history != nil) } @Test func migrationFailureKeepsSourceTest() async throws { let sourceKey = makeSourceStringKey(name: "legacy.rollback", domain: .userDefaults(suite: nil)) let destinationKey = makeDestinationIntKey(name: "modern.rollback", domain: .userDefaults(suite: nil)) try await router.set("not-a-number", for: sourceKey) let migration = DefaultTransformingMigration( destinationKey: destinationKey, sourceKey: sourceKey ) { _ in throw MigrationError.transformationFailed("Invalid integer") } let result = try await migration.migrate(using: router, context: MigrationContext()) #expect(result.success == false) #expect(try await router.exists(sourceKey) == true) #expect(try await router.exists(destinationKey) == false) } @Test func watchAvailabilityBlocksMigrationTest() async throws { let legacyKey = makeLegacyStringKey(name: "legacy.watch", domain: .userDefaults(suite: nil)) let destinationKey = makePhoneOnlyKey(name: "modern.watch", domain: .userDefaults(suite: nil)) let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey)) let deviceInfo = DeviceInfo( platform: .watchOS, systemVersion: "10.0", model: "Watch", isSimulator: true ) let context = MigrationContext(deviceInfo: deviceInfo) let shouldMigrate = try await migration.shouldMigrate(using: router, context: context) #expect(shouldMigrate == false) } @Test func largeDataMigrationTest() async throws { let legacyKey = makeLegacyStringKey(name: "legacy.large", domain: .userDefaults(suite: nil)) let modernKey = makeModernStringKey( name: "modern.large", domain: .userDefaults(suite: nil), legacyKey: .key(legacyKey) ) let largeValue = String(repeating: "a", count: 1_200_000) try await router.set(largeValue, for: legacyKey) let migration = SimpleLegacyMigration(destinationKey: modernKey, sourceKey: .key(legacyKey)) let result = try await migration.migrate(using: router, context: MigrationContext()) #expect(result.success == true) let migratedValue = try await router.get(modernKey) #expect(migratedValue == largeValue) } @Test func typeErasureMigrationTest() async throws { let legacyKey = makeLegacyStringKey(name: "legacy.erased", domain: .userDefaults(suite: nil)) let modernKey = makeModernStringKey( name: "modern.erased", domain: .userDefaults(suite: nil), legacyKey: .key(legacyKey) ) try await router.set("erased", for: legacyKey) let migration = AnyStorageMigration( SimpleLegacyMigration(destinationKey: modernKey, sourceKey: .key(legacyKey)) ) let shouldMigrate = try await migration.shouldMigrate(using: router, context: MigrationContext()) #expect(shouldMigrate == true) let result = try await migration.migrate(using: router, context: MigrationContext()) #expect(result.success == true) } }