LocalData/Tests/LocalDataTests/MigrationProtocolTests.swift
Matt Bruce 0afaf34c78 Tests AnyStorageKeyTests.swift, AuditTests.swift, LocalDataTests.swift (+13 more)
Summary:
- Tests: AnyStorageKeyTests.swift, AuditTests.swift, LocalDataTests.swift, MigrationAdditionalTests.swift, MigrationIntegrationTests.swift (+11 more)
- Added symbols: func makeStringKey, func makeUserDefaultsKey, func makeFileKey, func makeLegacyStringKey, func makeModernStringKey, func makePhoneOnlyKey (+14 more)
- Removed symbols: struct StringKey, typealias Value, struct TestKey, struct TestUserDefaultsKey, struct TestFileKey, struct LegacyStringKey (+19 more)

Stats:
- 16 files changed, 329 insertions(+), 386 deletions(-)
2026-01-18 14:53:30 -06:00

160 lines
6.1 KiB
Swift

import Foundation
import Testing
@testable import LocalData
private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Legacy",
description: "Legacy key"
)
}
private func makeDestinationStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none),
owner: "Destination",
description: "Destination key"
)
}
private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Source",
description: "Source key"
)
}
private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey<Int> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Destination",
description: "Destination int key"
)
}
@Suite(.serialized)
struct MigrationProtocolTests {
private let router: StorageRouter
init() {
let testBaseURL = FileManager.default.temporaryDirectory.appending(path: "MigrationProtocolTests-\(UUID().uuidString)")
router = StorageRouter(
keychain: MockKeychainHelper(),
encryption: EncryptionHelper(keychain: MockKeychainHelper()),
file: FileStorageHelper(configuration: FileStorageConfiguration(baseURL: testBaseURL)),
defaults: UserDefaultsHelper(defaults: UserDefaults(suiteName: "MigrationProtocolTests.\(UUID().uuidString)")!)
)
}
@Test func simpleLegacyMigrationTest() async throws {
let legacyKey = makeLegacyStringKey(name: "legacy.simple", domain: .userDefaults(suite: nil))
let destinationKey = makeDestinationStringKey(
name: "modern.simple",
domain: .keychain(service: "test.migration")
)
try await router.set("value", for: legacyKey)
let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
let context = MigrationContext(appVersion: "1.0")
let shouldMigrate = try await migration.shouldMigrate(using: router, context: context)
#expect(shouldMigrate == true)
let result = try await migration.migrate(using: router, context: context)
#expect(result.success == true)
let migratedValue = try await router.get(destinationKey)
#expect(migratedValue == "value")
}
@Test func conditionalMigrationTest() async throws {
let legacyKey = makeLegacyStringKey(name: "legacy.conditional", domain: .userDefaults(suite: nil))
let destinationKey = makeDestinationStringKey(
name: "modern.conditional",
domain: .keychain(service: "test.migration")
)
try await router.set("value", for: legacyKey)
let fallback = AnyStorageMigration(
SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
)
let migration = AppVersionConditionalMigration(
destinationKey: destinationKey,
minAppVersion: "2.0",
fallbackMigration: fallback
)
let context = MigrationContext(appVersion: "1.0")
let shouldMigrate = try await migration.shouldMigrate(using: router, context: context)
#expect(shouldMigrate == true)
let result = try await migration.migrate(using: router, context: context)
#expect(result.success == true)
}
@Test func transformingMigrationTest() async throws {
let sourceKey = makeSourceStringKey(name: "legacy.transform", domain: .userDefaults(suite: nil))
let destinationKey = makeDestinationIntKey(name: "modern.transform", domain: .userDefaults(suite: nil))
try await router.set("42", for: sourceKey)
let migration = DefaultTransformingMigration(
destinationKey: destinationKey,
sourceKey: sourceKey
) { value in
guard let intValue = Int(value) else {
throw MigrationError.transformationFailed("Invalid integer")
}
return intValue
}
let result = try await migration.migrate(using: router, context: MigrationContext())
#expect(result.success == true)
let migratedValue = try await router.get(destinationKey)
#expect(migratedValue == 42)
}
@Test func aggregatingMigrationTest() async throws {
let sourceKeyA = makeSourceStringKey(name: "legacy.aggregate.a", domain: .userDefaults(suite: nil))
let sourceKeyB = makeSourceStringKey(name: "legacy.aggregate.b", domain: .userDefaults(suite: nil))
let destinationKey = makeDestinationStringKey(name: "modern.aggregate", domain: .userDefaults(suite: nil))
try await router.set("alpha", for: sourceKeyA)
try await router.set("beta", for: sourceKeyB)
let migration = DefaultAggregatingMigration(
destinationKey: destinationKey,
sourceKeys: [.key(sourceKeyA), .key(sourceKeyB)]
) { sources in
let values = sources.compactMap { $0.value as? String }
return values.joined(separator: ",")
}
let result = try await migration.migrate(using: router, context: MigrationContext())
#expect(result.success == true)
let migratedValue = try await router.get(destinationKey)
#expect(migratedValue.contains("alpha"))
#expect(migratedValue.contains("beta"))
}
@Test func migrationErrorHandlingTest() async throws {
let destinationKey = makeDestinationStringKey(name: "modern.error", domain: .userDefaults(suite: nil))
let migration = FailingMigration(destinationKey: destinationKey, error: .transformationFailed("Failed"))
let result = try await migration.migrate(using: router, context: MigrationContext())
#expect(result.success == false)
#expect(result.errors.first == .transformationFailed("Failed"))
}
}