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 = 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 }