import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)struct ClaudeOAuthCredentialsStoreSecurityCLITests { private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data { let millis = Int(expiresAt.timeIntervalSince1970 * 1000) let refreshField: String = { guard let refreshToken else { return "" } return ",\n \"refreshToken\": \"\(refreshToken)\"" }() let json = """ { "claudeAiOauth": { "accessToken": "\(accessToken)", "expiresAt": \(millis), "scopes": ["user:profile"]\(refreshField) } } """ return Data(json.utf8) } @Test func `experimental reader prefers security CLI for non interactive load`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData( accessToken: "security-token", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh") let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } }) }) #expect(creds.accessToken == "security-token") #expect(creds.refreshToken == "security-refresh") #expect(creds.scopes.contains("user:profile")) } } } } @Test func `experimental reader non interactive background load still executes security CLI read`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData( accessToken: "security-token-background", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh-background") final class ReadCounter: @unchecked Sendable { var count = 0 } let securityReadCalls = ReadCounter() let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { _ in securityReadCalls.count += 1 return securityData }) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } }) }) #expect(creds.accessToken == "security-token-background") #expect(securityReadCalls.count == 1) } } } } @Test func `experimental reader falls back when security CLI throws`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let fallbackData = self.makeCredentialsData( accessToken: "fallback-token", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "fallback-refresh") let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .timedOut) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } } }) }) #expect(creds.accessToken == "fallback-token") } } } } @Test func `experimental reader falls back when security CLI output malformed`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let fallbackData = self.makeCredentialsData( accessToken: "fallback-token", expiresAt: Date(timeIntervalSinceNow: 3600)) let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(Data("not-json".utf8))) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } } }) }) #expect(creds.accessToken == "fallback-token") } } } } @Test func `experimental reader load from claude keychain uses security CLI`() throws { let securityData = self.makeCredentialsData( accessToken: "security-direct", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh") let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore() let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint( modifiedAt: 200, createdAt: 199, persistentRefHash: "sentinel") let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting( fingerprintStore) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: nil, fingerprint: sentinelFingerprint) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } } } }) }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(creds.accessToken == "security-direct") #expect(creds.refreshToken == "security-refresh") #expect(fingerprintStore.fingerprint == nil) } @Test func `experimental reader has claude keychain credentials without prompt uses security CLI`() { let securityData = self.makeCredentialsData( accessToken: "security-available", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { ProviderInteractionContext.$current.withValue(.userInitiated) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } }) }) #expect(hasCredentials == true) } @Test func `experimental reader has claude keychain credentials without prompt falls back when security CLI fails`() { let fallbackData = self.makeCredentialsData( accessToken: "fallback-available", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { ProviderInteractionContext.$current.withValue(.userInitiated) { ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .nonZeroExit) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } } }) }) #expect(hasCredentials == true) } @Test func `experimental reader ignores prompt policy and cooldown for background silent check`() { let securityData = self.makeCredentialsData( accessToken: "security-background", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = KeychainAccessGate.withTaskOverrideForTesting(false) { ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(false) { ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .never, operation: { ProviderInteractionContext.$current.withValue(.background) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } }) }) } } #expect(hasCredentials == true) } @Test func `experimental reader load from claude keychain fallback blocked when stored mode never`() throws { var threwNotFound = false do { _ = try KeychainAccessGate.withTaskOverrideForTesting(false) { try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .never, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .nonZeroExit) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) }) } } catch let error as ClaudeOAuthCredentialsError { if case .notFound = error { threwNotFound = true } } #expect(threwNotFound == true) } @Test func `experimental reader security CLI read pins preferred account when available`() throws { let securityData = self.makeCredentialsData( accessToken: "security-account-pinned", expiresAt: Date(timeIntervalSinceNow: 3600)) final class AccountBox: @unchecked Sendable { var value: String? } let pinnedAccount = AccountBox() let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting("new-account") { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { request in pinnedAccount.value = request.account return securityData }) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) } }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(pinnedAccount.value == "new-account") #expect(creds.accessToken == "security-account-pinned") } @Test func `experimental reader security CLI read does not pin account in background`() throws { let securityData = self.makeCredentialsData( accessToken: "security-account-not-pinned", expiresAt: Date(timeIntervalSinceNow: 3600)) final class AccountBox: @unchecked Sendable { var value: String? } let pinnedAccount = AccountBox() let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting("new-account") { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { request in pinnedAccount.value = request.account return securityData }) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) } }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(pinnedAccount.value == nil) #expect(creds.accessToken == "security-account-not-pinned") } @Test func `experimental reader freshness sync skips security CLI when preflight requires interaction`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting { try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData(Lines 1–500 of 882. Load more
import Foundation import Testing @testable import CodexBarCore @Suite(.serialized) struct ClaudeOAuthCredentialsStoreSecurityCLITests { private func makeCredentialsData(accessToken: String, expiresAt: Date, refreshToken: String? = nil) -> Data { let millis = Int(expiresAt.timeIntervalSince1970 * 1000) let refreshField: String = { guard let refreshToken else { return "" } return ",\n \"refreshToken\": \"\(refreshToken)\"" }() let json = """ { "claudeAiOauth": { "accessToken": "\(accessToken)", "expiresAt": \(millis), "scopes": ["user:profile"]\(refreshField) } } """ return Data(json.utf8) } @Test func `experimental reader prefers security CLI for non interactive load`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData( accessToken: "security-token", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh") let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } }) }) #expect(creds.accessToken == "security-token") #expect(creds.refreshToken == "security-refresh") #expect(creds.scopes.contains("user:profile")) } } } } @Test func `experimental reader non interactive background load still executes security CLI read`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData( accessToken: "security-token-background", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh-background") final class ReadCounter: @unchecked Sendable { var count = 0 } let securityReadCalls = ReadCounter() let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { _ in securityReadCalls.count += 1 return securityData }) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } }) }) #expect(creds.accessToken == "security-token-background") #expect(securityReadCalls.count == 1) } } } } @Test func `experimental reader falls back when security CLI throws`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let fallbackData = self.makeCredentialsData( accessToken: "fallback-token", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "fallback-refresh") let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .timedOut) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } } }) }) #expect(creds.accessToken == "fallback-token") } } } } @Test func `experimental reader falls back when security CLI output malformed`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil) ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil) } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let fallbackData = self.makeCredentialsData( accessToken: "fallback-token", expiresAt: Date(timeIntervalSinceNow: 3600)) let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(Data("not-json".utf8))) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false) } } } }) }) #expect(creds.accessToken == "fallback-token") } } } } @Test func `experimental reader load from claude keychain uses security CLI`() throws { let securityData = self.makeCredentialsData( accessToken: "security-direct", expiresAt: Date(timeIntervalSinceNow: 3600), refreshToken: "security-refresh") let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore() let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint( modifiedAt: 200, createdAt: 199, persistentRefHash: "sentinel") let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting( fingerprintStore) { try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: nil, fingerprint: sentinelFingerprint) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } } } }) }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(creds.accessToken == "security-direct") #expect(creds.refreshToken == "security-refresh") #expect(fingerprintStore.fingerprint == nil) } @Test func `experimental reader has claude keychain credentials without prompt uses security CLI`() { let securityData = self.makeCredentialsData( accessToken: "security-available", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { ProviderInteractionContext.$current.withValue(.userInitiated) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } }) }) #expect(hasCredentials == true) } @Test func `experimental reader has claude keychain credentials without prompt falls back when security CLI fails`() { let fallbackData = self.makeCredentialsData( accessToken: "fallback-available", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { ProviderInteractionContext.$current.withValue(.userInitiated) { ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: fallbackData, fingerprint: nil) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .nonZeroExit) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } } }) }) #expect(hasCredentials == true) } @Test func `experimental reader ignores prompt policy and cooldown for background silent check`() { let securityData = self.makeCredentialsData( accessToken: "security-background", expiresAt: Date(timeIntervalSinceNow: 3600)) let hasCredentials = KeychainAccessGate.withTaskOverrideForTesting(false) { ClaudeOAuthKeychainAccessGate.withShouldAllowPromptOverrideForTesting(false) { ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .never, operation: { ProviderInteractionContext.$current.withValue(.background) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { ClaudeOAuthCredentialsStore.hasClaudeKeychainCredentialsWithoutPrompt() } } }) }) } } #expect(hasCredentials == true) } @Test func `experimental reader load from claude keychain fallback blocked when stored mode never`() throws { var threwNotFound = false do { _ = try KeychainAccessGate.withTaskOverrideForTesting(false) { try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .never, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .nonZeroExit) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) }) } } catch let error as ClaudeOAuthCredentialsError { if case .notFound = error { threwNotFound = true } } #expect(threwNotFound == true) } @Test func `experimental reader security CLI read pins preferred account when available`() throws { let securityData = self.makeCredentialsData( accessToken: "security-account-pinned", expiresAt: Date(timeIntervalSinceNow: 3600)) final class AccountBox: @unchecked Sendable { var value: String? } let pinnedAccount = AccountBox() let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting("new-account") { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { request in pinnedAccount.value = request.account return securityData }) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) } }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(pinnedAccount.value == "new-account") #expect(creds.accessToken == "security-account-pinned") } @Test func `experimental reader security CLI read does not pin account in background`() throws { let securityData = self.makeCredentialsData( accessToken: "security-account-not-pinned", expiresAt: Date(timeIntervalSinceNow: 3600)) final class AccountBox: @unchecked Sendable { var value: String? } let pinnedAccount = AccountBox() let loaded = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthCredentialsStore.withSecurityCLIReadAccountOverrideForTesting("new-account") { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .always, operation: { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { request in pinnedAccount.value = request.account return securityData }) { try ClaudeOAuthCredentialsStore.loadFromClaudeKeychain() } } }) } }) let creds = try ClaudeOAuthCredentials.parse(data: loaded) #expect(pinnedAccount.value == nil) #expect(creds.accessToken == "security-account-not-pinned") } @Test func `experimental reader freshness sync skips security CLI when preflight requires interaction`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting { try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() defer { ClaudeOAuthCredentialsStore.invalidateCache() ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() } let tempDir = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString, isDirectory: true) try FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true) let fileURL = tempDir.appendingPathComponent("credentials.json") try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) { let securityData = self.makeCredentialsData(