import Foundation import Testing @testable import CodexBarCore @Suite(.serialized) struct ClaudeOAuthKeychainAccessGateTests { @Test func `blocks until cooldown expires`() { KeychainAccessGate.withTaskOverrideForTesting(false) { let store = ClaudeOAuthKeychainAccessGate.DeniedUntilStore() ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(store) { let now = Date(timeIntervalSince1970: 1000) #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now)) ClaudeOAuthKeychainAccessGate.recordDenied(now: now) #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) == false) #expect( ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 - 1)) == false) #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 + 1))) } } } @Test func `persists denied until`() { KeychainAccessGate.withTaskOverrideForTesting(false) { ClaudeOAuthKeychainAccessGate.resetForTesting() defer { ClaudeOAuthKeychainAccessGate.resetForTesting() } let now = Date(timeIntervalSince1970: 2000) ClaudeOAuthKeychainAccessGate.recordDenied(now: now) ClaudeOAuthKeychainAccessGate.resetInMemoryForTesting() #expect( ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now.addingTimeInterval(60 * 60 * 6 - 1)) == false) } } @Test func `respects debug disable keychain access`() { KeychainAccessGate.withTaskOverrideForTesting(true) { ClaudeOAuthKeychainAccessGate.resetForTesting() defer { ClaudeOAuthKeychainAccessGate.resetForTesting() } #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: Date()) == false) } } @Test func `process keeps keychain access disabled despite false global override`() { guard ProcessInfo.processInfo.environment["CODEXBAR_ALLOW_TEST_KEYCHAIN_ACCESS"] != "1" else { return } KeychainAccessGate.resetOverrideForTesting() defer { KeychainAccessGate.resetOverrideForTesting() } KeychainAccessGate.isDisabled = false #expect(KeychainAccessGate.isDisabled) } @Test func `process force disable survives settings override`() { KeychainAccessGate.resetOverrideForTesting() defer { KeychainAccessGate.resetOverrideForTesting() } KeychainAccessGate.forceDisabledForProcess(reason: "unbundled-executable") KeychainAccessGate.isDisabled = false #expect(KeychainAccessGate.isDisabled) #expect(KeychainAccessGate.processDisableReason == "unbundled-executable") } @Test func `clear denied allows immediate retry`() { KeychainAccessGate.withTaskOverrideForTesting(false) { ClaudeOAuthKeychainAccessGate.resetForTesting() defer { ClaudeOAuthKeychainAccessGate.resetForTesting() } let store = ClaudeOAuthKeychainAccessGate.DeniedUntilStore() ClaudeOAuthKeychainAccessGate.withDeniedUntilStoreOverrideForTesting(store) { let now = Date(timeIntervalSince1970: 3000) ClaudeOAuthKeychainAccessGate.recordDenied(now: now) #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now) == false) #expect(ClaudeOAuthKeychainAccessGate.clearDenied(now: now)) #expect(ClaudeOAuthKeychainAccessGate.shouldAllowPrompt(now: now)) #expect(ClaudeOAuthKeychainAccessGate.clearDenied(now: now) == false) } } } }