fixed tests

Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
Matt Bruce 2026-01-17 09:18:04 -06:00
parent b30cc0da19
commit b1bf6020f3
16 changed files with 329 additions and 386 deletions

View File

@ -4,20 +4,18 @@ import Testing
@Suite struct AnyStorageKeyTests {
private struct StringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Test"
let description: String = "Test"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeStringKey(name: String) -> StorageKey<String> {
StorageKey(
name: name,
domain: .userDefaults(suite: nil),
security: .none,
owner: "Test",
description: "Test"
)
}
@Test func anyStorageKeyCapturesDescriptor() {
let key = StringKey(name: "test.key")
let key = makeStringKey(name: "test.key")
let anyKey = AnyStorageKey.key(key)
#expect(anyKey.descriptor.name == "test.key")
@ -27,7 +25,7 @@ import Testing
@Test func anyStorageKeyTriggersMigration() async throws {
let router = StorageRouter(keychain: MockKeychainHelper())
let key = StringKey(name: "test.key")
let key = makeStringKey(name: "test.key")
let anyKey = AnyStorageKey.key(key)
// This will call router.forceMigration(for: key)

View File

@ -7,31 +7,35 @@ import Testing
private struct AuditCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[
.key(TestKey(name: "k1", domain: .userDefaults(suite: nil))),
.key(TestKey(name: "k2", domain: .keychain(service: "s"), security: .keychain(accessibility: .afterFirstUnlock, accessControl: .userPresence))),
.key(TestKey(name: "k3", domain: .fileSystem(directory: .documents))),
.key(TestKey(name: "k4", domain: .encryptedFileSystem(directory: .caches))),
.key(TestKey(name: "k5", domain: .appGroupUserDefaults(identifier: "ig"), security: .encrypted(.recommended)))
.key(AuditTests.makeTestKey(name: "k1", domain: .userDefaults(suite: nil))),
.key(AuditTests.makeTestKey(
name: "k2",
domain: .keychain(service: "s"),
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .userPresence)
)),
.key(AuditTests.makeTestKey(name: "k3", domain: .fileSystem(directory: .documents))),
.key(AuditTests.makeTestKey(name: "k4", domain: .encryptedFileSystem(directory: .caches))),
.key(AuditTests.makeTestKey(
name: "k5",
domain: .appGroupUserDefaults(identifier: "ig"),
security: .encrypted(.recommended)
))
]
}
}
private struct TestKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy
let serializer: Serializer<String> = .json
let owner: String = "Audit"
let description: String = "Desc"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, domain: StorageDomain, security: SecurityPolicy = .none) {
self.name = name
self.domain = domain
self.security = security
}
private static func makeTestKey(
name: String,
domain: StorageDomain,
security: SecurityPolicy = .none
) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: security,
owner: "Audit",
description: "Desc"
)
}
@Test func renderCatalogText() {

View File

@ -2,40 +2,24 @@ import Foundation
import Testing
@testable import LocalData
private struct TestUserDefaultsKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "LocalDataTests"
let description: String = "Test-only key for user defaults round-trip."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, suiteName: String) {
self.name = name
self.domain = .userDefaults(suite: suiteName)
}
private func makeUserDefaultsKey(name: String, suiteName: String) -> StorageKey<String> {
StorageKey(
name: name,
domain: .userDefaults(suite: suiteName),
security: .none,
owner: "LocalDataTests",
description: "Test-only key for user defaults round-trip."
)
}
private struct TestFileKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "LocalDataTests"
let description: String = "Test-only key for file system round-trip."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, directory: URL) {
self.name = name
self.domain = .fileSystem(directory: .custom(directory))
}
private func makeFileKey(name: String, directory: URL) -> StorageKey<String> {
StorageKey(
name: name,
domain: .fileSystem(directory: .custom(directory)),
security: .none,
owner: "LocalDataTests",
description: "Test-only key for file system round-trip."
)
}
@Suite(.serialized)
@ -50,7 +34,7 @@ struct LocalDataTests {
}
}
let key = TestUserDefaultsKey(name: "test.string", suiteName: suiteName)
let key = makeUserDefaultsKey(name: "test.string", suiteName: suiteName)
let storedValue = "1.0.0"
try await router.set(storedValue, for: key)
@ -72,7 +56,7 @@ struct LocalDataTests {
try? FileManager.default.removeItem(at: tempDirectory)
}
let key = TestFileKey(name: "test.json", directory: tempDirectory)
let key = makeFileKey(name: "test.json", directory: tempDirectory)
let storedValue = "payload"
try await router.set(storedValue, for: key)

