import Foundation import Testing @testable import LocalData private struct LegacyKey: StorageKey { typealias Value = String let name: String let domain: StorageDomain let security: SecurityPolicy = .none let serializer: Serializer = .json let owner: String = "Legacy" let description: String = "Legacy key" let availability: PlatformAvailability = .all let syncPolicy: SyncPolicy = .never } private struct ModernKey: StorageKey { typealias Value = String let name: String let domain: StorageDomain let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .none) let serializer: Serializer = .json let owner: String = "Modern" let description: String = "Modern key" let availability: PlatformAvailability = .all let syncPolicy: SyncPolicy = .never let migrationSources: [AnyStorageKey] init(name: String, domain: StorageDomain, migrationSources: [AnyStorageKey]) { self.name = name self.domain = domain self.migrationSources = migrationSources } } @Suite(.serialized) struct MigrationTests { private let router: StorageRouter init() { let testBaseURL = FileManager.default.temporaryDirectory.appending(path: "MigrationTests-\(UUID().uuidString)") router = StorageRouter( keychain: MockKeychainHelper(), encryption: EncryptionHelper(keychain: MockKeychainHelper()), file: FileStorageHelper(configuration: FileStorageConfiguration(baseURL: testBaseURL)), defaults: UserDefaultsHelper(defaults: UserDefaults(suiteName: "MigrationTests-\(UUID().uuidString)")!) ) } @Test func automaticMigrationFromUserDefaultsToKeychain() async throws { let legacyName = "legacy.user.name" let modernName = "user.name" let suiteName = "MigrationTests.\(UUID().uuidString)" let secretValue = "Matt Bruce" defer { UserDefaults().removePersistentDomain(forName: suiteName) } // 1. Setup legacy data manually in UserDefaults let legacyKey = LegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName)) try await router.set(secretValue, for: legacyKey) // Verify it exists in legacy location let existsInLegacy = try await router.exists(legacyKey) #expect(existsInLegacy == true) // 2. Setup modern key with legacy source let modernKey = ModernKey( name: modernName, domain: .keychain(service: "test.migration"), migrationSources: [.key(legacyKey)] ) // 3. Trigger automatic migration via GET let migratedValue = try await router.get(modernKey) #expect(migratedValue == secretValue) // 4. Verify data moved // Modern should now exist let existsInModern = try await router.exists(modernKey) #expect(existsInModern == true) // Legacy should be gone let existsInLegacyAfter = try await router.exists(legacyKey) #expect(existsInLegacyAfter == false) } @Test func manualMigrationSweep() async throws { let legacyName = "legacy.manual.key" let modernName = "modern.manual.key" let suiteName = "MigrationTests.Manual.\(UUID().uuidString)" let value = "Manual Data" defer { UserDefaults().removePersistentDomain(forName: suiteName) } // 1. Setup legacy data let legacyKey = LegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName)) try await router.set(value, for: legacyKey) // 2. Setup modern key let modernKey = ModernKey( name: modernName, domain: .userDefaults(suite: suiteName), migrationSources: [.key(legacyKey)] ) // 3. Trigger manual migration try await router.migrate(for: modernKey) // 4. Verify let hasModern = try await router.exists(modernKey) #expect(hasModern == true) let hasLegacy = try await router.exists(legacyKey) #expect(hasLegacy == false) } }