import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)struct KeychainCacheStoreTests { struct TestEntry: Codable, Equatable { let value: String let storedAt: Date } @Test func `tests suppress real keychain access by default`() { guard ProcessInfo.processInfo.environment["CODEXBAR_ALLOW_TEST_KEYCHAIN_ACCESS"] != "1" else { return } #expect(KeychainCacheStore.canUseRealKeychainForTesting == false) let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let entry = TestEntry(value: "implicit", storedAt: Date(timeIntervalSince1970: 0)) KeychainCacheStore.store(key: key, entry: entry) defer { KeychainCacheStore.clear(key: key) } switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case let .found(loaded): #expect(loaded == entry) case .missing, .temporarilyUnavailable, .invalid: #expect(Bool(false), "Expected implicit test cache entry") } } @Test func `background interaction keeps real keychain cache available for no UI reads writes and deletes`() { KeychainAccessGate.withTaskOverrideForTesting(false) { ProviderInteractionContext.$current.withValue(.background) { #expect(KeychainCacheStore.canUseRealKeychainForTesting == true) #expect(KeychainCacheStore.canEnumerateOrDeleteRealKeychainForTesting == true) } } } @Test func `stores and loads entry`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let storedAt = Date(timeIntervalSince1970: 0) let entry = TestEntry(value: "alpha", storedAt: storedAt) KeychainCacheStore.store(key: key, entry: entry) defer { KeychainCacheStore.clear(key: key) } switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case let .found(loaded): #expect(loaded == entry) case .missing, .temporarilyUnavailable, .invalid: #expect(Bool(false), "Expected keychain cache entry") } } @Test func `overwrites existing entry`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let first = TestEntry(value: "first", storedAt: Date(timeIntervalSince1970: 1)) let second = TestEntry(value: "second", storedAt: Date(timeIntervalSince1970: 2)) KeychainCacheStore.store(key: key, entry: first) KeychainCacheStore.store(key: key, entry: second) defer { KeychainCacheStore.clear(key: key) } switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case let .found(loaded): #expect(loaded == second) case .missing, .temporarilyUnavailable, .invalid: #expect(Bool(false), "Expected overwritten keychain cache entry") } } @Test func `clear removes entry`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let entry = TestEntry(value: "gone", storedAt: Date(timeIntervalSince1970: 0)) KeychainCacheStore.store(key: key, entry: entry) KeychainCacheStore.clear(key: key) switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case .missing: #expect(true) case .found, .temporarilyUnavailable, .invalid: #expect(Bool(false), "Expected keychain cache entry to be cleared") } } @Test func `clear reports whether an entry was removed`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let entry = TestEntry(value: "gone", storedAt: Date(timeIntervalSince1970: 0)) KeychainCacheStore.store(key: key, entry: entry) #expect(KeychainCacheStore.clear(key: key) == true) #expect(KeychainCacheStore.clear(key: key) == false) } @Test func `keys lists only matching category for current service`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let serviceA = "cache-keys-a-\(UUID().uuidString)" let serviceB = "cache-keys-b-\(UUID().uuidString)" let cookieA = KeychainCacheStore.Key(category: "cookie", identifier: "codex") let scopedCookieA = KeychainCacheStore.Key(category: "cookie", identifier: "codex.managed.account") let oauthA = KeychainCacheStore.Key(category: "oauth", identifier: "codex") let cookieB = KeychainCacheStore.Key(category: "cookie", identifier: "claude") let entry = TestEntry(value: "value", storedAt: Date(timeIntervalSince1970: 0)) KeychainCacheStore.withServiceOverrideForTesting(serviceA) { KeychainCacheStore.store(key: cookieA, entry: entry) KeychainCacheStore.store(key: scopedCookieA, entry: entry) KeychainCacheStore.store(key: oauthA, entry: entry) } KeychainCacheStore.withServiceOverrideForTesting(serviceB) { KeychainCacheStore.store(key: cookieB, entry: entry) } let keys = KeychainCacheStore.withServiceOverrideForTesting(serviceA) { KeychainCacheStore.keys(category: "cookie") } #expect(keys == [cookieA, scopedCookieA]) } #if os(macOS) @Test func `interaction not allowed is treated as temporarily unavailable`() { let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let result: KeychainCacheStore.LoadResult<TestEntry> = KeychainCacheStore.loadResultForKeychainReadFailure( status: errSecInteractionNotAllowed, key: key) switch result { case .temporarilyUnavailable: #expect(true) case .found, .missing, .invalid: #expect(Bool(false), "Expected temporary keychain lock to be retry-later") } } @Test func `delete interaction not allowed is non fatal`() { let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) #expect(KeychainCacheStore.clearResultForKeychainDeleteStatus( errSecInteractionNotAllowed, key: key) == .failed) } @Test func `load failure override bypasses test store without affecting store or clear`() { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString) let entry = TestEntry(value: "stored", storedAt: Date(timeIntervalSince1970: 0)) KeychainCacheStore.store(key: key, entry: entry) defer { KeychainCacheStore.clear(key: key) } KeychainCacheStore.withLoadFailureStatusOverrideForTesting(errSecInteractionNotAllowed) { switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case .temporarilyUnavailable: #expect(true) case .found, .missing, .invalid: #expect(Bool(false), "Expected override to run before test store") } } switch KeychainCacheStore.load(key: key, as: TestEntry.self) { case let .found(loaded): #expect(loaded == entry) case .missing, .temporarilyUnavailable, .invalid: #expect(Bool(false), "Expected override not to mutate test store") } } @Test func `cache ACL trusts bundled app and CLI helper`() { let root = URL(fileURLWithPath: "/Applications/CodexBar.app") let executable = root.appendingPathComponent("Contents/MacOS/CodexBar") let helper = root.appendingPathComponent("Contents/Helpers/CodexBarCLI") let existing = Set([ root.path, executable.path, helper.path, ]) let paths = KeychainCacheStore.trustedApplicationPathsForCacheAccess( bundleURL: root, executableURL: executable, fileExists: { existing.contains($0) }) #expect(paths == [ root.path, helper.path, executable.path, ]) } #endif}
import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct KeychainCacheStoreTests {
struct TestEntry: Codable, Equatable {
let value: String
let storedAt: Date
}
@Test
func `tests suppress real keychain access by default`() {
guard ProcessInfo.processInfo.environment["CODEXBAR_ALLOW_TEST_KEYCHAIN_ACCESS"] != "1" else { return }
#expect(KeychainCacheStore.canUseRealKeychainForTesting == false)
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let entry = TestEntry(value: "implicit", storedAt: Date(timeIntervalSince1970: 0))
KeychainCacheStore.store(key: key, entry: entry)
defer { KeychainCacheStore.clear(key: key) }
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case let .found(loaded):
#expect(loaded == entry)
case .missing, .temporarilyUnavailable, .invalid:
#expect(Bool(false), "Expected implicit test cache entry")
}
}
@Test
func `background interaction keeps real keychain cache available for no UI reads writes and deletes`() {
KeychainAccessGate.withTaskOverrideForTesting(false) {
ProviderInteractionContext.$current.withValue(.background) {
#expect(KeychainCacheStore.canUseRealKeychainForTesting == true)
#expect(KeychainCacheStore.canEnumerateOrDeleteRealKeychainForTesting == true)
}
}
}
@Test
func `stores and loads entry`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let storedAt = Date(timeIntervalSince1970: 0)
let entry = TestEntry(value: "alpha", storedAt: storedAt)
KeychainCacheStore.store(key: key, entry: entry)
defer { KeychainCacheStore.clear(key: key) }
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case let .found(loaded):
#expect(loaded == entry)
case .missing, .temporarilyUnavailable, .invalid:
#expect(Bool(false), "Expected keychain cache entry")
}
}
@Test
func `overwrites existing entry`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let first = TestEntry(value: "first", storedAt: Date(timeIntervalSince1970: 1))
let second = TestEntry(value: "second", storedAt: Date(timeIntervalSince1970: 2))
KeychainCacheStore.store(key: key, entry: first)
KeychainCacheStore.store(key: key, entry: second)
defer { KeychainCacheStore.clear(key: key) }
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case let .found(loaded):
#expect(loaded == second)
case .missing, .temporarilyUnavailable, .invalid:
#expect(Bool(false), "Expected overwritten keychain cache entry")
}
}
@Test
func `clear removes entry`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let entry = TestEntry(value: "gone", storedAt: Date(timeIntervalSince1970: 0))
KeychainCacheStore.store(key: key, entry: entry)
KeychainCacheStore.clear(key: key)
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case .missing:
#expect(true)
case .found, .temporarilyUnavailable, .invalid:
#expect(Bool(false), "Expected keychain cache entry to be cleared")
}
}
@Test
func `clear reports whether an entry was removed`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let entry = TestEntry(value: "gone", storedAt: Date(timeIntervalSince1970: 0))
KeychainCacheStore.store(key: key, entry: entry)
#expect(KeychainCacheStore.clear(key: key) == true)
#expect(KeychainCacheStore.clear(key: key) == false)
}
@Test
func `keys lists only matching category for current service`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let serviceA = "cache-keys-a-\(UUID().uuidString)"
let serviceB = "cache-keys-b-\(UUID().uuidString)"
let cookieA = KeychainCacheStore.Key(category: "cookie", identifier: "codex")
let scopedCookieA = KeychainCacheStore.Key(category: "cookie", identifier: "codex.managed.account")
let oauthA = KeychainCacheStore.Key(category: "oauth", identifier: "codex")
let cookieB = KeychainCacheStore.Key(category: "cookie", identifier: "claude")
let entry = TestEntry(value: "value", storedAt: Date(timeIntervalSince1970: 0))
KeychainCacheStore.withServiceOverrideForTesting(serviceA) {
KeychainCacheStore.store(key: cookieA, entry: entry)
KeychainCacheStore.store(key: scopedCookieA, entry: entry)
KeychainCacheStore.store(key: oauthA, entry: entry)
}
KeychainCacheStore.withServiceOverrideForTesting(serviceB) {
KeychainCacheStore.store(key: cookieB, entry: entry)
}
let keys = KeychainCacheStore.withServiceOverrideForTesting(serviceA) {
KeychainCacheStore.keys(category: "cookie")
}
#expect(keys == [cookieA, scopedCookieA])
}
#if os(macOS)
@Test
func `interaction not allowed is treated as temporarily unavailable`() {
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let result: KeychainCacheStore.LoadResult<TestEntry> = KeychainCacheStore.loadResultForKeychainReadFailure(
status: errSecInteractionNotAllowed,
key: key)
switch result {
case .temporarilyUnavailable:
#expect(true)
case .found, .missing, .invalid:
#expect(Bool(false), "Expected temporary keychain lock to be retry-later")
}
}
@Test
func `delete interaction not allowed is non fatal`() {
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
#expect(KeychainCacheStore.clearResultForKeychainDeleteStatus(
errSecInteractionNotAllowed,
key: key) == .failed)
}
@Test
func `load failure override bypasses test store without affecting store or clear`() {
KeychainCacheStore.setTestStoreForTesting(true)
defer { KeychainCacheStore.setTestStoreForTesting(false) }
let key = KeychainCacheStore.Key(category: "test", identifier: UUID().uuidString)
let entry = TestEntry(value: "stored", storedAt: Date(timeIntervalSince1970: 0))
KeychainCacheStore.store(key: key, entry: entry)
defer { KeychainCacheStore.clear(key: key) }
KeychainCacheStore.withLoadFailureStatusOverrideForTesting(errSecInteractionNotAllowed) {
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case .temporarilyUnavailable:
#expect(true)
case .found, .missing, .invalid:
#expect(Bool(false), "Expected override to run before test store")
}
}
switch KeychainCacheStore.load(key: key, as: TestEntry.self) {
case let .found(loaded):
#expect(loaded == entry)
case .missing, .temporarilyUnavailable, .invalid:
#expect(Bool(false), "Expected override not to mutate test store")
}
}
@Test
func `cache ACL trusts bundled app and CLI helper`() {
let root = URL(fileURLWithPath: "/Applications/CodexBar.app")
let executable = root.appendingPathComponent("Contents/MacOS/CodexBar")
let helper = root.appendingPathComponent("Contents/Helpers/CodexBarCLI")
let existing = Set([
root.path,
executable.path,
helper.path,
])
let paths = KeychainCacheStore.trustedApplicationPathsForCacheAccess(
bundleURL: root,
executableURL: executable,
fileExists: { existing.contains($0) })
#expect(paths == [
root.path,
helper.path,
executable.path,
])
}
#endif
}