Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
ce601b2bb6
commit
38cd3e8318
143
Tests/LocalDataTests/KeychainHelperTests.swift
Normal file
143
Tests/LocalDataTests/KeychainHelperTests.swift
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
@testable import LocalData
|
||||||
|
|
||||||
|
struct KeychainHelperTests {
|
||||||
|
private let testService = "LocalDataTests.Keychain.\(UUID().uuidString)"
|
||||||
|
|
||||||
|
// MARK: - Basic Round Trip
|
||||||
|
|
||||||
|
@Test func keychainRoundTrip() async throws {
|
||||||
|
let key = "test.credential"
|
||||||
|
let data = Data("secret-password".utf8)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
try? KeychainHelper.shared.delete(service: testService, key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
data,
|
||||||
|
service: testService,
|
||||||
|
key: key,
|
||||||
|
accessibility: .afterFirstUnlock
|
||||||
|
)
|
||||||
|
|
||||||
|
let retrieved = try await KeychainHelper.shared.get(service: testService, key: key)
|
||||||
|
#expect(retrieved == data)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func keychainNotFoundReturnsNil() async throws {
|
||||||
|
let result = try await KeychainHelper.shared.get(
|
||||||
|
service: testService,
|
||||||
|
key: "nonexistent.\(UUID().uuidString)"
|
||||||
|
)
|
||||||
|
#expect(result == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func keychainDeleteNonexistentDoesNotThrow() async throws {
|
||||||
|
// Should not throw even if item doesn't exist
|
||||||
|
try await KeychainHelper.shared.delete(
|
||||||
|
service: testService,
|
||||||
|
key: "nonexistent.\(UUID().uuidString)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func keychainExists() async throws {
|
||||||
|
let key = "test.exists.\(UUID().uuidString)"
|
||||||
|
let data = Data("test".utf8)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
try? KeychainHelper.shared.delete(service: testService, key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
let beforeExists = try await KeychainHelper.shared.exists(service: testService, key: key)
|
||||||
|
#expect(beforeExists == false)
|
||||||
|
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
data,
|
||||||
|
service: testService,
|
||||||
|
key: key,
|
||||||
|
accessibility: .whenUnlocked
|
||||||
|
)
|
||||||
|
|
||||||
|
let afterExists = try await KeychainHelper.shared.exists(service: testService, key: key)
|
||||||
|
#expect(afterExists == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func keychainUpdateReplacesData() async throws {
|
||||||
|
let key = "test.update.\(UUID().uuidString)"
|
||||||
|
let originalData = Data("original".utf8)
|
||||||
|
let updatedData = Data("updated".utf8)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
try? KeychainHelper.shared.delete(service: testService, key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
originalData,
|
||||||
|
service: testService,
|
||||||
|
key: key,
|
||||||
|
accessibility: .afterFirstUnlock
|
||||||
|
)
|
||||||
|
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
updatedData,
|
||||||
|
service: testService,
|
||||||
|
key: key,
|
||||||
|
accessibility: .afterFirstUnlock
|
||||||
|
)
|
||||||
|
|
||||||
|
let retrieved = try await KeychainHelper.shared.get(service: testService, key: key)
|
||||||
|
#expect(retrieved == updatedData)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func keychainDeleteAll() async throws {
|
||||||
|
let deleteAllService = "LocalDataTests.DeleteAll.\(UUID().uuidString)"
|
||||||
|
let data = Data("test".utf8)
|
||||||
|
|
||||||
|
// Create multiple items
|
||||||
|
for i in 0..<3 {
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
data,
|
||||||
|
service: deleteAllService,
|
||||||
|
key: "key\(i)",
|
||||||
|
accessibility: .afterFirstUnlock
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify they exist
|
||||||
|
let exists0 = try await KeychainHelper.shared.exists(service: deleteAllService, key: "key0")
|
||||||
|
#expect(exists0 == true)
|
||||||
|
|
||||||
|
// Delete all
|
||||||
|
try await KeychainHelper.shared.deleteAll(service: deleteAllService)
|
||||||
|
|
||||||
|
// Verify they're gone
|
||||||
|
for i in 0..<3 {
|
||||||
|
let exists = try await KeychainHelper.shared.exists(service: deleteAllService, key: "key\(i)")
|
||||||
|
#expect(exists == false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Accessibility Options
|
||||||
|
|
||||||
|
@Test(arguments: KeychainAccessibility.allCases)
|
||||||
|
func keychainAccessibilityOptions(accessibility: KeychainAccessibility) async throws {
|
||||||
|
let key = "test.accessibility.\(accessibility).\(UUID().uuidString)"
|
||||||
|
let data = Data("data-for-\(accessibility)".utf8)
|
||||||
|
|
||||||
|
defer {
|
||||||
|
try? KeychainHelper.shared.delete(service: testService, key: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
try await KeychainHelper.shared.set(
|
||||||
|
data,
|
||||||
|
service: testService,
|
||||||
|
key: key,
|
||||||
|
accessibility: accessibility
|
||||||
|
)
|
||||||
|
|
||||||
|
let retrieved = try await KeychainHelper.shared.get(service: testService, key: key)
|
||||||
|
#expect(retrieved == data)
|
||||||
|
}
|
||||||
|
}
|
||||||
95
Tests/LocalDataTests/StorageCatalogTests.swift
Normal file
95
Tests/LocalDataTests/StorageCatalogTests.swift
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
import Foundation
|
||||||
|
import Testing
|
||||||
|
@testable import LocalData
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Test Catalogs
|
||||||
|
|
||||||
|
private struct ValidCatalog: StorageKeyCatalog {
|
||||||
|
static var allKeys: [AnyStorageKey] {
|
||||||
|
[
|
||||||
|
.key(TestCatalogKey(name: "valid.key1", description: "First test key")),
|
||||||
|
.key(TestCatalogKey(name: "valid.key2", description: "Second test key"))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct DuplicateNameCatalog: StorageKeyCatalog {
|
||||||
|
static var allKeys: [AnyStorageKey] {
|
||||||
|
[
|
||||||
|
.key(TestCatalogKey(name: "duplicate.name", description: "First instance")),
|
||||||
|
.key(TestCatalogKey(name: "duplicate.name", description: "Second instance"))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct EmptyCatalog: StorageKeyCatalog {
|
||||||
|
static var allKeys: [AnyStorageKey] { [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Tests
|
||||||
|
|
||||||
|
struct StorageCatalogTests {
|
||||||
|
|
||||||
|
@Test func auditReportContainsAllKeys() {
|
||||||
|
let items = StorageAuditReport.items(for: ValidCatalog.self)
|
||||||
|
|
||||||
|
#expect(items.count == 2)
|
||||||
|
#expect(items[0].name == "valid.key1")
|
||||||
|
#expect(items[1].name == "valid.key2")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func auditReportRendersText() {
|
||||||
|
let report = StorageAuditReport.renderText(for: ValidCatalog.self)
|
||||||
|
|
||||||
|
#expect(report.contains("valid.key1"))
|
||||||
|
#expect(report.contains("valid.key2"))
|
||||||
|
#expect(report.contains("First test key"))
|
||||||
|
#expect(report.contains("Second test key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func descriptorCapturesKeyMetadata() {
|
||||||
|
let key = TestCatalogKey(name: "metadata.test", description: "Metadata test key")
|
||||||
|
let anyKey = AnyStorageKey.key(key)
|
||||||
|
let descriptor = anyKey.descriptor
|
||||||
|
|
||||||
|
#expect(descriptor.name == "metadata.test")
|
||||||
|
#expect(descriptor.owner == "CatalogTests")
|
||||||
|
#expect(descriptor.description == "Metadata test key")
|
||||||
|
#expect(descriptor.valueType == "String")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func emptyReportForEmptyCatalog() {
|
||||||
|
let items = StorageAuditReport.items(for: EmptyCatalog.self)
|
||||||
|
#expect(items.isEmpty)
|
||||||
|
|
||||||
|
let report = StorageAuditReport.renderText(for: EmptyCatalog.self)
|
||||||
|
#expect(report.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func catalogRegistrationDetectsDuplicates() async {
|
||||||
|
// Attempting to register a catalog with duplicate key names should throw
|
||||||
|
await #expect(throws: StorageError.self) {
|
||||||
|
try await StorageRouter.shared.registerCatalog(DuplicateNameCatalog.self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user