View File

@ -2,72 +2,65 @@ import Foundation
import Testing
@testable import LocalData
private struct LegacyStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Legacy"
let description: String = "Legacy string key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Legacy",
description: "Legacy string key"
)
}
private struct ModernStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Modern"
let description: String = "Modern string key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
let legacyKey: AnyStorageKey?
var migration: AnyStorageMigration? {
guard let legacyKey else { return nil }
return AnyStorageMigration(
SimpleLegacyMigration(destinationKey: self, sourceKey: legacyKey)
)
}
private func makeModernStringKey(
name: String,
domain: StorageDomain,
legacyKey: AnyStorageKey?
) -> StorageKey<String> {
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 struct PhoneOnlyKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "PhoneOnly"
let description: String = "Phone-only key"
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
private func makePhoneOnlyKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "PhoneOnly",
description: "Phone-only key",
availability: .phoneOnly
)
}
private struct SourceStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Source"
let description: String = "Source key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Source",
description: "Source key"
)
}
private struct DestinationIntKey: StorageKey {
typealias Value = Int
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<Int> = .json
let owner: String = "Destination"
let description: String = "Destination int key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey<Int> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Destination",
description: "Destination int key"
)
}
@Suite(.serialized)
@ -85,8 +78,8 @@ struct MigrationAdditionalTests {
}
@Test func migrationHistoryTrackingTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.history", domain: .userDefaults(suite: nil))
let modernKey = ModernStringKey(
let legacyKey = makeLegacyStringKey(name: "legacy.history", domain: .userDefaults(suite: nil))
let modernKey = makeModernStringKey(
name: "modern.history",
domain: .userDefaults(suite: nil),
legacyKey: .key(legacyKey)
@ -95,13 +88,13 @@ struct MigrationAdditionalTests {
try await router.set("history", for: legacyKey)
_ = try await router.forceMigration(for: modernKey)
let history = router.migrationHistory(for: modernKey)
let history = await router.migrationHistory(for: modernKey)
#expect(history != nil)
}
@Test func migrationFailureKeepsSourceTest() async throws {
let sourceKey = SourceStringKey(name: "legacy.rollback", domain: .userDefaults(suite: nil))
let destinationKey = DestinationIntKey(name: "modern.rollback", domain: .userDefaults(suite: nil))
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)
@ -119,8 +112,8 @@ struct MigrationAdditionalTests {
}
@Test func watchAvailabilityBlocksMigrationTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.watch", domain: .userDefaults(suite: nil))
let destinationKey = PhoneOnlyKey(name: "modern.watch", domain: .userDefaults(suite: nil))
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(
@ -136,8 +129,8 @@ struct MigrationAdditionalTests {
}
@Test func largeDataMigrationTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.large", domain: .userDefaults(suite: nil))
let modernKey = ModernStringKey(
let legacyKey = makeLegacyStringKey(name: "legacy.large", domain: .userDefaults(suite: nil))
let modernKey = makeModernStringKey(
name: "modern.large",
domain: .userDefaults(suite: nil),
legacyKey: .key(legacyKey)
@ -155,8 +148,8 @@ struct MigrationAdditionalTests {
}
@Test func typeErasureMigrationTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.erased", domain: .userDefaults(suite: nil))
let modernKey = ModernStringKey(
let legacyKey = makeLegacyStringKey(name: "legacy.erased", domain: .userDefaults(suite: nil))
let modernKey = makeModernStringKey(
name: "modern.erased",
domain: .userDefaults(suite: nil),
legacyKey: .key(legacyKey)

View File

@ -2,36 +2,34 @@ import Foundation
import Testing
@testable import LocalData
private struct LegacyIntegrationKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Legacy"
let description: String = "Legacy integration key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeLegacyIntegrationKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Legacy",
description: "Legacy integration key"
)
}
private struct ModernIntegrationKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
let serializer: Serializer<String> = .json
let owner: String = "Modern"
let description: String = "Modern integration key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
let legacyKey: AnyStorageKey?
var migration: AnyStorageMigration? {
guard let legacyKey else { return nil }
return AnyStorageMigration(
SimpleLegacyMigration(destinationKey: self, sourceKey: legacyKey)
)
}
private func makeModernIntegrationKey(
name: String,
domain: StorageDomain,
legacyKey: AnyStorageKey?
) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none),
owner: "Modern",
description: "Modern integration key",
migration: { destinationKey in
guard let legacyKey else { return nil }
return AnyStorageMigration(
SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: legacyKey)
)
}
)
}
private struct IntegrationCatalog: StorageKeyCatalog {
@ -53,8 +51,8 @@ struct MigrationIntegrationTests {
}
@Test func endToEndMigrationTest() async throws {
let legacyKey = LegacyIntegrationKey(name: "legacy.integration", domain: .userDefaults(suite: nil))
let modernKey = ModernIntegrationKey(
let legacyKey = makeLegacyIntegrationKey(name: "legacy.integration", domain: .userDefaults(suite: nil))
let modernKey = makeModernIntegrationKey(
name: "modern.integration",
domain: .keychain(service: "test.migration"),
legacyKey: .key(legacyKey)
@ -69,8 +67,8 @@ struct MigrationIntegrationTests {
}
@Test func migrationRegistrationTest() async throws {
let legacyKey = LegacyIntegrationKey(name: "legacy.catalog", domain: .userDefaults(suite: nil))
let modernKey = ModernIntegrationKey(
let legacyKey = makeLegacyIntegrationKey(name: "legacy.catalog", domain: .userDefaults(suite: nil))
let modernKey = makeModernIntegrationKey(
name: "modern.catalog",
domain: .keychain(service: "test.migration"),
legacyKey: .key(legacyKey)
@ -86,8 +84,8 @@ struct MigrationIntegrationTests {
}
@Test func concurrentMigrationTest() async throws {
let legacyKey = LegacyIntegrationKey(name: "legacy.concurrent", domain: .userDefaults(suite: nil))
let modernKey = ModernIntegrationKey(
let legacyKey = makeLegacyIntegrationKey(name: "legacy.concurrent", domain: .userDefaults(suite: nil))
let modernKey = makeModernIntegrationKey(
name: "modern.concurrent",
domain: .keychain(service: "test.migration"),
legacyKey: .key(legacyKey)
@ -105,7 +103,7 @@ struct MigrationIntegrationTests {
}
@Test func migrationFailureResultTest() async throws {
let destinationKey = ModernIntegrationKey(
let destinationKey = makeModernIntegrationKey(
name: "modern.failure",
domain: .userDefaults(suite: nil),
legacyKey: nil

View File

@ -2,52 +2,44 @@ import Foundation
import Testing
@testable import LocalData
private struct LegacyStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Legacy"
let description: String = "Legacy key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Legacy",
description: "Legacy key"
)
}
private struct DestinationStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
let serializer: Serializer<String> = .json
let owner: String = "Destination"
let description: String = "Destination key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
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 struct SourceStringKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "Source"
let description: String = "Source key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Source",
description: "Source key"
)
}
private struct DestinationIntKey: StorageKey {
typealias Value = Int
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<Int> = .json
let owner: String = "Destination"
let description: String = "Destination int key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey<Int> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Destination",
description: "Destination int key"
)
}
@Suite(.serialized)
@ -65,8 +57,11 @@ struct MigrationProtocolTests {
}
@Test func simpleLegacyMigrationTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.simple", domain: .userDefaults(suite: nil))
let destinationKey = DestinationStringKey(name: "modern.simple", domain: .keychain(service: "test.migration"))
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))
@ -83,8 +78,11 @@ struct MigrationProtocolTests {
}
@Test func conditionalMigrationTest() async throws {
let legacyKey = LegacyStringKey(name: "legacy.conditional", domain: .userDefaults(suite: nil))
let destinationKey = DestinationStringKey(name: "modern.conditional", domain: .keychain(service: "test.migration"))
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(
@ -105,8 +103,8 @@ struct MigrationProtocolTests {
}
@Test func transformingMigrationTest() async throws {
let sourceKey = SourceStringKey(name: "legacy.transform", domain: .userDefaults(suite: nil))
let destinationKey = DestinationIntKey(name: "modern.transform", domain: .userDefaults(suite: nil))
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(
@ -127,9 +125,9 @@ struct MigrationProtocolTests {
}
@Test func aggregatingMigrationTest() async throws {
let sourceKeyA = SourceStringKey(name: "legacy.aggregate.a", domain: .userDefaults(suite: nil))
let sourceKeyB = SourceStringKey(name: "legacy.aggregate.b", domain: .userDefaults(suite: nil))
let destinationKey = DestinationStringKey(name: "modern.aggregate", domain: .userDefaults(suite: nil))
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)
@ -151,7 +149,7 @@ struct MigrationProtocolTests {
}
@Test func migrationErrorHandlingTest() async throws {
let destinationKey = DestinationStringKey(name: "modern.error", domain: .userDefaults(suite: nil))
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())

View File

@ -2,42 +2,34 @@ 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<String> = .json
let owner: String = "Legacy"
let description: String = "Legacy key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeLegacyKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "Legacy",
description: "Legacy key"
)
}
private struct ModernKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
let serializer: Serializer<String> = .json
let owner: String = "Modern"
let description: String = "Modern key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
let legacyKey: AnyStorageKey?
init(name: String, domain: StorageDomain, legacyKey: AnyStorageKey?) {
self.name = name
self.domain = domain
self.legacyKey = legacyKey
}
var migration: AnyStorageMigration? {
guard let legacyKey else { return nil }
return AnyStorageMigration(
SimpleLegacyMigration(destinationKey: self, sourceKey: legacyKey)
)
}
private func makeModernKey(
name: String,
domain: StorageDomain,
legacyKey: AnyStorageKey?
) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none),
owner: "Modern",
description: "Modern key",
migration: { destinationKey in
guard let legacyKey else { return nil }
return AnyStorageMigration(
SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: legacyKey)
)
}
)
}
@Suite(.serialized)
@ -65,7 +57,7 @@ struct MigrationTests {
}
// 1. Setup legacy data manually in UserDefaults
let legacyKey = LegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName))
let legacyKey = makeLegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName))
try await router.set(secretValue, for: legacyKey)
// Verify it exists in legacy location
@ -73,7 +65,7 @@ struct MigrationTests {
#expect(existsInLegacy == true)
// 2. Setup modern key with legacy source
let modernKey = ModernKey(
let modernKey = makeModernKey(
name: modernName,
domain: .keychain(service: "test.migration"),
legacyKey: .key(legacyKey)
@ -104,11 +96,11 @@ struct MigrationTests {
}
// 1. Setup legacy data
let legacyKey = LegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName))
let legacyKey = makeLegacyKey(name: legacyName, domain: .userDefaults(suite: suiteName))
try await router.set(value, for: legacyKey)
// 2. Setup modern key
let modernKey = ModernKey(
let modernKey = makeModernKey(
name: modernName,
domain: .userDefaults(suite: suiteName),
legacyKey: .key(legacyKey)

View File

@ -1,10 +1,8 @@
import Foundation
@testable import LocalData
struct MockMigration<Destination: StorageKey>: StorageMigration {
typealias DestinationKey = Destination
let destinationKey: Destination
struct MockMigration<Value: Codable & Sendable>: StorageMigration {
let destinationKey: StorageKey<Value>
let shouldSucceed: Bool
let shouldMigrateResult: Bool
let migrationDelay: TimeInterval
@ -28,10 +26,8 @@ struct MockMigration<Destination: StorageKey>: StorageMigration {
}
}
struct FailingMigration<Destination: StorageKey>: StorageMigration {
typealias DestinationKey = Destination
let destinationKey: Destination
struct FailingMigration<Value: Codable & Sendable>: StorageMigration {
let destinationKey: StorageKey<Value>
let error: MigrationError
func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool { true }

View File

@ -2,39 +2,35 @@ import Foundation
import Testing
@testable import LocalData
private struct TestRegistryKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String
let description: String
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, owner: String = "Test", description: String = "Test") {
self.name = name
self.owner = owner
self.description = description
}
private func makeRegistryKey(
name: String,
owner: String = "Test",
description: String = "Test"
) -> StorageKey<String> {
StorageKey(
name: name,
domain: .userDefaults(suite: nil),
security: .none,
owner: owner,
description: description
)
}
private struct CatalogA: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[.key(TestRegistryKey(name: "key.a", owner: "ModuleA"))]
[.key(makeRegistryKey(name: "key.a", owner: "ModuleA"))]
}
}
private struct CatalogB: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[.key(TestRegistryKey(name: "key.b", owner: "ModuleB"))]
[.key(makeRegistryKey(name: "key.b", owner: "ModuleB"))]
}
}
private struct CatalogCollision: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[.key(TestRegistryKey(name: "key.a", owner: "ModuleCollision"))]
[.key(makeRegistryKey(name: "key.a", owner: "ModuleCollision"))]
}
}

