import Foundation import Testing @testable import CodexBarCore struct CodexOAuthTests { private func makeContext( runtime: ProviderRuntime = .app, sourceMode: ProviderSourceMode = .auto, includeCredits: Bool = true, includeOptionalUsage: Bool = true) -> ProviderFetchContext { let browserDetection = BrowserDetection(cacheTTL: 0) return ProviderFetchContext( runtime: runtime, sourceMode: sourceMode, includeCredits: includeCredits, includeOptionalUsage: includeOptionalUsage, webTimeout: 60, webDebugDumpHTML: false, verbose: false, env: [:], settings: nil, fetcher: UsageFetcher(), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection) } @Test func `parses O auth credentials`() throws { let json = """ { "OPENAI_API_KEY": null, "tokens": { "access_token": "access-token", "refresh_token": "refresh-token", "id_token": "id-token", "account_id": "account-123" }, "last_refresh": "2025-12-20T12:34:56Z" } """ let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8)) #expect(creds.accessToken == "access-token") #expect(creds.refreshToken == "refresh-token") #expect(creds.idToken == "id-token") #expect(creds.accountId == "account-123") #expect(creds.lastRefresh != nil) } @Test func `parses legacy camel case O auth credentials`() throws { let json = """ { "OPENAI_API_KEY": null, "tokens": { "accessToken": "access-token", "refreshToken": "refresh-token", "idToken": "id-token", "accountId": "account-123" }, "last_refresh": "2025-12-20T12:34:56Z" } """ let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8)) #expect(creds.accessToken == "access-token") #expect(creds.refreshToken == "refresh-token") #expect(creds.idToken == "id-token") #expect(creds.accountId == "account-123") #expect(creds.lastRefresh != nil) } @Test func `parses API key credentials`() throws { let json = """ { "OPENAI_API_KEY": "sk-test" } """ let creds = try CodexOAuthCredentialsStore.parse(data: Data(json.utf8)) #expect(creds.accessToken == "sk-test") #expect(creds.refreshToken.isEmpty) #expect(creds.idToken == nil) #expect(creds.accountId == nil) } @Test func `reset-credit token load ignores an API key beside O auth tokens`() throws { let home = FileManager.default.temporaryDirectory .appendingPathComponent("codexbar-reset-credit-oauth-\(UUID().uuidString)") try FileManager.default.createDirectory(at: home, withIntermediateDirectories: true) defer { try? FileManager.default.removeItem(at: home) } let json = """ { "OPENAI_API_KEY": "sk-test", "tokens": { "access_token": "oauth-access-token", "refresh_token": "oauth-refresh-token", "account_id": "account-123" }, "last_refresh": "2026-07-01T12:00:00Z" } """ try Data(json.utf8).write(to: home.appendingPathComponent("auth.json")) let credentials = try CodexOAuthCredentialsStore.loadOAuthTokens(env: ["CODEX_HOME": home.path]) #expect(credentials.accessToken == "oauth-access-token") #expect(credentials.refreshToken == "oauth-refresh-token") #expect(credentials.accountId == "account-123") } @Test func `decodes credits balance string`() throws { let json = """ { "plan_type": "pro", "rate_limit": { "primary_window": { "used_percent": 12, "reset_at": 1766948068, "limit_window_seconds": 18000 } }, "credits": { "has_credits": false, "unlimited": false, "balance": "0" } } """ let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8)) #expect(response.planType?.rawValue == "pro") #expect(response.credits?.balance == 0) #expect(response.credits?.hasCredits == false) #expect(response.credits?.unlimited == false) } @Test func `decodes prolite plan type without failing usage mapping`() throws { let json = """ { "plan_type": "prolite", "rate_limit": { "primary_window": { "used_percent": 12, "reset_at": 1766948068, "limit_window_seconds": 18000 } } } """ let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8)) #expect(response.planType?.rawValue == "prolite") let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) #expect(mapped?.primary?.usedPercent == 12) } @Test func `maps usage windows from O auth`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary?.usedPercent == 22) #expect(snapshot.primary?.windowMinutes == 300) #expect(snapshot.secondary?.usedPercent == 43) #expect(snapshot.secondary?.windowMinutes == 10080) #expect(snapshot.primary?.resetsAt != nil) #expect(snapshot.secondary?.resetsAt != nil) } @Test func `O auth response with precise windows maps to exact confidence`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds) #expect(result.sourceLabel == "oauth") #expect(result.usage.dataConfidence == .exact) #expect(result.usage.primary?.usedPercent == 22) #expect(result.usage.secondary?.usedPercent == 43) } @Test func `O auth response with malformed additional window maps to unknown confidence`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 } }, "additional_rate_limits": [ { "limit_name": "GPT-5.3-Codex-Spark", "metered_feature": "gpt_5_3_codex_spark", "rate_limit": { "primary_window": { "used_percent": "bad" } } } ] } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds) #expect(result.usage.primary?.usedPercent == 22) #expect(result.usage.extraRateWindows == nil) #expect(result.usage.dataConfidence == .unknown) } @Test func `maps free weekly only window into secondary`() throws { let json = """ { "plan_type": "free", "rate_limit": { "primary_window": { "used_percent": 0, "reset_at": 1775468693, "limit_window_seconds": 604800 }, "secondary_window": null } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary == nil) #expect(snapshot.secondary?.usedPercent == 0) #expect(snapshot.secondary?.windowMinutes == 10080) } @Test func `keeps single session window as primary`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 9, "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": null } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary?.usedPercent == 9) #expect(snapshot.primary?.windowMinutes == 300) #expect(snapshot.secondary == nil) } @Test func `preserves unknown single window as primary`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 17, "reset_at": 1766948068, "limit_window_seconds": 32400 }, "secondary_window": null } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary?.usedPercent == 17) #expect(snapshot.primary?.windowMinutes == 540) #expect(snapshot.secondary == nil) } @Test func `preserves unknown secondary only window as primary`() throws { let json = """ { "rate_limit": { "primary_window": null, "secondary_window": { "used_percent": 17, "reset_at": 1766948068, "limit_window_seconds": 32400 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary?.usedPercent == 17) #expect(snapshot.primary?.windowMinutes == 540) #expect(snapshot.secondary == nil) } @Test func `swaps reversed weekly and unknown windows`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 }, "secondary_window": { "used_percent": 17, "reset_at": 1766948068, "limit_window_seconds": 32400 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) let snapshot = try #require(mapped) #expect(snapshot.primary?.usedPercent == 17) #expect(snapshot.primary?.windowMinutes == 540) #expect(snapshot.secondary?.usedPercent == 43) #expect(snapshot.secondary?.windowMinutes == 10080) } @Test func `returns nil when O auth usage has no windows`() throws { let json = """ { "rate_limit": { "primary_window": null, "secondary_window": null } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) #expect(snapshot == nil) } @Test func `keeps valid window when secondary window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 18, "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": { "used_percent": "bad", "reset_at": 1767407914, "limit_window_seconds": 604800 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) #expect(snapshot?.primary?.usedPercent == 18) #expect(snapshot?.secondary == nil) let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds) #expect(result.usage.primary?.usedPercent == 18) #expect(result.usage.secondary == nil) #expect(result.usage.dataConfidence == .unknown) } @Test func `auto mode keeps weekly window when primary window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": "bad", "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .auto) #expect(result.usage.primary == nil) #expect(result.usage.secondary?.usedPercent == 43) #expect(result.usage.secondary?.windowMinutes == 10080) } @Test func `explicit oauth keeps weekly window when primary window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": "bad", "reset_at": 1766948068, "limit_window_seconds": 18000 }, "secondary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .oauth) #expect(result.usage.primary == nil) #expect(result.usage.secondary?.usedPercent == 43) #expect(result.usage.secondary?.windowMinutes == 10080) } @Test func `auto mode preserves reversed session window when primary window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": "bad", "reset_at": 1767407914, "limit_window_seconds": 604800 }, "secondary_window": { "used_percent": 18, "reset_at": 1766948068, "limit_window_seconds": 18000 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .auto) #expect(result.usage.primary?.usedPercent == 18) #expect(result.usage.primary?.windowMinutes == 300) #expect(result.usage.secondary == nil) } @Test func `auto mode keeps weekly window when reversed session window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 }, "secondary_window": { "used_percent": "bad", "reset_at": 1766948068, "limit_window_seconds": 18000 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .auto) #expect(result.usage.primary == nil) #expect(result.usage.secondary?.usedPercent == 43) #expect(result.usage.secondary?.windowMinutes == 10080) } @Test func `explicit oauth keeps weekly window when reversed session window is malformed`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 }, "secondary_window": { "used_percent": "bad", "reset_at": 1766948068, "limit_window_seconds": 18000 } } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .oauth) #expect(result.usage.primary == nil) #expect(result.usage.secondary?.usedPercent == 43) #expect(result.usage.secondary?.windowMinutes == 10080) } @Test func `ignores malformed credits payload while keeping usage`() throws { let json = """ { "rate_limit": { "primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 } }, "credits": { "has_credits": false, "unlimited": false, "balance": [] } } """ let response = try CodexOAuthUsageFetcher._decodeUsageResponseForTesting(Data(json.utf8)) #expect(response.credits?.hasCredits == false) #expect(response.credits?.unlimited == false) #expect(response.credits?.balance == nil) let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds) #expect(snapshot?.primary?.usedPercent == 22) } @Test func `credits only O auth payload still returns credits result`() throws { let json = """ { "rate_limit": { "primary_window": null, "secondary_window": null }, "credits": { "has_credits": true, "unlimited": false, "balance": "14.5" } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting(Data(json.utf8), credentials: creds) #expect(result.usage.primary == nil) #expect(result.usage.secondary == nil) #expect(result.credits?.remaining == 14.5) #expect(result.sourceLabel == "oauth") } @Test func `credits only O auth payload returns credits in auto mode`() throws { let json = """ { "rate_limit": { "primary_window": null, "secondary_window": null }, "credits": { "has_credits": true, "unlimited": false, "balance": "14.5" } } """ let creds = CodexOAuthCredentials( accessToken: "access", refreshToken: "refresh", idToken: nil, accountId: nil, lastRefresh: Date()) let result = try CodexOAuthFetchStrategy._mapResultForTesting( Data(json.utf8), credentials: creds, sourceMode: .auto) #expect(result.usage.primary == nil) #expect(result.usage.secondary == nil) #expect(result.credits?.remaining == 14.5) #expect(result.sourceLabel == "oauth") } @Test func `auto mode only falls back from O auth on auth failures`() { let strategy = CodexOAuthFetchStrategy() let context = self.makeContext(sourceMode: .auto) #expect(strategy.shouldFallback(on: CodexOAuthFetchError.unauthorized, context: context)) #expect(strategy.shouldFallback(on: CodexOAuthCredentialsError.notFound, context: context)) #expect(strategy.shouldFallback(on: CodexOAuthCredentialsError.missingTokens, context: context)) #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.expired, context: context)) #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.revoked, context: context)) #expect(strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.reused, context: context)) #expect(!strategy.shouldFallback(on: UsageError.noRateLimitsFound, context: context)) #expect(!strategy.shouldFallback(on: CodexOAuthCredentialsError.decodeFailed("bad json"), context: context)) #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.invalidResponse, context: context)) #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.serverError(500, "offline"), context: context)) #expect(!strategy.shouldFallback( on: CodexOAuthFetchError.networkError(URLError(.notConnectedToInternet)), context: context)) #expect(!strategy.shouldFallback( on: CodexTokenRefresher.RefreshError.networkError(URLError(.timedOut)), context: context)) } @Test func `non 401 invalid grant refresh failure is treated as revoked`() { let data = Data(#"{"error":"invalid_grant"}"#.utf8) let error = CodexTokenRefresher._refreshFailureErrorForTesting(statusCode: 400, data: data) switch error { case .revoked: break default: Issue.record("Expected invalid_grant to be treated as revoked") } } @Test func `non auth refresh failure remains invalid response`() { let data = Data(#"{"error":"invalid_request"}"#.utf8) let error = CodexTokenRefresher._refreshFailureErrorForTesting(statusCode: 400, data: data) switch error { case let .invalidResponse(message): #expect(message == "Status 400") default: Issue.record("Expected invalid_request to remain an invalid response") } } @Test func `explicit O auth mode never falls back to CLI`() { let strategy = CodexOAuthFetchStrategy() let context = self.makeContext(sourceMode: .oauth) #expect(!strategy.shouldFallback(on: CodexOAuthFetchError.unauthorized, context: context)) #expect(!strategy.shouldFallback(on: CodexTokenRefresher.RefreshError.expired, context: context)) } @Test func `resolves chat GPT usage URL from config`() { let config = "chatgpt_base_url = \"https://chatgpt.com/backend-api/\"\n" let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config) #expect(url.absoluteString == "https://chatgpt.com/backend-api/wham/usage") } @Test func `resolves codex usage URL from config`() { let config = "chatgpt_base_url = \"https://api.openai.com\"\n" let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config) #expect(url.absoluteString == "https://api.openai.com/api/codex/usage") } @Test func `normalizes chat GPT base URL without backend API`() { let config = "chatgpt_base_url = \"https://chat.openai.com\"\n" let url = CodexOAuthUsageFetcher._resolveUsageURLForTesting(configContents: config) #expect(url.absoluteString == "https://chat.openai.com/backend-api/wham/usage") } }