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(-)
This commit is contained in:
parent
7b4a0b46f8
commit
0afaf34c78
@ -4,20 +4,18 @@ import Testing
|
|||||||
|
|
||||||
@Suite struct AnyStorageKeyTests {
|
@Suite struct AnyStorageKeyTests {
|
||||||
|
|
||||||
private struct StringKey: StorageKey {
|
private func makeStringKey(name: String) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
domain: .userDefaults(suite: nil),
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Test",
|
||||||
let owner: String = "Test"
|
description: "Test"
|
||||||
let description: String = "Test"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func anyStorageKeyCapturesDescriptor() {
|
@Test func anyStorageKeyCapturesDescriptor() {
|
||||||
let key = StringKey(name: "test.key")
|
let key = makeStringKey(name: "test.key")
|
||||||
let anyKey = AnyStorageKey.key(key)
|
let anyKey = AnyStorageKey.key(key)
|
||||||
|
|
||||||
#expect(anyKey.descriptor.name == "test.key")
|
#expect(anyKey.descriptor.name == "test.key")
|
||||||
@ -27,7 +25,7 @@ import Testing
|
|||||||
|
|
||||||
@Test func anyStorageKeyTriggersMigration() async throws {
|
@Test func anyStorageKeyTriggersMigration() async throws {
|
||||||
let router = StorageRouter(keychain: MockKeychainHelper())
|
let router = StorageRouter(keychain: MockKeychainHelper())
|
||||||
let key = StringKey(name: "test.key")
|
let key = makeStringKey(name: "test.key")
|
||||||
let anyKey = AnyStorageKey.key(key)
|
let anyKey = AnyStorageKey.key(key)
|
||||||
|
|
||||||
// This will call router.forceMigration(for: key)
|
// This will call router.forceMigration(for: key)
|
||||||
|
|||||||
@ -7,31 +7,35 @@ import Testing
|
|||||||
private struct AuditCatalog: StorageKeyCatalog {
|
private struct AuditCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[
|
[
|
||||||
.key(TestKey(name: "k1", domain: .userDefaults(suite: nil))),
|
.key(AuditTests.makeTestKey(name: "k1", domain: .userDefaults(suite: nil))),
|
||||||
.key(TestKey(name: "k2", domain: .keychain(service: "s"), security: .keychain(accessibility: .afterFirstUnlock, accessControl: .userPresence))),
|
.key(AuditTests.makeTestKey(
|
||||||
.key(TestKey(name: "k3", domain: .fileSystem(directory: .documents))),
|
name: "k2",
|
||||||
.key(TestKey(name: "k4", domain: .encryptedFileSystem(directory: .caches))),
|
domain: .keychain(service: "s"),
|
||||||
.key(TestKey(name: "k5", domain: .appGroupUserDefaults(identifier: "ig"), security: .encrypted(.recommended)))
|
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 {
|
private static func makeTestKey(
|
||||||
typealias Value = String
|
name: String,
|
||||||
let name: String
|
domain: StorageDomain,
|
||||||
let domain: StorageDomain
|
security: SecurityPolicy = .none
|
||||||
let security: SecurityPolicy
|
) -> StorageKey<String> {
|
||||||
let serializer: Serializer<String> = .json
|
StorageKey(
|
||||||
let owner: String = "Audit"
|
name: name,
|
||||||
let description: String = "Desc"
|
domain: domain,
|
||||||
let availability: PlatformAvailability = .all
|
security: security,
|
||||||
let syncPolicy: SyncPolicy = .never
|
owner: "Audit",
|
||||||
|
description: "Desc"
|
||||||
init(name: String, domain: StorageDomain, security: SecurityPolicy = .none) {
|
)
|
||||||
self.name = name
|
|
||||||
self.domain = domain
|
|
||||||
self.security = security
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func renderCatalogText() {
|
@Test func renderCatalogText() {
|
||||||
|
|||||||
@ -2,40 +2,24 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct TestUserDefaultsKey: StorageKey {
|
private func makeUserDefaultsKey(name: String, suiteName: String) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
|
name: name,
|
||||||
let name: String
|
domain: .userDefaults(suite: suiteName),
|
||||||
let domain: StorageDomain
|
security: .none,
|
||||||
let security: SecurityPolicy = .none
|
owner: "LocalDataTests",
|
||||||
let serializer: Serializer<String> = .json
|
description: "Test-only key for user defaults round-trip."
|
||||||
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 struct TestFileKey: StorageKey {
|
private func makeFileKey(name: String, directory: URL) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
|
name: name,
|
||||||
let name: String
|
domain: .fileSystem(directory: .custom(directory)),
|
||||||
let domain: StorageDomain
|
security: .none,
|
||||||
let security: SecurityPolicy = .none
|
owner: "LocalDataTests",
|
||||||
let serializer: Serializer<String> = .json
|
description: "Test-only key for file system round-trip."
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite(.serialized)
|
@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"
|
let storedValue = "1.0.0"
|
||||||
|
|
||||||
try await router.set(storedValue, for: key)
|
try await router.set(storedValue, for: key)
|
||||||
@ -72,7 +56,7 @@ struct LocalDataTests {
|
|||||||
try? FileManager.default.removeItem(at: tempDirectory)
|
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"
|
let storedValue = "payload"
|
||||||
|
|
||||||
try await router.set(storedValue, for: key)
|
try await router.set(storedValue, for: key)
|
||||||
|
|||||||
@ -2,72 +2,65 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct LegacyStringKey: StorageKey {
|
private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Legacy",
|
||||||
let owner: String = "Legacy"
|
description: "Legacy string key"
|
||||||
let description: String = "Legacy string key"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
private func makePhoneOnlyKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "PhoneOnly",
|
||||||
let owner: String = "PhoneOnly"
|
description: "Phone-only key",
|
||||||
let description: String = "Phone-only key"
|
availability: .phoneOnly
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
)
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SourceStringKey: StorageKey {
|
private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Source",
|
||||||
let owner: String = "Source"
|
description: "Source key"
|
||||||
let description: String = "Source key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DestinationIntKey: StorageKey {
|
private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey<Int> {
|
||||||
typealias Value = Int
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<Int> = .json
|
owner: "Destination",
|
||||||
let owner: String = "Destination"
|
description: "Destination int key"
|
||||||
let description: String = "Destination int key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@ -85,8 +78,8 @@ struct MigrationAdditionalTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func migrationHistoryTrackingTest() async throws {
|
@Test func migrationHistoryTrackingTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.history", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.history", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernStringKey(
|
let modernKey = makeModernStringKey(
|
||||||
name: "modern.history",
|
name: "modern.history",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -95,13 +88,13 @@ struct MigrationAdditionalTests {
|
|||||||
try await router.set("history", for: legacyKey)
|
try await router.set("history", for: legacyKey)
|
||||||
_ = try await router.forceMigration(for: modernKey)
|
_ = try await router.forceMigration(for: modernKey)
|
||||||
|
|
||||||
let history = router.migrationHistory(for: modernKey)
|
let history = await router.migrationHistory(for: modernKey)
|
||||||
#expect(history != nil)
|
#expect(history != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func migrationFailureKeepsSourceTest() async throws {
|
@Test func migrationFailureKeepsSourceTest() async throws {
|
||||||
let sourceKey = SourceStringKey(name: "legacy.rollback", domain: .userDefaults(suite: nil))
|
let sourceKey = makeSourceStringKey(name: "legacy.rollback", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = DestinationIntKey(name: "modern.rollback", domain: .userDefaults(suite: nil))
|
let destinationKey = makeDestinationIntKey(name: "modern.rollback", domain: .userDefaults(suite: nil))
|
||||||
|
|
||||||
try await router.set("not-a-number", for: sourceKey)
|
try await router.set("not-a-number", for: sourceKey)
|
||||||
|
|
||||||
@ -119,8 +112,8 @@ struct MigrationAdditionalTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func watchAvailabilityBlocksMigrationTest() async throws {
|
@Test func watchAvailabilityBlocksMigrationTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.watch", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.watch", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = PhoneOnlyKey(name: "modern.watch", domain: .userDefaults(suite: nil))
|
let destinationKey = makePhoneOnlyKey(name: "modern.watch", domain: .userDefaults(suite: nil))
|
||||||
|
|
||||||
let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
|
let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
|
||||||
let deviceInfo = DeviceInfo(
|
let deviceInfo = DeviceInfo(
|
||||||
@ -136,8 +129,8 @@ struct MigrationAdditionalTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func largeDataMigrationTest() async throws {
|
@Test func largeDataMigrationTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.large", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.large", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernStringKey(
|
let modernKey = makeModernStringKey(
|
||||||
name: "modern.large",
|
name: "modern.large",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -155,8 +148,8 @@ struct MigrationAdditionalTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func typeErasureMigrationTest() async throws {
|
@Test func typeErasureMigrationTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.erased", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.erased", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernStringKey(
|
let modernKey = makeModernStringKey(
|
||||||
name: "modern.erased",
|
name: "modern.erased",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
|
|||||||
@ -2,36 +2,34 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct LegacyIntegrationKey: StorageKey {
|
private func makeLegacyIntegrationKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Legacy",
|
||||||
let owner: String = "Legacy"
|
description: "Legacy integration key"
|
||||||
let description: String = "Legacy integration key"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
private struct IntegrationCatalog: StorageKeyCatalog {
|
||||||
@ -53,8 +51,8 @@ struct MigrationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func endToEndMigrationTest() async throws {
|
@Test func endToEndMigrationTest() async throws {
|
||||||
let legacyKey = LegacyIntegrationKey(name: "legacy.integration", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyIntegrationKey(name: "legacy.integration", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernIntegrationKey(
|
let modernKey = makeModernIntegrationKey(
|
||||||
name: "modern.integration",
|
name: "modern.integration",
|
||||||
domain: .keychain(service: "test.migration"),
|
domain: .keychain(service: "test.migration"),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -69,8 +67,8 @@ struct MigrationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func migrationRegistrationTest() async throws {
|
@Test func migrationRegistrationTest() async throws {
|
||||||
let legacyKey = LegacyIntegrationKey(name: "legacy.catalog", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyIntegrationKey(name: "legacy.catalog", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernIntegrationKey(
|
let modernKey = makeModernIntegrationKey(
|
||||||
name: "modern.catalog",
|
name: "modern.catalog",
|
||||||
domain: .keychain(service: "test.migration"),
|
domain: .keychain(service: "test.migration"),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -86,8 +84,8 @@ struct MigrationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func concurrentMigrationTest() async throws {
|
@Test func concurrentMigrationTest() async throws {
|
||||||
let legacyKey = LegacyIntegrationKey(name: "legacy.concurrent", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyIntegrationKey(name: "legacy.concurrent", domain: .userDefaults(suite: nil))
|
||||||
let modernKey = ModernIntegrationKey(
|
let modernKey = makeModernIntegrationKey(
|
||||||
name: "modern.concurrent",
|
name: "modern.concurrent",
|
||||||
domain: .keychain(service: "test.migration"),
|
domain: .keychain(service: "test.migration"),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -105,7 +103,7 @@ struct MigrationIntegrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func migrationFailureResultTest() async throws {
|
@Test func migrationFailureResultTest() async throws {
|
||||||
let destinationKey = ModernIntegrationKey(
|
let destinationKey = makeModernIntegrationKey(
|
||||||
name: "modern.failure",
|
name: "modern.failure",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
legacyKey: nil
|
legacyKey: nil
|
||||||
|
|||||||
@ -2,52 +2,44 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct LegacyStringKey: StorageKey {
|
private func makeLegacyStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Legacy",
|
||||||
let owner: String = "Legacy"
|
description: "Legacy key"
|
||||||
let description: String = "Legacy key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DestinationStringKey: StorageKey {
|
private func makeDestinationStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none),
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Destination",
|
||||||
let owner: String = "Destination"
|
description: "Destination key"
|
||||||
let description: String = "Destination key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SourceStringKey: StorageKey {
|
private func makeSourceStringKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Source",
|
||||||
let owner: String = "Source"
|
description: "Source key"
|
||||||
let description: String = "Source key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DestinationIntKey: StorageKey {
|
private func makeDestinationIntKey(name: String, domain: StorageDomain) -> StorageKey<Int> {
|
||||||
typealias Value = Int
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<Int> = .json
|
owner: "Destination",
|
||||||
let owner: String = "Destination"
|
description: "Destination int key"
|
||||||
let description: String = "Destination int key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suite(.serialized)
|
@Suite(.serialized)
|
||||||
@ -65,8 +57,11 @@ struct MigrationProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func simpleLegacyMigrationTest() async throws {
|
@Test func simpleLegacyMigrationTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.simple", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.simple", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = DestinationStringKey(name: "modern.simple", domain: .keychain(service: "test.migration"))
|
let destinationKey = makeDestinationStringKey(
|
||||||
|
name: "modern.simple",
|
||||||
|
domain: .keychain(service: "test.migration")
|
||||||
|
)
|
||||||
try await router.set("value", for: legacyKey)
|
try await router.set("value", for: legacyKey)
|
||||||
|
|
||||||
let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
|
let migration = SimpleLegacyMigration(destinationKey: destinationKey, sourceKey: .key(legacyKey))
|
||||||
@ -83,8 +78,11 @@ struct MigrationProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func conditionalMigrationTest() async throws {
|
@Test func conditionalMigrationTest() async throws {
|
||||||
let legacyKey = LegacyStringKey(name: "legacy.conditional", domain: .userDefaults(suite: nil))
|
let legacyKey = makeLegacyStringKey(name: "legacy.conditional", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = DestinationStringKey(name: "modern.conditional", domain: .keychain(service: "test.migration"))
|
let destinationKey = makeDestinationStringKey(
|
||||||
|
name: "modern.conditional",
|
||||||
|
domain: .keychain(service: "test.migration")
|
||||||
|
)
|
||||||
try await router.set("value", for: legacyKey)
|
try await router.set("value", for: legacyKey)
|
||||||
|
|
||||||
let fallback = AnyStorageMigration(
|
let fallback = AnyStorageMigration(
|
||||||
@ -105,8 +103,8 @@ struct MigrationProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func transformingMigrationTest() async throws {
|
@Test func transformingMigrationTest() async throws {
|
||||||
let sourceKey = SourceStringKey(name: "legacy.transform", domain: .userDefaults(suite: nil))
|
let sourceKey = makeSourceStringKey(name: "legacy.transform", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = DestinationIntKey(name: "modern.transform", domain: .userDefaults(suite: nil))
|
let destinationKey = makeDestinationIntKey(name: "modern.transform", domain: .userDefaults(suite: nil))
|
||||||
try await router.set("42", for: sourceKey)
|
try await router.set("42", for: sourceKey)
|
||||||
|
|
||||||
let migration = DefaultTransformingMigration(
|
let migration = DefaultTransformingMigration(
|
||||||
@ -127,9 +125,9 @@ struct MigrationProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func aggregatingMigrationTest() async throws {
|
@Test func aggregatingMigrationTest() async throws {
|
||||||
let sourceKeyA = SourceStringKey(name: "legacy.aggregate.a", domain: .userDefaults(suite: nil))
|
let sourceKeyA = makeSourceStringKey(name: "legacy.aggregate.a", domain: .userDefaults(suite: nil))
|
||||||
let sourceKeyB = SourceStringKey(name: "legacy.aggregate.b", domain: .userDefaults(suite: nil))
|
let sourceKeyB = makeSourceStringKey(name: "legacy.aggregate.b", domain: .userDefaults(suite: nil))
|
||||||
let destinationKey = DestinationStringKey(name: "modern.aggregate", 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("alpha", for: sourceKeyA)
|
||||||
try await router.set("beta", for: sourceKeyB)
|
try await router.set("beta", for: sourceKeyB)
|
||||||
@ -151,7 +149,7 @@ struct MigrationProtocolTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func migrationErrorHandlingTest() async throws {
|
@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 migration = FailingMigration(destinationKey: destinationKey, error: .transformationFailed("Failed"))
|
||||||
let result = try await migration.migrate(using: router, context: MigrationContext())
|
let result = try await migration.migrate(using: router, context: MigrationContext())
|
||||||
|
|
||||||
|
|||||||
@ -2,42 +2,34 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct LegacyKey: StorageKey {
|
private func makeLegacyKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "Legacy",
|
||||||
let owner: String = "Legacy"
|
description: "Legacy key"
|
||||||
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<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)
|
@Suite(.serialized)
|
||||||
@ -65,7 +57,7 @@ struct MigrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Setup legacy data manually in UserDefaults
|
// 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)
|
try await router.set(secretValue, for: legacyKey)
|
||||||
|
|
||||||
// Verify it exists in legacy location
|
// Verify it exists in legacy location
|
||||||
@ -73,7 +65,7 @@ struct MigrationTests {
|
|||||||
#expect(existsInLegacy == true)
|
#expect(existsInLegacy == true)
|
||||||
|
|
||||||
// 2. Setup modern key with legacy source
|
// 2. Setup modern key with legacy source
|
||||||
let modernKey = ModernKey(
|
let modernKey = makeModernKey(
|
||||||
name: modernName,
|
name: modernName,
|
||||||
domain: .keychain(service: "test.migration"),
|
domain: .keychain(service: "test.migration"),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
@ -104,11 +96,11 @@ struct MigrationTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Setup legacy data
|
// 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)
|
try await router.set(value, for: legacyKey)
|
||||||
|
|
||||||
// 2. Setup modern key
|
// 2. Setup modern key
|
||||||
let modernKey = ModernKey(
|
let modernKey = makeModernKey(
|
||||||
name: modernName,
|
name: modernName,
|
||||||
domain: .userDefaults(suite: suiteName),
|
domain: .userDefaults(suite: suiteName),
|
||||||
legacyKey: .key(legacyKey)
|
legacyKey: .key(legacyKey)
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
struct MockMigration<Destination: StorageKey>: StorageMigration {
|
struct MockMigration<Value: Codable & Sendable>: StorageMigration {
|
||||||
typealias DestinationKey = Destination
|
let destinationKey: StorageKey<Value>
|
||||||
|
|
||||||
let destinationKey: Destination
|
|
||||||
let shouldSucceed: Bool
|
let shouldSucceed: Bool
|
||||||
let shouldMigrateResult: Bool
|
let shouldMigrateResult: Bool
|
||||||
let migrationDelay: TimeInterval
|
let migrationDelay: TimeInterval
|
||||||
@ -28,10 +26,8 @@ struct MockMigration<Destination: StorageKey>: StorageMigration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FailingMigration<Destination: StorageKey>: StorageMigration {
|
struct FailingMigration<Value: Codable & Sendable>: StorageMigration {
|
||||||
typealias DestinationKey = Destination
|
let destinationKey: StorageKey<Value>
|
||||||
|
|
||||||
let destinationKey: Destination
|
|
||||||
let error: MigrationError
|
let error: MigrationError
|
||||||
|
|
||||||
func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool { true }
|
func shouldMigrate(using router: StorageRouter, context: MigrationContext) async throws -> Bool { true }
|
||||||
|
|||||||
@ -2,39 +2,35 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct TestRegistryKey: StorageKey {
|
private func makeRegistryKey(
|
||||||
typealias Value = String
|
name: String,
|
||||||
let name: String
|
owner: String = "Test",
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
description: String = "Test"
|
||||||
let security: SecurityPolicy = .none
|
) -> StorageKey<String> {
|
||||||
let serializer: Serializer<String> = .json
|
StorageKey(
|
||||||
let owner: String
|
name: name,
|
||||||
let description: String
|
domain: .userDefaults(suite: nil),
|
||||||
let availability: PlatformAvailability = .all
|
security: .none,
|
||||||
let syncPolicy: SyncPolicy = .never
|
owner: owner,
|
||||||
|
description: description
|
||||||
init(name: String, owner: String = "Test", description: String = "Test") {
|
)
|
||||||
self.name = name
|
|
||||||
self.owner = owner
|
|
||||||
self.description = description
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CatalogA: StorageKeyCatalog {
|
private struct CatalogA: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[.key(TestRegistryKey(name: "key.a", owner: "ModuleA"))]
|
[.key(makeRegistryKey(name: "key.a", owner: "ModuleA"))]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CatalogB: StorageKeyCatalog {
|
private struct CatalogB: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[.key(TestRegistryKey(name: "key.b", owner: "ModuleB"))]
|
[.key(makeRegistryKey(name: "key.b", owner: "ModuleB"))]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CatalogCollision: StorageKeyCatalog {
|
private struct CatalogCollision: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[.key(TestRegistryKey(name: "key.a", owner: "ModuleCollision"))]
|
[.key(makeRegistryKey(name: "key.a", owner: "ModuleCollision"))]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,26 +16,22 @@ import Testing
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct DomainKey: StorageKey {
|
private func makeDomainKey(
|
||||||
typealias Value = String
|
name: String,
|
||||||
let name: String
|
domain: StorageDomain,
|
||||||
let domain: StorageDomain
|
security: SecurityPolicy = .none
|
||||||
let security: SecurityPolicy
|
) -> StorageKey<String> {
|
||||||
let serializer: Serializer<String> = .json
|
StorageKey(
|
||||||
let owner: String = "DomainTests"
|
name: name,
|
||||||
let description: String = "Domain test key"
|
domain: domain,
|
||||||
let availability: PlatformAvailability = .all
|
security: security,
|
||||||
let syncPolicy: SyncPolicy = .never
|
owner: "DomainTests",
|
||||||
|
description: "Domain test key"
|
||||||
init(name: String, domain: StorageDomain, security: SecurityPolicy = .none) {
|
)
|
||||||
self.name = name
|
|
||||||
self.domain = domain
|
|
||||||
self.security = security
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func domainUserDefaults() async throws {
|
@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)
|
try await router.set("value", for: key)
|
||||||
#expect(try await router.get(key) == "value")
|
#expect(try await router.get(key) == "value")
|
||||||
try await router.remove(key)
|
try await router.remove(key)
|
||||||
@ -46,14 +42,14 @@ import Testing
|
|||||||
// We use a mock configuration to avoid requiring a real app group
|
// We use a mock configuration to avoid requiring a real app group
|
||||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
|
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)
|
try await router.set("value", for: key)
|
||||||
#expect(try await router.get(key) == "value")
|
#expect(try await router.get(key) == "value")
|
||||||
try await router.remove(key)
|
try await router.remove(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func domainKeychain() async throws {
|
@Test func domainKeychain() async throws {
|
||||||
let key = DomainKey(
|
let key = makeDomainKey(
|
||||||
name: "keychain.key",
|
name: "keychain.key",
|
||||||
domain: .keychain(service: "test"),
|
domain: .keychain(service: "test"),
|
||||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||||
@ -64,14 +60,14 @@ import Testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func domainFileSystem() async throws {
|
@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)
|
try await router.set("value", for: key)
|
||||||
#expect(try await router.get(key) == "value")
|
#expect(try await router.get(key) == "value")
|
||||||
try await router.remove(key)
|
try await router.remove(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func domainEncryptedFileSystem() async throws {
|
@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)
|
try await router.set("value", for: key)
|
||||||
#expect(try await router.get(key) == "value")
|
#expect(try await router.get(key) == "value")
|
||||||
try await router.remove(key)
|
try await router.remove(key)
|
||||||
@ -80,7 +76,10 @@ import Testing
|
|||||||
@Test func domainAppGroupFileSystem() async throws {
|
@Test func domainAppGroupFileSystem() async throws {
|
||||||
// App blocks usually fail or return nil in tests, but we exercise the path
|
// App blocks usually fail or return nil in tests, but we exercise the path
|
||||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: "group.test"))
|
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 {
|
do {
|
||||||
try await router.set("value", for: key)
|
try await router.set("value", for: key)
|
||||||
@ -94,7 +93,7 @@ import Testing
|
|||||||
@Test func resolutionFailureService() async throws {
|
@Test func resolutionFailureService() async throws {
|
||||||
// Clear default service
|
// Clear default service
|
||||||
await router.updateStorageConfiguration(StorageConfiguration(defaultKeychainService: nil))
|
await router.updateStorageConfiguration(StorageConfiguration(defaultKeychainService: nil))
|
||||||
let key = DomainKey(
|
let key = makeDomainKey(
|
||||||
name: "bad.service.key",
|
name: "bad.service.key",
|
||||||
domain: .keychain(service: nil),
|
domain: .keychain(service: nil),
|
||||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||||
@ -108,7 +107,7 @@ import Testing
|
|||||||
@Test func resolutionFailureIdentifier() async throws {
|
@Test func resolutionFailureIdentifier() async throws {
|
||||||
// Clear default identifier
|
// Clear default identifier
|
||||||
await router.updateStorageConfiguration(StorageConfiguration(defaultAppGroupIdentifier: nil))
|
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")) {
|
await #expect(throws: StorageError.invalidAppGroupIdentifier("none")) {
|
||||||
try await router.set("value", for: key)
|
try await router.set("value", for: key)
|
||||||
|
|||||||
@ -2,21 +2,19 @@ import Foundation
|
|||||||
import Testing
|
import Testing
|
||||||
@testable import LocalData
|
@testable import LocalData
|
||||||
|
|
||||||
private struct MockKey: StorageKey {
|
private func makeMockKey(name: String, domain: StorageDomain) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain
|
domain: domain,
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "ErrorTests",
|
||||||
let owner: String = "ErrorTests"
|
description: "Test key"
|
||||||
let description: String = "Test key"
|
)
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct PartialCatalog: StorageKeyCatalog {
|
private struct PartialCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
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 {
|
@Test func unregisteredKeyThrows() async throws {
|
||||||
try await router.registerCatalog(PartialCatalog())
|
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")) {
|
await #expect(throws: StorageError.unregisteredKey("unregistered.key")) {
|
||||||
try await router.set("value", for: badKey)
|
try await router.set("value", for: badKey)
|
||||||
@ -51,7 +49,7 @@ struct RouterErrorTests {
|
|||||||
defaultAppGroupIdentifier: nil
|
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")) {
|
await #expect(throws: StorageError.invalidAppGroupIdentifier("none")) {
|
||||||
try await router.set("value", for: appGroupKey)
|
try await router.set("value", for: appGroupKey)
|
||||||
@ -65,7 +63,7 @@ struct RouterErrorTests {
|
|||||||
defaultAppGroupIdentifier: "test"
|
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
|
// Note: Keychain security policy must match keychain domain in descriptor
|
||||||
// but descriptor is usually created from key.
|
// but descriptor is usually created from key.
|
||||||
|
|||||||
@ -17,20 +17,22 @@ import Security
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SecurityKey: StorageKey {
|
private func makeSecurityKey(
|
||||||
typealias Value = String
|
name: String,
|
||||||
let name: String
|
domain: StorageDomain,
|
||||||
let domain: StorageDomain
|
security: SecurityPolicy
|
||||||
let security: SecurityPolicy
|
) -> StorageKey<String> {
|
||||||
let serializer: Serializer<String> = .json
|
StorageKey(
|
||||||
let owner: String = "SecurityTests"
|
name: name,
|
||||||
let description: String = "Security test key"
|
domain: domain,
|
||||||
let availability: PlatformAvailability = .all
|
security: security,
|
||||||
let syncPolicy: SyncPolicy = .never
|
owner: "SecurityTests",
|
||||||
|
description: "Security test key"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test func applySecurityNone() async throws {
|
@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"
|
let value = "test-value"
|
||||||
|
|
||||||
try await router.set(value, for: key)
|
try await router.set(value, for: key)
|
||||||
@ -39,7 +41,7 @@ import Security
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func applySecurityEncryptedAES() async throws {
|
@Test func applySecurityEncryptedAES() async throws {
|
||||||
let key = SecurityKey(
|
let key = makeSecurityKey(
|
||||||
name: "aes.key",
|
name: "aes.key",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
security: .encrypted(.aes256(keyDerivation: .hkdf()))
|
security: .encrypted(.aes256(keyDerivation: .hkdf()))
|
||||||
@ -52,7 +54,7 @@ import Security
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func applySecurityEncryptedChaCha() async throws {
|
@Test func applySecurityEncryptedChaCha() async throws {
|
||||||
let key = SecurityKey(
|
let key = makeSecurityKey(
|
||||||
name: "chacha.key",
|
name: "chacha.key",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
security: .encrypted(.chacha20Poly1305(keyDerivation: .hkdf()))
|
security: .encrypted(.chacha20Poly1305(keyDerivation: .hkdf()))
|
||||||
@ -65,7 +67,7 @@ import Security
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func applySecurityKeychain() async throws {
|
@Test func applySecurityKeychain() async throws {
|
||||||
let key = SecurityKey(
|
let key = makeSecurityKey(
|
||||||
name: "keychain.key",
|
name: "keychain.key",
|
||||||
domain: .keychain(service: "test-service"),
|
domain: .keychain(service: "test-service"),
|
||||||
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
security: .keychain(accessibility: .afterFirstUnlock, accessControl: .none)
|
||||||
@ -78,7 +80,7 @@ import Security
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func applySecurityPBKDF2() async throws {
|
@Test func applySecurityPBKDF2() async throws {
|
||||||
let key = SecurityKey(
|
let key = makeSecurityKey(
|
||||||
name: "pbkdf2.key",
|
name: "pbkdf2.key",
|
||||||
domain: .userDefaults(suite: nil),
|
domain: .userDefaults(suite: nil),
|
||||||
security: .encrypted(.aes256(keyDerivation: .pbkdf2()))
|
security: .encrypted(.aes256(keyDerivation: .pbkdf2()))
|
||||||
|
|||||||
@ -4,22 +4,14 @@ import Testing
|
|||||||
|
|
||||||
// MARK: - Test Keys
|
// MARK: - Test Keys
|
||||||
|
|
||||||
private struct TestCatalogKey: StorageKey {
|
private func makeCatalogKey(name: String, description: String = "Test key") -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
|
name: name,
|
||||||
let name: String
|
domain: .userDefaults(suite: nil),
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
security: .none,
|
||||||
let security: SecurityPolicy = .none
|
owner: "CatalogTests",
|
||||||
let serializer: Serializer<String> = .json
|
description: description
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Test Catalogs
|
// MARK: - Test Catalogs
|
||||||
@ -27,8 +19,8 @@ private struct TestCatalogKey: StorageKey {
|
|||||||
private struct ValidCatalog: StorageKeyCatalog {
|
private struct ValidCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[
|
[
|
||||||
.key(TestCatalogKey(name: "valid.key1", description: "First test key")),
|
.key(makeCatalogKey(name: "valid.key1", description: "First test key")),
|
||||||
.key(TestCatalogKey(name: "valid.key2", description: "Second test key"))
|
.key(makeCatalogKey(name: "valid.key2", description: "Second test key"))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,8 +28,8 @@ private struct ValidCatalog: StorageKeyCatalog {
|
|||||||
private struct DuplicateNameCatalog: StorageKeyCatalog {
|
private struct DuplicateNameCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[
|
[
|
||||||
.key(TestCatalogKey(name: "duplicate.name", description: "First instance")),
|
.key(makeCatalogKey(name: "duplicate.name", description: "First instance")),
|
||||||
.key(TestCatalogKey(name: "duplicate.name", description: "Second instance"))
|
.key(makeCatalogKey(name: "duplicate.name", description: "Second instance"))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,7 +41,7 @@ private struct EmptyCatalog: StorageKeyCatalog {
|
|||||||
private struct MissingDescriptionCatalog: StorageKeyCatalog {
|
private struct MissingDescriptionCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
var allKeys: [AnyStorageKey] {
|
||||||
[
|
[
|
||||||
.key(TestCatalogKey(name: "missing.desc", description: " "))
|
.key(makeCatalogKey(name: "missing.desc", description: " "))
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +70,7 @@ struct StorageCatalogTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test func descriptorCapturesKeyMetadata() {
|
@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 anyKey = AnyStorageKey.key(key)
|
||||||
let descriptor = anyKey.descriptor
|
let descriptor = anyKey.descriptor
|
||||||
|
|
||||||
|
|||||||
@ -4,19 +4,13 @@ import Testing
|
|||||||
|
|
||||||
@Suite struct StorageKeyDefaultsTests {
|
@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() {
|
@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
|
// This exercises the default implementation in StorageKey+Defaults.swift
|
||||||
#expect(key.security == .recommended)
|
#expect(key.security == .recommended)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,21 +16,20 @@ struct SyncIntegrationTests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SyncKey: StorageKey {
|
private static func makeSyncKey(name: String) -> StorageKey<String> {
|
||||||
typealias Value = String
|
StorageKey(
|
||||||
let name: String
|
name: name,
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
domain: .userDefaults(suite: nil),
|
||||||
let security: SecurityPolicy = .none
|
security: .none,
|
||||||
let serializer: Serializer<String> = .json
|
owner: "SyncTests",
|
||||||
let owner: String = "SyncTests"
|
description: "Sync key",
|
||||||
let description: String = "Sync key"
|
syncPolicy: .automaticSmall
|
||||||
let availability: PlatformAvailability = .all
|
)
|
||||||
let syncPolicy: SyncPolicy = .automaticSmall
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct SyncCatalog: StorageKeyCatalog {
|
private struct SyncCatalog: StorageKeyCatalog {
|
||||||
var allKeys: [AnyStorageKey] {
|
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)
|
try await router.updateFromSync(keyName: keyName, data: data)
|
||||||
|
|
||||||
// 3. Verify it was stored in the local domain
|
// 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)
|
#expect(retrieved == expectedValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"configurations" : [
|
"configurations" : [
|
||||||
{
|
{
|
||||||
"id" : "D2951487-F388-4A07-A1E8-3A4B179619B9",
|
"id" : "CB38B4BA-86AE-457E-B74C-31A492DEB330",
|
||||||
"name" : "Configuration 1",
|
"name" : "Configuration 1",
|
||||||
"options" : {
|
"options" : {
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user