Files
steipete--codexbar/Tests/CodexBarTests/ClaudeOAuthCredentialsStorePromptPolicyTests.swift
2026-07-13 12:22:33 +08:00

743 lines
42 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct ClaudeOAuthCredentialsStorePromptPolicyTests {
@Test
func `keychain prompt notify preserves its void function signature`() {
let notify: (KeychainPromptContext) -> Void = KeychainPromptHandler.notify
_ = notify
}
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 `does not read claude keychain in background when prompt mode 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) }
ClaudeOAuthCredentialsStore.invalidateCache()
ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()
defer {
ClaudeOAuthCredentialsStore.invalidateCache()
ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()
ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()
}
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) {
ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()
let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(
modifiedAt: 1,
createdAt: 1,
persistentRefHash: "ref1")
let keychainData = self.makeCredentialsData(
accessToken: "keychain-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
do {
_ = try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {
try ProviderInteractionContext.$current.withValue(.background) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: keychainData,
fingerprint: fingerprint)
{
try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)
}
}
}
Issue.record("Expected ClaudeOAuthCredentialsError.notFound")
} catch let error as ClaudeOAuthCredentialsError {
guard case .notFound = error else {
Issue.record("Expected .notFound, got \(error)")
return
}
}
}
}
}
}
@Test
func `can read claude keychain on user action when prompt mode 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) }
ClaudeOAuthCredentialsStore.invalidateCache()
ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()
defer {
ClaudeOAuthCredentialsStore.invalidateCache()
ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()
ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()
}
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) {
ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()
let fingerprint = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(
modifiedAt: 1,
createdAt: 1,
persistentRefHash: "ref1")
let keychainData = self.makeCredentialsData(
accessToken: "keychain-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
let creds = try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: keychainData,
fingerprint: fingerprint)
{
try ClaudeOAuthCredentialsStore.load(environment: [:], allowKeychainPrompt: false)
}
}
}
#expect(creds.accessToken == "keychain-token")
}
}
}
}
@Test
func `user initiated claude keychain reads respect pre alert acknowledgement cooldown`() 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 {
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 keychainData = self.makeCredentialsData(
accessToken: "keychain-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.allowed
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let credentials = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(
.onlyOnUserAction)
{
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: keychainData,
fingerprint: nil)
{
let first = try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
ClaudeOAuthCredentialsStore.invalidateCache()
let second = try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
return (first, second)
}
}
}
})
})
#expect(credentials.0.accessToken == "keychain-token")
#expect(credentials.1.accessToken == "keychain-token")
#expect(preAlertHits == 1)
}
}
}
}
}
@Test
func `shows pre alert when claude keychain likely 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 {
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 keychainData = self.makeCredentialsData(
accessToken: "keychain-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(
.onlyOnUserAction)
{
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: keychainData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
}
}
}
})
})
#expect(creds.accessToken == "keychain-token")
#expect(preAlertHits == 1)
}
}
}
}
}
@Test
func `shows pre alert when claude keychain preflight fails`() 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 {
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 keychainData = self.makeCredentialsData(
accessToken: "keychain-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.failure(-1)
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(
.onlyOnUserAction)
{
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: keychainData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
}
}
}
})
})
#expect(creds.accessToken == "keychain-token")
#expect(preAlertHits == 1)
}
}
}
}
}
@Test
func `experimental reader skips pre alert when security CLI read succeeds`() 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 {
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-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withSecurityCLIReadOverrideForTesting(
.data(securityData))
{
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
}
}
}
}
})
})
#expect(creds.accessToken == "security-token")
#expect(preAlertHits == 0)
}
}
}
}
}
@Test
func `experimental reader shows pre alert when security CLI fails and fallback needs 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 {
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 fallbackData = self.makeCredentialsData(
accessToken: "fallback-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: fallbackData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore
.withSecurityCLIReadOverrideForTesting(.timedOut) {
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
}
}
}
}
}
})
})
#expect(creds.accessToken == "fallback-token")
#expect(preAlertHits == 1)
}
}
}
}
}
@Test
func `experimental reader does not fallback in background when stored mode 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 {
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 fallbackData = self.makeCredentialsData(
accessToken: "fallback-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
do {
_ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(
.onlyOnUserAction)
{
try ProviderInteractionContext.$current.withValue(.background) {
try ClaudeOAuthCredentialsStore
.withClaudeKeychainOverridesForTesting(
data: fallbackData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore
.withSecurityCLIReadOverrideForTesting(.timedOut) {
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true,
respectKeychainPromptCooldown: true)
}
}
}
}
}
})
})
Issue.record("Expected ClaudeOAuthCredentialsError.notFound")
} catch let error as ClaudeOAuthCredentialsError {
guard case .notFound = error else {
Issue.record("Expected .notFound, got \(error)")
return
}
}
#expect(preAlertHits == 0)
}
}
}
}
}
@Test
func `experimental reader does not fallback when stored mode never`() 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 {
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 fallbackData = self.makeCredentialsData(
accessToken: "fallback-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
do {
_ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.never) {
try ProviderInteractionContext.$current.withValue(.userInitiated) {
try ClaudeOAuthCredentialsStore
.withClaudeKeychainOverridesForTesting(
data: fallbackData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore
.withSecurityCLIReadOverrideForTesting(.timedOut) {
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true)
}
}
}
}
}
})
})
Issue.record("Expected ClaudeOAuthCredentialsError.notFound")
} catch let error as ClaudeOAuthCredentialsError {
guard case .notFound = error else {
Issue.record("Expected .notFound, got \(error)")
return
}
}
#expect(preAlertHits == 0)
}
}
}
}
}
@Test
func `experimental reader non interactive fallback blocked in background when stored mode 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 {
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 fallbackData = self.makeCredentialsData(
accessToken: "fallback-token-only-on-user-action",
expiresAt: Date(timeIntervalSinceNow: 3600))
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.allowed
}
do {
_ = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(
.onlyOnUserAction)
{
try ProviderInteractionContext.$current.withValue(.background) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: fallbackData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore
.withSecurityCLIReadOverrideForTesting(.timedOut) {
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: false,
respectKeychainPromptCooldown: true)
}
}
}
}
}
})
Issue.record("Expected ClaudeOAuthCredentialsError.notFound")
} catch let error as ClaudeOAuthCredentialsError {
guard case .notFound = error else {
Issue.record("Expected .notFound, got \(error)")
return
}
}
}
}
}
}
}
@Test
func `experimental reader allows fallback in background when stored mode always`() 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 {
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 fallbackData = self.makeCredentialsData(
accessToken: "fallback-token",
expiresAt: Date(timeIntervalSinceNow: 3600))
var preAlertHits = 0
let preflightOverride: (String, String?) -> KeychainAccessPreflight.Outcome = { _, _ in
.interactionRequired
}
let promptHandler: (KeychainPromptContext) -> Void = { _ in
preAlertHits += 1
}
let creds = try KeychainAccessPreflight.withCheckGenericPasswordOverrideForTesting(
preflightOverride,
operation: {
try KeychainPromptHandler.withHandlerForTesting(promptHandler, operation: {
try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(
.securityCLIExperimental)
{
try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.always) {
try ProviderInteractionContext.$current.withValue(.background) {
try ClaudeOAuthCredentialsStore.withClaudeKeychainOverridesForTesting(
data: fallbackData,
fingerprint: nil)
{
try ClaudeOAuthCredentialsStore
.withSecurityCLIReadOverrideForTesting(.timedOut) {
try ClaudeOAuthCredentialsStore.load(
environment: [:],
allowKeychainPrompt: true,
respectKeychainPromptCooldown: false)
}
}
}
}
}
})
})
#expect(creds.accessToken == "fallback-token")
#expect(preAlertHits == 1)
}
}
}
}
}
}