import Foundation import Testing @testable import CodexBarCore struct OpenAIAPICreditBalanceTests { private func makeContext( apiKey: String = "sk-test", usesAdminKey: Bool = false, projectID: String? = nil, selectedTokenAccountID: UUID? = nil, historyDays: Int = 30) -> ProviderFetchContext { let browserDetection = BrowserDetection(cacheTTL: 0) let apiKeyEnvironmentKey = usesAdminKey ? OpenAIAPISettingsReader.adminAPIKeyEnvironmentKey : OpenAIAPISettingsReader.apiKeyEnvironmentKey var env = [apiKeyEnvironmentKey: apiKey] if let projectID { env[OpenAIAPISettingsReader.projectIDEnvironmentKey] = projectID } return ProviderFetchContext( runtime: .app, sourceMode: .api, includeCredits: false, webTimeout: 1, webDebugDumpHTML: false, verbose: false, env: env, settings: nil, fetcher: UsageFetcher(environment: env), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection, selectedTokenAccountID: selectedTokenAccountID, costUsageHistoryDays: historyDays) } @Test func `prefers admin key environment variable`() { let token = OpenAIAPISettingsReader.apiKey(environment: [ "OPENAI_API_KEY": "sk-project", "OPENAI_ADMIN_KEY": "sk-admin", ]) #expect(token == "sk-admin") } @Test func `parses credit grants balance`() throws { let now = Date(timeIntervalSince1970: 1_700_000_000) let json = """ { "object": "credit_summary", "total_granted": 25.5, "total_used": 7.25, "total_available": 18.25, "grants": { "object": "list", "data": [ { "grant_amount": 10.0, "used_amount": 1.0, "effective_at": 1690000000, "expires_at": 1800000000 } ] } } """ let snapshot = try OpenAIAPICreditBalanceFetcher._parseSnapshotForTesting(Data(json.utf8), now: now) #expect(snapshot.totalGranted == 25.5) #expect(snapshot.totalUsed == 7.25) #expect(snapshot.totalAvailable == 18.25) #expect(snapshot.nextGrantExpiry == Date(timeIntervalSince1970: 1_800_000_000)) } @Test func `maps balance to usage snapshot`() { let now = Date(timeIntervalSince1970: 1_700_000_000) let balance = OpenAIAPICreditBalanceSnapshot( totalGranted: 100, totalUsed: 40, totalAvailable: 60, nextGrantExpiry: Date(timeIntervalSince1970: 1_800_000_000), updatedAt: now) let usage = balance.toUsageSnapshot() #expect(usage.primary?.usedPercent == 40) #expect(usage.primary?.resetDescription == "$60.00 available") #expect(usage.providerCost?.used == 40) #expect(usage.providerCost?.limit == 100) #expect(usage.identity?.providerID == .openai) #expect(usage.identity?.loginMethod == "API balance: $60.00") } @Test func `maps unauthorized legacy balance to admin key guidance`() async { let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) let response = HTTPURLResponse( url: url, statusCode: 401, httpVersion: nil, headerFields: nil)! return (Data("{}".utf8), response) } do { _ = try await OpenAIAPICreditBalanceFetcher.fetchBalance( apiKey: "sk-test", session: transport) Issue.record("Expected credential rejection") } catch let error as OpenAIAPICreditBalanceError { #expect(error == .unauthorized) #expect(error.errorDescription?.contains("organization Admin API key") == true) #expect(error.errorDescription?.contains("service-account keys") == true) } catch { Issue.record("Expected OpenAIAPICreditBalanceError, got \(error)") } } @Test func `falls back to legacy billing when admin usage rejects credentials`() async throws { let strategy = OpenAIAPIBalanceFetchStrategy( usageFetcher: { _, _ in throw OpenAIAPIUsageError.apiError(endpoint: "costs", statusCode: 403) }, balanceFetcher: { _ in OpenAIAPICreditBalanceSnapshot( totalGranted: 100, totalUsed: 25, totalAvailable: 75, nextGrantExpiry: nil, updatedAt: Date(timeIntervalSince1970: 1_700_000_000)) }) let result = try await strategy.fetch(self.makeContext()) #expect(result.sourceLabel == "billing-api") #expect(result.usage.identity?.loginMethod == "API balance: $75.00") } @Test func `legacy API key without project ID falls back to legacy billing`() async throws { let strategy = OpenAIAPIBalanceFetchStrategy( usageFetcher: { credential, historyDays in #expect(credential.apiKey == "sk-test") #expect(credential.projectID == nil) #expect(historyDays == 30) throw OpenAIAPIUsageError.apiError(endpoint: "costs", statusCode: 403) }, balanceFetcher: { apiKey in #expect(apiKey == "sk-test") return OpenAIAPICreditBalanceSnapshot( totalGranted: 100, totalUsed: 25, totalAvailable: 75, nextGrantExpiry: nil, updatedAt: Date(timeIntervalSince1970: 1_700_000_000)) }) let result = try await strategy.fetch(self.makeContext()) #expect(result.sourceLabel == "billing-api") #expect(result.usage.identity?.loginMethod == "API balance: $75.00") #expect(result.usage.identity?.accountOrganization == nil) } @Test func `selected token account uses scrubbed final environment for legacy fallback`() async throws { let accountID = UUID() let strategy = OpenAIAPIBalanceFetchStrategy( usageFetcher: { credential, historyDays in #expect(credential.apiKey == "account-token") #expect(credential.projectID == nil) #expect(historyDays == 30) throw OpenAIAPIUsageError.apiError(endpoint: "costs", statusCode: 403) }, balanceFetcher: { apiKey in #expect(apiKey == "account-token") return OpenAIAPICreditBalanceSnapshot( totalGranted: 100, totalUsed: 25, totalAvailable: 75, nextGrantExpiry: nil, updatedAt: Date(timeIntervalSince1970: 1_700_000_000)) }) let result = try await strategy.fetch(self.makeContext( apiKey: "account-token", usesAdminKey: true, selectedTokenAccountID: accountID)) #expect(result.sourceLabel == "billing-api") #expect(result.usage.identity?.loginMethod == "API balance: $75.00") #expect(result.usage.identity?.accountOrganization == nil) } @Test func `preserves admin usage error when legacy fallback also fails`() async { let usageFailure = OpenAIAPIUsageError.parseFailed(endpoint: "costs", message: "changed") let strategy = OpenAIAPIBalanceFetchStrategy( usageFetcher: { _, _ in throw usageFailure }, balanceFetcher: { _ in throw OpenAIAPICreditBalanceError.forbidden }) do { _ = try await strategy.fetch(self.makeContext()) Issue.record("Expected admin usage failure") } catch let error as OpenAIAPIUsageError { #expect(error == usageFailure) } catch { Issue.record("Expected OpenAIAPIUsageError, got \(error)") } } @Test func `falls back to credit balance when admin usage endpoint is unavailable`() async throws { let strategy = OpenAIAPIBalanceFetchStrategy( usageFetcher: { credential, historyDays in #expect(credential.apiKey == "sk-test") #expect(credential.projectID == nil) #expect(historyDays == 90) throw OpenAIAPIUsageError.apiError(endpoint: "costs", statusCode: 500) }, balanceFetcher: { apiKey in #expect(apiKey == "sk-test") return OpenAIAPICreditBalanceSnapshot( totalGranted: 100, totalUsed: 25, totalAvailable: 75, nextGrantExpiry: nil, updatedAt: Date(timeIntervalSince1970: 1_700_000_000)) }) let result = try await strategy.fetch(self.makeContext(historyDays: 90)) #expect(result.sourceLabel == "billing-api") #expect(result.usage.providerCost?.used == 25) #expect(result.usage.providerCost?.limit == 100) } }