Files
steipete--codexbar/Tests/CodexBarTests/OpenRouterUsageStatsTests.swift
2026-07-13 12:22:33 +08:00

295 lines
11 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct OpenRouterUsageStatsTests {
@Test
func `to usage snapshot uses key quota for primary window`() {
let snapshot = OpenRouterUsageSnapshot(
totalCredits: 50,
totalUsage: 45.3895596325,
balance: 4.6104403675,
usedPercent: 90.779119265,
keyLimit: 20,
keyUsage: 5,
rateLimit: nil,
updatedAt: Date(timeIntervalSince1970: 1_739_841_600))
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 25)
#expect(usage.primary?.resetsAt == nil)
#expect(usage.primary?.resetDescription == nil)
#expect(usage.openRouterUsage?.keyQuotaStatus == .available)
}
@Test
func `to usage snapshot without valid key limit omits primary window`() {
let snapshot = OpenRouterUsageSnapshot(
totalCredits: 50,
totalUsage: 45.3895596325,
balance: 4.6104403675,
usedPercent: 90.779119265,
keyLimit: nil,
keyUsage: nil,
rateLimit: nil,
updatedAt: Date(timeIntervalSince1970: 1_739_841_600))
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.openRouterUsage?.keyQuotaStatus == .unavailable)
}
@Test
func `to usage snapshot when no limit configured omits primary and marks no limit`() {
let snapshot = OpenRouterUsageSnapshot(
totalCredits: 50,
totalUsage: 45.3895596325,
balance: 4.6104403675,
usedPercent: 90.779119265,
keyDataFetched: true,
keyLimit: nil,
keyUsage: nil,
rateLimit: nil,
updatedAt: Date(timeIntervalSince1970: 1_739_841_600))
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary == nil)
#expect(usage.openRouterUsage?.keyQuotaStatus == .noLimitConfigured)
}
@Test
func `sanitizers redact sensitive token shapes`() {
let body = """
{"error":"bad token sk-or-v1-abc123","token":"secret-token","authorization":"Bearer sk-or-v1-xyz789"}
"""
let summary = OpenRouterUsageFetcher._sanitizedResponseBodySummaryForTesting(body)
let debugBody = OpenRouterUsageFetcher._redactedDebugResponseBodyForTesting(body)
#expect(summary.contains("sk-or-v1-[REDACTED]"))
#expect(summary.contains("\"token\":\"[REDACTED]\""))
#expect(!summary.contains("secret-token"))
#expect(!summary.contains("sk-or-v1-abc123"))
#expect(debugBody?.contains("sk-or-v1-[REDACTED]") == true)
#expect(debugBody?.contains("\"token\":\"[REDACTED]\"") == true)
#expect(debugBody?.contains("secret-token") == false)
#expect(debugBody?.contains("sk-or-v1-xyz789") == false)
}
@Test
func `non200 fetch throws generic HTTP error without body details`() async throws {
let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)
}
OpenRouterStubURLProtocol.handler = nil
}
OpenRouterStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
let body = #"{"error":"invalid sk-or-v1-super-secret","token":"dont-leak-me"}"#
return Self.makeResponse(url: url, body: body, statusCode: 401)
}
do {
_ = try await OpenRouterUsageFetcher.fetchUsage(
apiKey: "sk-or-v1-test",
environment: ["OPENROUTER_API_URL": "https://openrouter.test/api/v1"])
Issue.record("Expected OpenRouterUsageError.apiError")
} catch let error as OpenRouterUsageError {
guard case let .apiError(message) = error else {
Issue.record("Expected apiError, got: \(error)")
return
}
#expect(message == "HTTP 401")
#expect(!message.contains("dont-leak-me"))
#expect(!message.contains("sk-or-v1-super-secret"))
}
}
@Test
func `fetch usage sets credits timeout and client headers`() async throws {
let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)
}
OpenRouterStubURLProtocol.handler = nil
}
OpenRouterStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
switch url.path {
case "/api/v1/credits":
#expect(request.timeoutInterval == 15)
#expect(request.value(forHTTPHeaderField: "HTTP-Referer") == "https://codexbar.example")
#expect(request.value(forHTTPHeaderField: "X-Title") == "CodexBar QA")
let body = #"{"data":{"total_credits":100,"total_usage":40}}"#
return Self.makeResponse(url: url, body: body, statusCode: 200)
case "/api/v1/key":
let body = #"""
{"data":{
"limit":20,
"usage":0.5,
"usage_daily":0.12,
"usage_weekly":0.74,
"usage_monthly":4.56,
"rate_limit":{"requests":120,"interval":"10s"}
}}
"""#
return Self.makeResponse(url: url, body: body, statusCode: 200)
default:
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
}
let usage = try await OpenRouterUsageFetcher.fetchUsage(
apiKey: "sk-or-v1-test",
environment: [
"OPENROUTER_API_URL": "https://openrouter.test/api/v1",
"OPENROUTER_HTTP_REFERER": " https://codexbar.example ",
"OPENROUTER_X_TITLE": "CodexBar QA",
])
#expect(usage.totalCredits == 100)
#expect(usage.totalUsage == 40)
#expect(usage.keyDataFetched)
#expect(usage.keyLimit == 20)
#expect(usage.keyUsage == 0.5)
#expect(usage.keyUsageDaily == 0.12)
#expect(usage.keyUsageWeekly == 0.74)
#expect(usage.keyUsageMonthly == 4.56)
#expect(usage.keyRemaining == 19.5)
#expect(usage.keyUsedPercent == 2.5)
#expect(usage.keyQuotaStatus == .available)
}
@Test
func `fetch usage when key endpoint fails marks quota unavailable`() async throws {
let registered = URLProtocol.registerClass(OpenRouterStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(OpenRouterStubURLProtocol.self)
}
OpenRouterStubURLProtocol.handler = nil
}
OpenRouterStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
switch url.path {
case "/api/v1/credits":
let body = #"{"data":{"total_credits":100,"total_usage":40}}"#
return Self.makeResponse(url: url, body: body, statusCode: 200)
case "/api/v1/key":
return Self.makeResponse(url: url, body: "{}", statusCode: 500)
default:
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
}
let usage = try await OpenRouterUsageFetcher.fetchUsage(
apiKey: "sk-or-v1-test",
environment: ["OPENROUTER_API_URL": "https://openrouter.test/api/v1"])
#expect(!usage.keyDataFetched)
#expect(usage.keyQuotaStatus == .unavailable)
}
@Test
func `key enrichment timeout does not wait for operation that ignores cancellation`() async throws {
let startedAt = ContinuousClock.now
let fetched = try await OpenRouterUsageFetcher._boundedKeyFetchForTesting(
timeout: .milliseconds(20))
{
await withCheckedContinuation { continuation in
DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {
continuation.resume()
}
}
}
let elapsed = startedAt.duration(to: .now)
#expect(!fetched)
#expect(elapsed < .milliseconds(300))
try await Task.sleep(for: .milliseconds(550))
}
@Test
func `usage snapshot round trip persists open router usage metadata`() throws {
let openRouter = OpenRouterUsageSnapshot(
totalCredits: 50,
totalUsage: 45.3895596325,
balance: 4.6104403675,
usedPercent: 90.779119265,
keyDataFetched: true,
keyLimit: nil,
keyUsage: nil,
keyUsageDaily: 0.12,
keyUsageWeekly: 0.74,
keyUsageMonthly: 4.56,
rateLimit: nil,
updatedAt: Date(timeIntervalSince1970: 1_739_841_600))
let snapshot = openRouter.toUsageSnapshot()
let encoder = JSONEncoder()
let data = try encoder.encode(snapshot)
let decoded = try JSONDecoder().decode(UsageSnapshot.self, from: data)
#expect(decoded.openRouterUsage?.keyDataFetched == true)
#expect(decoded.openRouterUsage?.keyQuotaStatus == .noLimitConfigured)
#expect(decoded.openRouterUsage?.keyUsageDaily == 0.12)
#expect(decoded.openRouterUsage?.keyUsageWeekly == 0.74)
#expect(decoded.openRouterUsage?.keyUsageMonthly == 4.56)
}
private static func makeResponse(
url: URL,
body: String,
statusCode: Int = 200) -> (HTTPURLResponse, Data)
{
let response = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "application/json"])!
return (response, Data(body.utf8))
}
}
final class OpenRouterStubURLProtocol: URLProtocol {
nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
override static func canInit(with request: URLRequest) -> Bool {
request.url?.host == "openrouter.test"
}
override static func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
guard let handler = Self.handler else {
self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))
return
}
do {
let (response, data) = try handler(self.request)
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
self.client?.urlProtocol(self, didLoad: data)
self.client?.urlProtocolDidFinishLoading(self)
} catch {
self.client?.urlProtocol(self, didFailWithError: error)
}
}
override func stopLoading() {}
}