View File

@ -16,26 +16,22 @@ import Testing
)
}
private struct DomainKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy
let serializer: Serializer<String> = .json
let owner: String = "DomainTests"
let description: String = "Domain test key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, domain: StorageDomain, security: SecurityPolicy = .none) {
self.name = name
self.domain = domain
self.security = security
}
private func makeDomainKey(
name: String,
domain: StorageDomain,
security: SecurityPolicy = .none
) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: security,
owner: "DomainTests",
description: "Domain test key"
)
}
@Test func domainUserDefaults() async throws {
let key = DomainKey(name: "defaults.key", domain: .userDefaults(suite: nil))
let key = makeDomainKey(name: "defaults.key", domain: .userDefaults(suite: nil))
try await router.set("value", for: key)
#expect(try await router.get(key) == "value")
try await router.remove(key)
@ -46,14 +42,14 @@ import Testing
// We use a mock configuration to avoid requiring a real app group
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
let key = DomainKey(name: "appgroup.defaults.key", domain: .appGroupUserDefaults(identifier: "group.test"))
let key = makeDomainKey(name: "appgroup.defaults.key", domain: .appGroupUserDefaults(identifier: "group.test"))
try await router.set("value", for: key)
#expect(try await router.get(key) == "value")
try await router.remove(key)
}
@Test func domainKeychain() async throws {
let key = DomainKey(
let key = makeDomainKey(
name: "keychain.key",
domain: .keychain(service: "test"),
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
@ -64,14 +60,14 @@ import Testing
}
@Test func domainFileSystem() async throws {
let key = DomainKey(name: "file.key", domain: .fileSystem(directory: .documents))
let key = makeDomainKey(name: "file.key", domain: .fileSystem(directory: .documents))
try await router.set("value", for: key)
#expect(try await router.get(key) == "value")
try await router.remove(key)
}
@Test func domainEncryptedFileSystem() async throws {
let key = DomainKey(name: "encfile.key", domain: .encryptedFileSystem(directory: .documents))
let key = makeDomainKey(name: "encfile.key", domain: .encryptedFileSystem(directory: .documents))
try await router.set("value", for: key)
#expect(try await router.get(key) == "value")
try await router.remove(key)
@ -80,7 +76,10 @@ import Testing
@Test func domainAppGroupFileSystem() async throws {
// App blocks usually fail or return nil in tests, but we exercise the path
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
let key = DomainKey(name: "appgroup.file.key", domain: .appGroupFileSystem(identifier: "group.test", directory: .documents))
let key = makeDomainKey(
name: "appgroup.file.key",
domain: .appGroupFileSystem(identifier: "group.test", directory: .documents)
)
do {
try await router.set("value", for: key)
@ -94,7 +93,7 @@ import Testing
@Test func resolutionFailureService() async throws {
// Clear default service
await router.updateStorageConfiguration(StorageConfiguration(defaultKeychainService: nil))
let key = DomainKey(
let key = makeDomainKey(
name: "bad.service.key",
domain: .keychain(service: nil),
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
@ -108,7 +107,7 @@ import Testing
@Test func resolutionFailureIdentifier() async throws {
// Clear default identifier
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: nil))
let key = DomainKey(name: "bad.id.key", domain: .appGroupUserDefaults(identifier: nil))
let key = makeDomainKey(name: "bad.id.key", domain: .appGroupUserDefaults(identifier: nil))
await #expect(throws: StorageError.invalidAppGroupIdentifier("none")) {
try await router.set("value", for: key)

View File

@ -2,21 +2,19 @@ import Foundation
import Testing
@testable import LocalData
private struct MockKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "ErrorTests"
let description: String = "Test key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeMockKey(name: String, domain: StorageDomain) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: .none,
owner: "ErrorTests",
description: "Test key"
)
}
private struct PartialCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[.key(MockKey(name: "registered.key", domain: .userDefaults(suite: nil)))]
[.key(makeMockKey(name: "registered.key", domain: .userDefaults(suite: nil)))]
}
}
@ -37,7 +35,7 @@ struct RouterErrorTests {
@Test func unregisteredKeyThrows() async throws {
try await router.registerCatalog(PartialCatalog())
let badKey = MockKey(name: "unregistered.key", domain: .userDefaults(suite: nil))
let badKey = makeMockKey(name: "unregistered.key", domain: .userDefaults(suite: nil))
await #expect(throws: StorageError.unregisteredKey("unregistered.key")) {
try await router.set("value", for: badKey)
@ -51,7 +49,7 @@ struct RouterErrorTests {
defaultAppGroupIdentifier: nil
))
let appGroupKey = MockKey(name: "appgroup.key", domain: .appGroupUserDefaults(identifier: nil))
let appGroupKey = makeMockKey(name: "appgroup.key", domain: .appGroupUserDefaults(identifier: nil))
await #expect(throws: StorageError.invalidAppGroupIdentifier("none")) {
try await router.set("value", for: appGroupKey)
@ -65,7 +63,7 @@ struct RouterErrorTests {
defaultAppGroupIdentifier: "test"
))
let _ = MockKey(name: "keychain.key", domain: .keychain(service: nil))
let _ = makeMockKey(name: "keychain.key", domain: .keychain(service: nil))
// Note: Keychain security policy must match keychain domain in descriptor
// but descriptor is usually created from key.

View File

@ -17,20 +17,22 @@ import Security
)
}
private struct SecurityKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain
let security: SecurityPolicy
let serializer: Serializer<String> = .json
let owner: String = "SecurityTests"
let description: String = "Security test key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
private func makeSecurityKey(
name: String,
domain: StorageDomain,
security: SecurityPolicy
) -> StorageKey<String> {
StorageKey(
name: name,
domain: domain,
security: security,
owner: "SecurityTests",
description: "Security test key"
)
}
@Test func applySecurityNone() async throws {
let key = SecurityKey(name: "none.key", domain: .userDefaults(suite: nil), security: .none)
let key = makeSecurityKey(name: "none.key", domain: .userDefaults(suite: nil), security: .none)
let value = "test-value"
try await router.set(value, for: key)
@ -39,7 +41,7 @@ import Security
}
@Test func applySecurityEncryptedAES() async throws {
let key = SecurityKey(
let key = makeSecurityKey(
name: "aes.key",
domain: .userDefaults(suite: nil),
security: .encrypted(.aes256(keyDerivation: .hkdf()))
@ -52,7 +54,7 @@ import Security
}
@Test func applySecurityEncryptedChaCha() async throws {
let key = SecurityKey(
let key = makeSecurityKey(
name: "chacha.key",
domain: .userDefaults(suite: nil),
security: .encrypted(.chacha20Poly1305(keyDerivation: .hkdf()))
@ -65,7 +67,7 @@ import Security
}
@Test func applySecurityKeychain() async throws {
let key = SecurityKey(
let key = makeSecurityKey(
name: "keychain.key",
domain: .keychain(service: "test-service"),
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
@ -78,7 +80,7 @@ import Security
}
@Test func applySecurityPBKDF2() async throws {
let key = SecurityKey(
let key = makeSecurityKey(
name: "pbkdf2.key",
domain: .userDefaults(suite: nil),
security: .encrypted(.aes256(keyDerivation: .pbkdf2()))

View File

@ -4,22 +4,14 @@ import Testing
// MARK: - Test Keys
private struct TestCatalogKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "CatalogTests"
let description: String
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
init(name: String, description: String = "Test key") {
self.name = name
self.description = description
}
private func makeCatalogKey(name: String, description: String = "Test key") -> StorageKey<String> {
StorageKey(
name: name,
domain: .userDefaults(suite: nil),
security: .none,
owner: "CatalogTests",
description: description
)
}
// MARK: - Test Catalogs
@ -27,8 +19,8 @@ private struct TestCatalogKey: StorageKey {
private struct ValidCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[
.key(TestCatalogKey(name: "valid.key1", description: "First test key")),
.key(TestCatalogKey(name: "valid.key2", description: "Second test key"))
.key(makeCatalogKey(name: "valid.key1", description: "First test key")),
.key(makeCatalogKey(name: "valid.key2", description: "Second test key"))
]
}
}
@ -36,8 +28,8 @@ private struct ValidCatalog: StorageKeyCatalog {
private struct DuplicateNameCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[
.key(TestCatalogKey(name: "duplicate.name", description: "First instance")),
.key(TestCatalogKey(name: "duplicate.name", description: "Second instance"))
.key(makeCatalogKey(name: "duplicate.name", description: "First instance")),
.key(makeCatalogKey(name: "duplicate.name", description: "Second instance"))
]
}
}
@ -49,7 +41,7 @@ private struct EmptyCatalog: StorageKeyCatalog {
private struct MissingDescriptionCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[
.key(TestCatalogKey(name: "missing.desc", description: " "))
.key(makeCatalogKey(name: "missing.desc", description: " "))
]
}
}
@ -78,7 +70,7 @@ struct StorageCatalogTests {
}
@Test func descriptorCapturesKeyMetadata() {
let key = TestCatalogKey(name: "metadata.test", description: "Metadata test key")
let key = makeCatalogKey(name: "metadata.test", description: "Metadata test key")
let anyKey = AnyStorageKey.key(key)
let descriptor = anyKey.descriptor

View File

@ -4,19 +4,13 @@ import Testing
@Suite struct StorageKeyDefaultsTests {
private struct MinimalKey: StorageKey {
typealias Value = Int
let name: String = "minimal.key"
let domain: StorageDomain = .userDefaults(suite: nil)
let serializer: Serializer<Int> = .json
let owner: String = "Test"
let description: String = "Test"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
@Test func defaultSecurityPolicyIsRecommended() {
let key = MinimalKey()
let key = StorageKey<Int>(
name: "minimal.key",
domain: .userDefaults(suite: nil),
owner: "Test",
description: "Test"
)
// This exercises the default implementation in StorageKey+Defaults.swift
#expect(key.security == .recommended)
}

View File

@ -16,21 +16,20 @@ struct SyncIntegrationTests {
)
}
private struct SyncKey: StorageKey {
typealias Value = String
let name: String
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner: String = "SyncTests"
let description: String = "Sync key"
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .automaticSmall
private static func makeSyncKey(name: String) -> StorageKey<String> {
StorageKey(
name: name,
domain: .userDefaults(suite: nil),
security: .none,
owner: "SyncTests",
description: "Sync key",
syncPolicy: .automaticSmall
)
}
private struct SyncCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[.key(SyncKey(name: "sync.test.key"))]
[.key(SyncIntegrationTests.makeSyncKey(name: "sync.test.key"))]
}
}
@ -46,7 +45,7 @@ struct SyncIntegrationTests {
try await router.updateFromSync(keyName: keyName, data: data)
// 3. Verify it was stored in the local domain
let retrieved: String? = try await router.get(SyncKey(name: keyName))
let retrieved: String? = try await router.get(Self.makeSyncKey(name: keyName))
#expect(retrieved == expectedValue)
}

View File

@ -1,7 +1,7 @@
{
"configurations" : [
{
"id" : "D2951487-F388-4A07-A1E8-3A4B179619B9",
"id" : "CB38B4BA-86AE-457E-B74C-31A492DEB330",
"name" : "Configuration 1",
"options" : {