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( accessToken: "security-sync", expiresAt: Date(timeIntervalSinceNow: 3600)) final class ReadCounter: @unchecked Sendable { var count = 0 } let securityReadCalls = ReadCounter() func loadWithPreflight( _ outcome: KeychainAccessPreflight.Outcome) throws -> ClaudeOAuthCredentials { let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in outcome } return try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting( preflightOverride, operation: { try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental) { try ClaudeOAuthKeychainPromptPreference .withTaskOverrideForTesting(.always) { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore .withSecurityCLIReadOverrideForTesting( .dynamic { _ in securityReadCalls.count += 1 return securityData }) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false, respectKeychainPromptCooldown: true) } } } } }) } let first = try loadWithPreflight(.allowed) #expect(first.accessToken == "security-sync") #expect(securityReadCalls.count == 1) let second = try loadWithPreflight(.interactionRequired) #expect(second.accessToken == "security-sync") #expect(securityReadCalls.count == 1) } } } } } } @Test func `experimental reader freshness sync background respects stored only on user action`() 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( accessToken: "security-sync-only-on-user-action", expiresAt: Date(timeIntervalSinceNow: 3600)) final class ReadCounter: @unchecked Sendable { var count = 0 } let securityReadCalls = ReadCounter() let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in .allowed } func load(_ interaction: ProviderInteraction) throws -> ClaudeOAuthCredentials { try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting( preflightOverride, operation: { try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental) { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting( .onlyOnUserAction) { try ProviderInteractionContext.$current.withValue(interaction) { try ClaudeOAuthCredentialsStore .withSecurityCLIReadOverrideForTesting( .dynamic { _ in securityReadCalls.count += 1 return securityData }) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: false, respectKeychainPromptCooldown: true) } } } } }) } let first = try load(.userInitiated) #expect(first.accessToken == "security-sync-only-on-user-action") #expect(securityReadCalls.count == 1) let second = try load(.background) #expect(second.accessToken == "security-sync-only-on-user-action") #expect(securityReadCalls.count == 1) } } } } } } @Test func `experimental reader sync skips fingerprint probe after security CLI read`() { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" KeychainCacheStore.withServiceOverrideForTesting(service) { KeychainAccessGate.withTaskOverrideForTesting(false) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting { ClaudeOAuthCredentialsStore.invalidateCache() defer { ClaudeOAuthCredentialsStore.invalidateCache() } let securityData = self.makeCredentialsData( accessToken: "security-sync-no-fingerprint-probe", expiresAt: Date(timeIntervalSinceNow: 3600)) let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore() let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint( modifiedAt: 123, createdAt: 122, persistentRefHash: "sentinel") let synced = ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) { ProviderInteractionContext.$current.withValue(.background) { ClaudeOAuthCredentialsStore.withClaudeKeychainFingerprintStoreOverrideForTesting( fingerprintStore) { ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting( data: nil, fingerprint: sentinelFingerprint) { ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .data(securityData)) { ClaudeOAuthCredentialsStore.syncFromClaudeKeychainWithoutPrompt( now: Date()) } } } } } }) #expect(synced == true) #expect(fingerprintStore.fingerprint == nil) } } } } @Test func `experimental reader no prompt repair skips fingerprint probe after security CLI success`() 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() } try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting { try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting { 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-repair-no-fingerprint-probe", expiresAt: Date(timeIntervalSinceNow: 3600)) let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore() let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint( modifiedAt: 456, createdAt: 455, persistentRefHash: "sentinel") let record = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) { try ProviderInteractionContext.$current.withValue(.background) { try ClaudeOAuthCredentialsStore .withClaudeKeychainFingerprintStoreOverrideForTesting( fingerprintStore) { try ClaudeOAuthCredentialsStore .withClaudeKeychainOverridesForTesting( data: nil, fingerprint: sentinelFingerprint) { try ClaudeOAuthCredentialsStore .withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.loadRecord( environment: [:], allowKeychainPrompt: false, respectKeychainPromptCooldown: true) } } } } } }) #expect(record.credentials.accessToken == "security-repair-no-fingerprint-probe") #expect(record.source == .claudeKeychain) #expect(fingerprintStore.fingerprint == nil) } } } } } } @Test func `experimental reader load with prompt skips fingerprint probe after security CLI success`() 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() } try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting { try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting { 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-load-with-prompt", expiresAt: Date(timeIntervalSinceNow: 3600)) let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore() let sentinelFingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint( modifiedAt: 321, createdAt: 320, persistentRefHash: "sentinel") let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore .withClaudeKeychainFingerprintStoreOverrideForTesting( fingerprintStore) { try ClaudeOAuthCredentialsStore .withClaudeKeychainOverridesForTesting( data: nil, fingerprint: sentinelFingerprint) { try ClaudeOAuthCredentialsStore .withSecurityCLIReadOverrideForTesting( .data(securityData)) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: true, respectKeychainPromptCooldown: false) } } } } } }) #expect(creds.accessToken == "security-load-with-prompt") #expect(fingerprintStore.fingerprint == nil) } } } } } } @Test func `experimental reader load with prompt does not read when global keychain disabled`() throws { let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)" try KeychainCacheStore.withServiceOverrideForTesting(service) { try KeychainAccessGate.withTaskOverrideForTesting(true) { KeychainCacheStore.setTestStoreForTesting(true) defer { KeychainCacheStore.setTestStoreForTesting(false) } 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( accessToken: "security-should-not-read", expiresAt: Date(timeIntervalSinceNow: 3600)) var threwNotFound = false final class ReadCounter: @unchecked Sendable { var count = 0 } let securityReadCalls = ReadCounter() do { _ = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting( .securityCLIExperimental, operation: { try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) { try ProviderInteractionContext.$current.withValue(.userInitiated) { try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting( .dynamic { _ in securityReadCalls.count += 1 return securityData }) { try ClaudeOAuthCredentialsStore.load( environment: [:], allowKeychainPrompt: true, respectKeychainPromptCooldown: false) } } } }) } catch let error as ClaudeOAuthCredentialsError { if case .notFound = error { threwNotFound = true } else { throw error } } #expect(threwNotFound == true) #expect(securityReadCalls.count < 1) } } } } }