Peter Steinbergertest: harden release gate timing2c8021b5 months agoHistory
889 lines · 47.1 KB · Swift
RawDownload
import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)struct ClaudeOAuthCredentialsStoreTests {    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 `loads from keychain cache before expired file`() throws {        let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)"        try ProviderInteractionContext.$current.withValue(.background) {            try KeychainCacheStore.withServiceOverrideForTesting(service) {                try KeychainAccessGate.withTaskOverrideForTesting(false) {                    KeychainCacheStore.setTestStoreForTesting(true)                    defer { KeychainCacheStore.setTestStoreForTesting(false) }                     ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()                    defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }                    try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {                        try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {                            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 expiredData = self.makeCredentialsData(                                    accessToken: "expired",                                    expiresAt: Date(timeIntervalSinceNow: -3600))                                try expiredData.write(to: fileURL)                                 let cachedData = self.makeCredentialsData(                                    accessToken: "cached",                                    expiresAt: Date(timeIntervalSinceNow: 3600))                                let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(                                    data: cachedData,                                    storedAt: Date())                                let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                                ClaudeOAuthCredentialsStore.invalidateCache()                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)                                defer { KeychainCacheStore.clear(key: cacheKey) }                                _ = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(                                    .securityFramework)                                {                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(                                        .onlyOnUserAction)                                    {                                        try ClaudeOAuthCredentialsStore.load(                                            environment: [:],                                            allowKeychainPrompt: false)                                    }                                }                                // Re-store to cache after file check has marked file as "seen"                                KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)                                let creds = try ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(                                    .securityFramework)                                {                                    try ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(                                        .onlyOnUserAction)                                    {                                        try ClaudeOAuthCredentialsStore.load(                                            environment: [:],                                            allowKeychainPrompt: false)                                    }                                }                                 #expect(creds.accessToken == "cached")                                #expect(creds.isExpired == false)                            }                        }                    }                }            }        }    }     @Test    func `load record non interactive repair can be disabled`() throws {        let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)"        try KeychainCacheStore.withServiceOverrideForTesting(service) {            KeychainCacheStore.setTestStoreForTesting(true)            defer { KeychainCacheStore.setTestStoreForTesting(false) }             ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()            defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }             try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {                    // Ensure file-based lookup doesn't interfere (and avoid touching ~/.claude).                    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.invalidateCache()                         let keychainData = self.makeCredentialsData(                            accessToken: "claude-keychain",                            expiresAt: Date(timeIntervalSinceNow: 3600))                         // Simulate Claude Keychain containing creds, without querying the real Keychain.                        try ProviderInteractionContext.$current.withValue(.userInitiated) {                            try ClaudeOAuthCredentialsStore                                .withClaudeKeychainOverridesForTesting(data: keychainData, fingerprint: nil) {                                    // When repair is disabled, non-interactive loads should not consult Claude's                                    // keychain data.                                    do {                                        _ = try ClaudeOAuthCredentialsStore.loadRecord(                                            environment: [:],                                            allowKeychainPrompt: false,                                            respectKeychainPromptCooldown: true,                                            allowClaudeKeychainRepairWithoutPrompt: false)                                        Issue.record("Expected ClaudeOAuthCredentialsError.notFound")                                    } catch let error as ClaudeOAuthCredentialsError {                                        guard case .notFound = error else {                                            Issue.record("Expected .notFound, got \(error)")                                            return                                        }                                    }                                     // With repair enabled, we should be able to seed from the "Claude keychain"                                    // override.                                    let record = try ClaudeOAuthCredentialsStore.loadRecord(                                        environment: [:],                                        allowKeychainPrompt: false,                                        respectKeychainPromptCooldown: true,                                        allowClaudeKeychainRepairWithoutPrompt: true)                                    #expect(record.credentials.accessToken == "claude-keychain")                                }                        }                    }                }            }        }    }     @Test    func `invalidates cache when credentials file changes`() throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }         ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()        defer { ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting() }         // Avoid interacting with the real Keychain in unit tests.        try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {            try ClaudeOAuthCredentialsStore.withIsolatedCredentialsFileTrackingForTesting {                try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {                    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 first = self.makeCredentialsData(                            accessToken: "first",                            expiresAt: Date(timeIntervalSinceNow: 3600))                        try first.write(to: fileURL)                         let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                        let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(data: first, storedAt: Date())                        KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)                         _ = try ClaudeOAuthCredentialsStore.load(environment: [:])                         let updated = self.makeCredentialsData(                            accessToken: "second",                            expiresAt: Date(timeIntervalSinceNow: 3600))                        try updated.write(to: fileURL)                         #expect(ClaudeOAuthCredentialsStore.invalidateCacheIfCredentialsFileChanged())                        KeychainCacheStore.clear(key: cacheKey)                         let creds = try ClaudeOAuthCredentialsStore.load(environment: [:])                        #expect(creds.accessToken == "second")                    }                }            }        }    }     @Test    func `returns expired file when no other sources`() 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) }                 try ClaudeOAuthCredentialsStore.withIsolatedMemoryCacheForTesting {                    ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()                    defer { 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 expiredData = self.makeCredentialsData(                            accessToken: "expired-only",                            expiresAt: Date(timeIntervalSinceNow: -3600))                        try expiredData.write(to: fileURL)                         try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {                            ClaudeOAuthCredentialsStore.invalidateCache()                            let creds = try ClaudeOAuthCredentialsStore.load(environment: [:])                             #expect(creds.accessToken == "expired-only")                            #expect(creds.isExpired == true)                        }                    }                }            }        }    }     @Test    func `load with auto refresh expired claude CLI owner throws delegated refresh`() async throws {        let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)"        try await KeychainCacheStore.withServiceOverrideForTesting(service) {            KeychainCacheStore.setTestStoreForTesting(true)            defer { KeychainCacheStore.setTestStoreForTesting(false) }             ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()            defer { 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")            await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {                await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {                    ClaudeOAuthCredentialsStore.invalidateCache()                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                    defer { KeychainCacheStore.clear(key: cacheKey) }                     let expiredData = self.makeCredentialsData(                        accessToken: "expired-claude-cli-owner",                        expiresAt: Date(timeIntervalSinceNow: -3600),                        refreshToken: "refresh-token")                    KeychainCacheStore.store(                        key: cacheKey,                        entry: ClaudeOAuthCredentialsStore.CacheEntry(                            data: expiredData,                            storedAt: Date(),                            owner: .claudeCLI))                     do {                        _ = try await ClaudeOAuthCredentialsStore.loadWithAutoRefresh(                            environment: [:],                            allowKeychainPrompt: false,                            respectKeychainPromptCooldown: true)                        Issue.record("Expected delegated refresh error for Claude CLI-owned credentials")                    } catch let error as ClaudeOAuthCredentialsError {                        guard case .refreshDelegatedToClaudeCLI = error else {                            Issue.record("Expected .refreshDelegatedToClaudeCLI, got \(error)")                            return                        }                    } catch {                        Issue.record("Expected ClaudeOAuthCredentialsError, got \(error)")                    }                }            }        }    }     @Test    func `load with auto refresh expired codexbar owner uses direct refresh path`() async throws {        let service = "com.steipete.codexbar.cache.tests.\(UUID().uuidString)"        try await KeychainCacheStore.withServiceOverrideForTesting(service) {            KeychainCacheStore.setTestStoreForTesting(true)            defer { KeychainCacheStore.setTestStoreForTesting(false) }             ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()            defer { 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")            await ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {                await ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {                    ClaudeOAuthCredentialsStore.invalidateCache()                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                    defer { KeychainCacheStore.clear(key: cacheKey) }                     let expiredData = self.makeCredentialsData(                        accessToken: "expired-codexbar-owner",                        expiresAt: Date(timeIntervalSinceNow: -3600),                        refreshToken: "refresh-token")                    KeychainCacheStore.store(                        key: cacheKey,                        entry: ClaudeOAuthCredentialsStore.CacheEntry(                            data: expiredData,                            storedAt: Date(),                            owner: .codexbar))                     await ClaudeOAuthRefreshFailureGate.$shouldAttemptOverride.withValue(false) {                        do {                            _ = try await ClaudeOAuthCredentialsStore.loadWithAutoRefresh(                                environment: [:],                                allowKeychainPrompt: false,                                respectKeychainPromptCooldown: true)                            Issue.record("Expected refresh failure for CodexBar-owned direct refresh path")                        } catch let error as ClaudeOAuthCredentialsError {                            guard case .refreshFailed = error else {                                Issue.record("Expected .refreshFailed, got \(error)")                                return                            }                        } catch {                            Issue.record("Expected ClaudeOAuthCredentialsError, got \(error)")                        }                    }                }            }        }    }     @Test    func `load record legacy cache entry without owner defaults to claude CLI owner`() throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }         ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()        defer { 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.withIsolatedMemoryCacheForTesting {            try ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {                try ClaudeOAuthCredentialsStore.withKeychainAccessOverrideForTesting(true) {                    ClaudeOAuthCredentialsStore.invalidateCache()                    let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                    defer { KeychainCacheStore.clear(key: cacheKey) }                     let validData = self.makeCredentialsData(                        accessToken: "legacy-owner",                        expiresAt: Date(timeIntervalSinceNow: 3600),                        refreshToken: "refresh-token")                    KeychainCacheStore.store(                        key: cacheKey,                        entry: ClaudeOAuthCredentialsStore.CacheEntry(                            data: validData,                            storedAt: Date()))                     let record = try ClaudeOAuthCredentialsStore.loadRecord(                        environment: [:],                        allowKeychainPrompt: false,                        respectKeychainPromptCooldown: true)                    #expect(record.owner == .claudeCLI)                    #expect(record.source == .cacheKeychain)                }            }        }    }     @Test    func `has cached credentials returns false for expired unrefreshable codexbar cache entry`() throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }         ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()        defer { 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")        ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {            ClaudeOAuthCredentialsStore.invalidateCache()             let expiredData = self.makeCredentialsData(                accessToken: "expired-no-refresh",                expiresAt: Date(timeIntervalSinceNow: -3600),                refreshToken: nil)            let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(                data: expiredData,                storedAt: Date(),                owner: .codexbar)            let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)            KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)             #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == false)        }    }     @Test    func `has cached credentials returns true for expired refreshable cache entry`() throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }         ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()        defer { 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")        ClaudeOAuthCredentialsStore.withCredentialsURLOverrideForTesting(fileURL) {            ClaudeOAuthCredentialsStore.invalidateCache()             let expiredData = self.makeCredentialsData(                accessToken: "expired-refreshable",                expiresAt: Date(timeIntervalSinceNow: -3600),                refreshToken: "refresh")            let cacheEntry = ClaudeOAuthCredentialsStore.CacheEntry(data: expiredData, storedAt: Date())            let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)            KeychainCacheStore.store(key: cacheKey, entry: cacheEntry)             #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == true)        }    }     @Test    func `has cached credentials returns true for expired claude CLI backed credentials file`() throws {        KeychainCacheStore.setTestStoreForTesting(true)        defer { KeychainCacheStore.setTestStoreForTesting(false) }         ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()        defer { 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) {            ClaudeOAuthCredentialsStore.invalidateCache()             let expiredData = self.makeCredentialsData(                accessToken: "expired-file-no-refresh",                expiresAt: Date(timeIntervalSinceNow: -3600),                refreshToken: nil)            try expiredData.write(to: fileURL)             #expect(ClaudeOAuthCredentialsStore.hasCachedCredentials() == true)        }    }     @Test    func `syncs cache when claude keychain fingerprint changes and token differs`() 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) }                 ClaudeOAuthKeychainAccessGate.resetForTesting()                defer { ClaudeOAuthKeychainAccessGate.resetForTesting() }                 ClaudeOAuthCredentialsStore._resetCredentialsFileTrackingForTesting()                defer { 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) {                    ClaudeOAuthCredentialsStore.invalidateCache()                    ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()                    defer {                        ClaudeOAuthCredentialsStore.invalidateCache()                        ClaudeOAuthCredentialsStore._resetClaudeKeychainChangeTrackingForTesting()                        ClaudeOAuthCredentialsStore.setClaudeKeychainDataOverrideForTesting(nil)                        ClaudeOAuthCredentialsStore.setClaudeKeychainFingerprintOverrideForTesting(nil)                    }                     // Avoid cross-suite interference from UserDefaults fingerprint persistence.                    let fingerprintStore = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprintStore()                     let cacheKey = KeychainCacheStore.Key.oauth(provider: .claude)                    let cachedData = self.makeCredentialsData(                        accessToken: "cached-token",                        expiresAt: Date(timeIntervalSinceNow: 3600))                    KeychainCacheStore.store(                        key: cacheKey,                        entry: ClaudeOAuthCredentialsStore.CacheEntry(data: cachedData, storedAt: Date()))                     let fingerprint1 = ClaudeOAuthCredentialsStore.ClaudeKeychainFingerprint(                        modifiedAt: 1,                        createdAt: 1,                        persistentRefHash: "ref1")                     let first = try ProviderInteractionContext.$current.withValue(.userInitiated) {

Lines 1–500 of 889.