882 lines · 48.0 KB · Swift
RawDownload
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.