1507 lines · 61.5 KB · Swift
RawDownload
import Foundationimport Testing@testable import CodexBar@testable import CodexBarCore struct ClaudeUsageTests {    private actor AsyncCounter {        private var value = 0         func increment() -> Int {            self.value += 1            return self.value        }         func current() -> Int {            self.value        }    }     private static func makeOAuthUsageResponse() throws -> OAuthUsageResponse {        let json = """        {          "five_hour": { "utilization": 7, "resets_at": "2025-12-23T16:00:00.000Z" },          "seven_day": { "utilization": 21, "resets_at": "2025-12-29T23:00:00.000Z" }        }        """        return try ClaudeOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8))    }     @Test    func `parses usage JSON with sonnet limit`() {        let json = """        {          "ok": true,          "session_5h": { "pct_used": 1, "resets": "11am (Europe/Vienna)" },          "week_all_models": { "pct_used": 8, "resets": "Nov 21 at 5am (Europe/Vienna)" },          "week_sonnet": { "pct_used": 0, "resets": "Nov 21 at 5am (Europe/Vienna)" }        }        """        let data = Data(json.utf8)        let snap = ClaudeUsageFetcher.parse(json: data)        #expect(snap != nil)        #expect(snap?.primary.usedPercent == 1)        #expect(snap?.primary.windowMinutes == 300)        #expect(snap?.secondary?.usedPercent == 8)        #expect(snap?.secondary?.windowMinutes == 10080)        #expect(snap?.primary.resetDescription == "11am (Europe/Vienna)")    }     @Test    func `oauth delegated retry retries once then succeeds`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()        let usageResponse = try Self.makeOAuthUsageResponse()        let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true)         let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }        let delegatedOverride: (@Sendable (            Date,            TimeInterval,            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .attemptedSucceeded        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in            let call = await loadCounter.increment()            if call == 1 {                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI            }            return ClaudeOAuthCredentials(                accessToken: "fresh-token",                refreshToken: "refresh-token",                expiresAt: Date(timeIntervalSinceNow: 3600),                scopes: ["user:profile"],                rateLimitTier: nil)        }         let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {            try await ProviderInteractionContext.$current.withValue(.userInitiated) {                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride, operation: {                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                        delegatedOverride,                        operation: {                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride                                .withValue(loadCredsOverride, operation: {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                })                        })                })            }        }         #expect(await loadCounter.current() == 2)        #expect(await delegatedCounter.current() == 1)        #expect(snapshot.primary.usedPercent == 7)        #expect(snapshot.secondary?.usedPercent == 21)    }     @Test    func `oauth delegated retry second attempt still expired fails cleanly`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()        let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true)         do {            let delegatedOverride: (@Sendable (                Date,                TimeInterval,                [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in                _ = await delegatedCounter.increment()                return .attemptedSucceeded            }            let loadCredsOverride: (@Sendable (                [String: String],                Bool,                Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in                _ = await loadCounter.increment()                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI            }             _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {                try await ProviderInteractionContext.$current.withValue(.userInitiated) {                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                        delegatedOverride,                        operation: {                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(                                loadCredsOverride,                                operation: {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                })                        })                }            }            Issue.record("Expected delegated retry to fail when credentials remain expired")        } catch let error as ClaudeUsageError {            guard case let .oauthFailed(message) = error else {                Issue.record("Expected ClaudeUsageError.oauthFailed, got \(error)")                return            }            #expect(message.contains("delegated Claude CLI refresh"))        } catch {            Issue.record("Expected ClaudeUsageError, got \(error)")        }         #expect(await loadCounter.current() == 2)        #expect(await delegatedCounter.current() == 1)    }     @Test    func `oauth delegated retry auto mode cli unavailable fails fast`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()         let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true)         let delegatedOverride: (@Sendable (Date, TimeInterval, [String: String]) async            -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .cliUnavailable        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in            _ = await loadCounter.increment()            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI        }         do {            _ = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {                try await ProviderInteractionContext.$current.withValue(.userInitiated) {                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                        delegatedOverride,                        operation: {                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(                                loadCredsOverride,                                operation: {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                })                        })                }            }            Issue.record("Expected delegated retry to fail fast when CLI is unavailable")        } catch let error as ClaudeUsageError {            guard case let .oauthFailed(message) = error else {                Issue.record("Expected ClaudeUsageError.oauthFailed, got \(error)")                return            }            #expect(message.contains("Claude CLI is not available"))        } catch {            Issue.record("Expected ClaudeUsageError, got \(error)")        }         // Auto-mode: should not attempt a second credential load.        #expect(await loadCounter.current() == 1)        #expect(await delegatedCounter.current() == 1)    }     @Test    func `oauth delegated retry auto mode attempted failed then non interactive reload succeeds`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()        let usageResponse = try Self.makeOAuthUsageResponse()         final class FlagBox: @unchecked Sendable {            var allowKeychainPromptFlags: [Bool] = []        }        let flags = FlagBox()         let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true)         let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }        let delegatedOverride: (@Sendable (Date, TimeInterval, [String: String]) async            -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .attemptedFailed("no-change")        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)            let call = await loadCounter.increment()            if call == 1 {                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI            }            return ClaudeOAuthCredentials(                accessToken: "fresh-token",                refreshToken: "refresh-token",                expiresAt: Date(timeIntervalSinceNow: 3600),                scopes: ["user:profile"],                rateLimitTier: nil)        }         let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {            try await ProviderInteractionContext.$current.withValue(.userInitiated) {                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride, operation: {                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                        delegatedOverride,                        operation: {                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride                                .withValue(loadCredsOverride, operation: {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                })                        })                })            }        }         #expect(await loadCounter.current() == 2)        #expect(await delegatedCounter.current() == 1)        #expect(snapshot.primary.usedPercent == 7)         // User-initiated repair: if the delegated refresh couldn't sync silently, we may allow an interactive prompt        // on the retry to help recovery.        #expect(flags.allowKeychainPromptFlags.count == 2)        #expect(flags.allowKeychainPromptFlags[1] == true)    }     @Test    func `oauth delegated retry only on user action background suppresses delegation`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()         let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true)         let delegatedOverride: (@Sendable (            Date,            TimeInterval,            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .attemptedSucceeded        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in            _ = await loadCounter.increment()            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI        }         do {            _ = try await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(                .securityFramework,                operation: {                    try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {                        try await ProviderInteractionContext.$current.withValue(.background) {                            try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                                delegatedOverride)                            {                                try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(                                    loadCredsOverride)                                {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                }                            }                        }                    }                })            Issue.record("Expected delegated refresh to be suppressed in background")        } catch let error as ClaudeUsageError {            guard case let .oauthFailed(message) = error else {                Issue.record("Expected ClaudeUsageError.oauthFailed, got \(error)")                return            }            #expect(message.contains("background repair is suppressed"))        } catch {            Issue.record("Expected ClaudeUsageError, got \(error)")        }         #expect(await loadCounter.current() == 1)        #expect(await delegatedCounter.current() == 0)    }     @Test    func `oauth delegated retry never background suppresses delegation even for CLI`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()         let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true,            allowBackgroundDelegatedRefresh: true)         let delegatedOverride: (@Sendable (            Date,            TimeInterval,            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .attemptedSucceeded        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, _, _ in            _ = await loadCounter.increment()            throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI        }         do {            _ = try await ClaudeOAuthKeychainReadStrategyPreference.withTaskOverrideForTesting(                .securityFramework,                operation: {                    try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.never) {                        try await ProviderInteractionContext.$current.withValue(.background) {                            try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(                                delegatedOverride)                            {                                try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(                                    loadCredsOverride)                                {                                    try await fetcher.loadLatestUsage(model: "sonnet")                                }                            }                        }                    }                })            Issue.record("Expected delegated refresh to be suppressed for prompt policy 'never'")        } catch let error as ClaudeUsageError {            guard case let .oauthFailed(message) = error else {                Issue.record("Expected ClaudeUsageError.oauthFailed, got \(error)")                return            }            #expect(message.contains("Delegated refresh is disabled by 'never' keychain policy"))        } catch {            Issue.record("Expected ClaudeUsageError, got \(error)")        }         #expect(await loadCounter.current() == 1)        #expect(await delegatedCounter.current() == 0)    }     @Test    func `oauth bootstrap only on user action background startup allows interactive read when no cache`() async throws {        final class FlagBox: @unchecked Sendable {            var allowKeychainPromptFlags: [Bool] = []            var allowBackgroundPromptBootstrapFlags: [Bool] = []        }         let flags = FlagBox()        let usageResponse = try Self.makeOAuthUsageResponse()        let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: true,            allowStartupBootstrapPrompt: true)         let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)            flags.allowBackgroundPromptBootstrapFlags.append(ClaudeOAuthCredentialsStore.allowBackgroundPromptBootstrap)            return ClaudeOAuthCredentials(                accessToken: "fresh-token",                refreshToken: "refresh-token",                expiresAt: Date(timeIntervalSinceNow: 3600),                scopes: ["user:profile"],                rateLimitTier: nil)        }         let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {            try await ProviderRefreshContext.$current.withValue(.startup) {                try await ProviderInteractionContext.$current.withValue(.background) {                    try await ClaudeUsageFetcher.$hasCachedCredentialsOverride.withValue(false) {                        try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride) {                            try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {                                try await fetcher.loadLatestUsage(model: "sonnet")                            }                        }                    }                }            }        }         #expect(flags.allowKeychainPromptFlags == [true])        #expect(flags.allowBackgroundPromptBootstrapFlags == [true])        #expect(snapshot.primary.usedPercent == 7)    }     @Test    func `oauth delegated retry only on user action background allows delegation for CLI`() async throws {        let loadCounter = AsyncCounter()        let delegatedCounter = AsyncCounter()        let usageResponse = try Self.makeOAuthUsageResponse()         final class FlagBox: @unchecked Sendable {            var allowKeychainPromptFlags: [Bool] = []        }        let flags = FlagBox()         let fetcher = ClaudeUsageFetcher(            browserDetection: BrowserDetection(cacheTTL: 0),            environment: [:],            dataSource: .oauth,            oauthKeychainPromptCooldownEnabled: false,            allowBackgroundDelegatedRefresh: true)         let fetchOverride: (@Sendable (String) async throws -> OAuthUsageResponse)? = { _ in usageResponse }        let delegatedOverride: (@Sendable (            Date,            TimeInterval,            [String: String]) async -> ClaudeOAuthDelegatedRefreshCoordinator.Outcome)? = { _, _, _ in            _ = await delegatedCounter.increment()            return .attemptedSucceeded        }        let loadCredsOverride: (@Sendable (            [String: String],            Bool,            Bool) async throws -> ClaudeOAuthCredentials)? = { _, allowKeychainPrompt, _ in            flags.allowKeychainPromptFlags.append(allowKeychainPrompt)            let call = await loadCounter.increment()            if call == 1 {                throw ClaudeOAuthCredentialsError.refreshDelegatedToClaudeCLI            }            return ClaudeOAuthCredentials(                accessToken: "fresh-token",                refreshToken: "refresh-token",                expiresAt: Date(timeIntervalSinceNow: 3600),                scopes: ["user:profile"],                rateLimitTier: nil)        }         let snapshot = try await ClaudeOAuthKeychainPromptPreference.withTaskOverrideForTesting(.onlyOnUserAction) {            try await ProviderInteractionContext.$current.withValue(.background) {                try await ClaudeUsageFetcher.$fetchOAuthUsageOverride.withValue(fetchOverride) {                    try await ClaudeUsageFetcher.$delegatedRefreshAttemptOverride.withValue(delegatedOverride) {                        try await ClaudeUsageFetcher.$loadOAuthCredentialsOverride.withValue(loadCredsOverride) {                            try await fetcher.loadLatestUsage(model: "sonnet")                        }                    }                }            }

Lines 1–500 of 1507.