Files
2026-07-13 12:22:33 +08:00

1521 lines
54 KiB
Swift

import Commander
import Foundation
import Testing
#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
@testable import CodexBarCLI
@testable import CodexBarCore
// Cache state-machine coverage is intentionally kept together for sequence readability.
// swiftlint:disable:next type_body_length
struct CLIServeRouterTests {
@Test
func `termination monitor handles interactive and hangup signals`() {
#expect(CLITerminationSignalMonitor.signalNumbers == [SIGINT, SIGTERM, SIGHUP])
}
@Test
func `local http parser accepts only loopback host headers`() throws {
let allowedHosts = [
"localhost",
"localhost.",
"localhost:8080",
"127.0.0.1",
"127.0.0.1:8080",
"[::1]",
"[::1]:8080",
]
for host in allowedHosts {
let request = try Self.parsedRequest(host: host)
#expect(request.host == host)
#expect(request.path == "/usage")
}
}
@Test
func `local http parser rejects hostile missing and duplicate hosts`() {
Self.expectParseFailure(raw: "GET /usage HTTP/1.1\r\n\r\n", .missingHost)
Self.expectParseFailure(raw: "GET /usage HTTP/1.1\r\nHost: evil.test\r\n\r\n", .disallowedHost)
Self.expectParseFailure(raw: "GET /usage HTTP/1.1\r\nHost: localhost, evil.test\r\n\r\n", .disallowedHost)
Self.expectParseFailure(raw: "GET /usage HTTP/1.1\r\nHost: localhost:abc\r\n\r\n", .disallowedHost)
Self.expectParseFailure(
raw: "GET /usage HTTP/1.1\r\nHost: localhost\r\nHost: 127.0.0.1\r\n\r\n",
.duplicateHost)
}
@Test
func `routes health usage and cost endpoints`() throws {
#expect(try CLIServeRouter.route(method: "GET", path: "/health", queryItems: [:]) == .health)
#expect(try CLIServeRouter.route(method: "GET", path: "/usage", queryItems: [:]) == .usage(provider: nil))
#expect(
try CLIServeRouter.route(
method: "GET",
path: "/usage",
queryItems: ["provider": "claude"]) == .usage(provider: "claude"))
#expect(
try CLIServeRouter.route(
method: "GET",
path: "/cost",
queryItems: ["provider": "codex"]) == .cost(provider: "codex"))
}
@Test
func `rejects non get methods`() {
do {
_ = try CLIServeRouter.route(method: "POST", path: "/usage", queryItems: [:])
Issue.record("Expected methodNotAllowed")
} catch let error as CLIServeRouteError {
#expect(error == .methodNotAllowed)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test
func `rejects unknown paths`() {
do {
_ = try CLIServeRouter.route(method: "GET", path: "/missing", queryItems: [:])
Issue.record("Expected notFound")
} catch let error as CLIServeRouteError {
#expect(error == .notFound)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test
func `health response reports ok status and build version`() throws {
let response = CodexBarCLI.serveHealthResponse(version: "1.2.3")
#expect(response.status == .ok)
let object = try JSONSerialization.jsonObject(with: response.body) as? [String: Any]
#expect(object?["status"] as? String == "ok")
#expect(object?["version"] as? String == "1.2.3")
}
@Test
func `health response omits version detail when unavailable`() throws {
let response = CodexBarCLI.serveHealthResponse(version: nil)
#expect(response.status == .ok)
let object = try JSONSerialization.jsonObject(with: response.body) as? [String: Any]
#expect(object?["status"] as? String == "ok")
#expect(object?.keys.contains("version") == false)
}
@Test
func `serve numeric options reject malformed values`() {
#expect(CodexBarCLI.decodeServePort(from: ParsedValues(
positional: [],
options: ["port": ["abc"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServePort(from: ParsedValues(
positional: [],
options: ["port": ["0"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServePort(from: ParsedValues(
positional: [],
options: ["port": ["65536"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServePort(from: ParsedValues(
positional: [],
options: [:],
flags: [])) == 8080)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: ["refreshInterval": ["later"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: ["refreshInterval": ["-1"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: ["refreshInterval": ["inf"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: ["refreshInterval": ["86401"]],
flags: [])) == 86401)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: ["refreshInterval": ["86400"]],
flags: [])) == 86400)
#expect(CodexBarCLI.decodeServeRefreshInterval(from: ParsedValues(
positional: [],
options: [:],
flags: [])) == 60)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: ["requestTimeout": ["soon"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: ["requestTimeout": ["-0.5"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: ["requestTimeout": ["inf"]],
flags: [])) == nil)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: ["requestTimeout": ["0"]],
flags: [])) == 0)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: ["requestTimeout": ["12.5"]],
flags: [])) == 12.5)
#expect(CodexBarCLI.decodeServeRequestTimeout(from: ParsedValues(
positional: [],
options: [:],
flags: [])) == 30)
}
@Test
func `serve help documents request timeout option`() {
let serve = CodexBarCLI.serveHelp(version: "0.0.0")
let root = CodexBarCLI.rootHelp(version: "0.0.0")
#expect(serve.contains("--request-timeout <seconds>"))
#expect(serve.contains("codexbar serve --port 8080 --refresh-interval 60 --request-timeout 30"))
#expect(root.contains("--request-timeout <seconds>"))
}
@Test
func `serve config snapshot reflects provider changes`() throws {
let store = testConfigStore(suiteName: "CLIServeRouterTests-serve-config-freshness-\(UUID().uuidString)")
defer { try? store.deleteIfPresent() }
var firstConfig = CodexBarConfig.makeDefault()
firstConfig.setProviderConfig(ProviderConfig(id: .opencodego, enabled: false))
try store.save(firstConfig)
let firstSnapshot = try CodexBarCLI.loadServeConfigSnapshot(configStore: store)
var secondConfig = firstConfig
secondConfig.setProviderConfig(ProviderConfig(id: .opencodego, enabled: true))
try store.save(secondConfig)
let secondSnapshot = try CodexBarCLI.loadServeConfigSnapshot(configStore: store)
#expect(!firstSnapshot.config.enabledProviders().contains(.opencodego))
#expect(secondSnapshot.config.enabledProviders().contains(.opencodego))
#expect(firstSnapshot.cacheToken != secondSnapshot.cacheToken)
let operationKey = try CodexBarCLI.serveOperationKey(kind: "usage", provider: nil)
#expect(try operationKey == (CodexBarCLI.serveOperationKey(kind: "usage", provider: nil)))
#expect(
CodexBarCLI.serveCacheKey(operationKey: operationKey, configToken: firstSnapshot.cacheToken) !=
CodexBarCLI.serveCacheKey(operationKey: operationKey, configToken: secondSnapshot.cacheToken))
}
@Test
func `serve cache skips provider error payloads`() {
let success = CLILocalHTTPResponse(
status: .ok,
body: Data(#"[{"provider":"codex","source":"local"}]"#.utf8))
let providerError = CLILocalHTTPResponse(
status: .ok,
body: Data(#"[{"provider":"codex","source":"local","error":{"message":"temporary"}}]"#.utf8))
let routeError = CLILocalHTTPResponse(
status: .badRequest,
body: Data(#"{"error":"bad request"}"#.utf8))
#expect(CodexBarCLI.shouldCacheServeResponse(success))
#expect(!CodexBarCLI.shouldCacheServeResponse(providerError))
#expect(!CodexBarCLI.shouldCacheServeResponse(routeError))
}
@Test
func `serve provider timeout stays below the request deadline`() throws {
let thirtySecondTimeout = try #require(CodexBarCLI.serveProviderTimeout(requestTimeout: 30))
let tenSecondTimeout = try #require(CodexBarCLI.serveProviderTimeout(requestTimeout: 10))
#expect(abs(thirtySecondTimeout - 24) < 1e-9)
#expect(abs(tenSecondTimeout - 8) < 1e-9)
// Outer deadline disabled (0) or non-finite: add no serve-level provider bound.
#expect(CodexBarCLI.serveProviderTimeout(requestTimeout: 0) == nil)
#expect(CodexBarCLI.serveProviderTimeout(requestTimeout: .infinity) == nil)
// Finite deadlines stay strictly below the request timeout at every
// value, including sub-second ones.
let oneSecondTimeout = try #require(CodexBarCLI.serveProviderTimeout(requestTimeout: 1))
let halfSecondTimeout = try #require(CodexBarCLI.serveProviderTimeout(requestTimeout: 0.5))
#expect(oneSecondTimeout < 1)
#expect(abs(halfSecondTimeout - 0.4) < 1e-9)
// Oversized finite deadlines share the outer 24-hour cap and cannot
// overflow Duration conversion.
let oversizedTimeout = try #require(CodexBarCLI.serveProviderTimeout(
requestTimeout: .greatestFiniteMagnitude))
#expect(abs(oversizedTimeout - 69120) < 1e-9)
#expect(oversizedTimeout < 86400)
}
@Test
func `serve usage collection bounds a hung provider without blocking others`() async {
let providers: [UsageProvider] = [.codex, .claude, .gemini]
let start = Date()
let output = await CodexBarCLI.serveCollectUsageOutputs(
providers: providers,
providerTimeout: 0.1)
{ provider in
if provider == .claude {
try? await Task.sleep(for: .seconds(30))
return UsageCommandOutput(sections: ["late:\(provider.rawValue)"])
}
return UsageCommandOutput(sections: ["ok:\(provider.rawValue)"])
}
let elapsed = Date().timeIntervalSince(start)
// The hung provider must not serialize or stall the others.
#expect(elapsed < 5)
// Fast providers render in caller order; the hung one yields no section.
#expect(output.sections == ["ok:codex", "ok:gemini"])
// The hung provider degrades to a single provider error row.
#expect(output.payload.count == 1)
#expect(output.payload.first?.provider == UsageProvider.claude.rawValue)
#expect(output.payload.first?.error != nil)
#expect(output.payload.first?.error?.kind == .provider)
// The timeout row is account-agnostic: it carries no cache key, so the
// cache's keyed last-good merge intentionally does not reconstruct it
// (a timeout cannot prove which account is active).
#expect(output.payload.first?.cacheAccountKey == nil)
#expect(output.payload.first?.account == nil)
#expect(output.exitCode == .failure)
}
@Test
func `serve usage collection adds no join bound when request deadline is disabled`() async {
let output = await CodexBarCLI.serveCollectUsageOutputs(
providers: [.codex, .claude],
providerTimeout: nil)
{ provider in
if provider == .codex {
try? await Task.sleep(for: .milliseconds(25))
}
return UsageCommandOutput(sections: ["ok:\(provider.rawValue)"])
}
#expect(output.sections == ["ok:codex", "ok:claude"])
#expect(output.payload.isEmpty)
#expect(output.exitCode == .success)
}
@Test
func `serve cache uses stable Codex account identities`() {
let storedID = UUID()
let firstProjection = Self.codexVisibleAccount(
id: "email-shaped-id",
workspaceAccountID: "workspace-1",
authFingerprint: "auth-1",
storedAccountID: storedID)
let reshapedProjection = Self.codexVisibleAccount(
id: "managed:\(storedID.uuidString)",
workspaceAccountID: "workspace-1",
authFingerprint: "auth-1",
storedAccountID: storedID)
let replacement = Self.codexVisibleAccount(
id: "email-shaped-id",
workspaceAccountID: "workspace-2",
authFingerprint: "auth-2",
storedAccountID: UUID())
let workspacePeer = Self.codexVisibleAccount(
id: "workspace-peer",
email: "other@example.com",
workspaceAccountID: "workspace-1",
authFingerprint: "auth-3",
storedAccountID: UUID())
let ambiguous = Self.codexVisibleAccount(
id: "email-only",
workspaceAccountID: nil,
authFingerprint: nil,
storedAccountID: nil)
let storedBeforeRefresh = Self.codexVisibleAccount(
id: "stored-before",
workspaceAccountID: nil,
authFingerprint: "old-auth",
storedAccountID: storedID)
let storedAfterRefresh = Self.codexVisibleAccount(
id: "stored-after",
workspaceAccountID: nil,
authFingerprint: "new-auth",
storedAccountID: storedID)
let firstKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: firstProjection)
let reshapedKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: reshapedProjection)
let replacementKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: replacement)
let workspacePeerKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: workspacePeer)
let storedBeforeKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: storedBeforeRefresh)
let storedAfterKey = CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: storedAfterRefresh)
#expect(firstKey == reshapedKey)
#expect(firstKey != replacementKey)
#expect(firstKey != workspacePeerKey)
#expect(storedBeforeKey == storedAfterKey)
#expect(CodexBarCLI.usageCacheAccountKey(
provider: .codex,
account: nil,
codexVisibleAccount: ambiguous) == nil)
#expect(CodexBarCLI.usageCacheAccountKey(
provider: .antigravity,
account: nil,
codexVisibleAccount: nil) == nil)
}
@Test
func `serve cache coalesces concurrent cache misses`() async {
let cache = CLIServeResponseCache()
let counter = ServeTestCounter()
let responses = await withTaskGroup(of: CLILocalHTTPResponse.self) { group -> [CLILocalHTTPResponse] in
for _ in 0..<5 {
group.addTask {
await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 60,
requestTimeout: 1)
{
let call = await counter.increment()
try? await Task.sleep(nanoseconds: 50_000_000)
return Self.response("[{\"provider\":\"codex\",\"call\":\(call)}]")
}
}
}
var responses: [CLILocalHTTPResponse] = []
for await response in group {
responses.append(response)
}
return responses
}
#expect(await counter.current() == 1)
#expect(Set(responses.map(Self.bodyString)).count == 1)
#expect(responses.allSatisfy { $0.status == .ok })
#expect(responses.allSatisfy { Self.bodyString($0).contains("\"call\":1") })
}
@Test
func `serve cache prunes expired config token entries`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage::old-config",
cache: cache,
refreshInterval: 0.001)
{
Self.response(#"[{"provider":"codex","config":"old"}]"#)
}
#expect(await cache.cachedEntryCount() == 1)
try await Task.sleep(nanoseconds: 20_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage::new-config",
cache: cache,
refreshInterval: 60)
{
Self.response(#"[{"provider":"codex","config":"new"}]"#)
}
#expect(await cache.cachedEntryCount() == 1)
}
@Test
func `serve cache does not cache timeouts and recovers on next success`() async {
let cache = CLIServeResponseCache()
let counter = ServeTestCounter()
let timeout = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 60,
requestTimeout: 0.01)
{
_ = await counter.increment()
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[{\"provider\":\"codex\",\"call\":1}]")
}
#expect(timeout.status == .gatewayTimeout)
#expect(Self.bodyString(timeout).contains("request timed out"))
// Timeout delivery can win the actor race just before the canceled
// source reports completion. A successor must not start in that gap.
for _ in 0..<1000 {
if await cache.operations.snapshot().operationCount == 0 {
break
}
await Task.yield()
}
#expect(await cache.operations.snapshot().operationCount == 0)
let success = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 60,
requestTimeout: 1)
{
let call = await counter.increment()
return Self.response("[{\"provider\":\"codex\",\"call\":\(call)}]")
}
#expect(success.status == .ok)
#expect(Self.bodyString(success).contains("\"call\":2"))
let cached = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 60,
requestTimeout: 1)
{
let call = await counter.increment()
return Self.response("[{\"provider\":\"codex\",\"call\":\(call)}]")
}
#expect(cached.status == .ok)
#expect(Self.bodyString(cached) == Self.bodyString(success))
#expect(await counter.current() == 2)
}
@Test
func `serve cache resumes coalesced waiters on timeout`() async {
let cache = CLIServeResponseCache()
let counter = ServeTestCounter()
let responses = await withTaskGroup(of: CLILocalHTTPResponse.self) { group -> [CLILocalHTTPResponse] in
for _ in 0..<4 {
group.addTask {
await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 60,
requestTimeout: 0.01)
{
_ = await counter.increment()
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[{\"provider\":\"codex\"}]")
}
}
}
var responses: [CLILocalHTTPResponse] = []
for await response in group {
responses.append(response)
}
return responses
}
#expect(await counter.current() == 1)
#expect(responses.count == 4)
#expect(responses.allSatisfy { $0.status == .gatewayTimeout })
#expect(responses.allSatisfy { Self.bodyString($0).contains("request timed out") })
}
@Test
func `serve cache serves last good payload when refresh fails`() async {
let cache = CLIServeResponseCache()
let counter = ServeTestCounter()
let first = await CodexBarCLI.cachedServeResponse(
key: "usage:antigravity",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
let call = await counter.increment()
return Self.response("[{\"provider\":\"antigravity\",\"call\":\(call)}]")
}
#expect(first.status == .ok)
// Let the fresh cache entry expire so the next request re-fetches.
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:antigravity",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
_ = await counter.increment()
return Self.response(
"[{\"provider\":\"antigravity\",\"error\":{\"message\":\"transient\"}}]")
}
// Transient failure is masked by the last good payload.
#expect(failed.status == .ok)
let failedRows = try? Self.jsonRows(failed)
#expect(failedRows?.first?["call"] as? Int == 1)
#expect(await counter.current() == 2)
try? await Task.sleep(nanoseconds: 100_000_000)
let recovered = await CodexBarCLI.cachedServeResponse(
key: "usage:antigravity",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
let call = await counter.increment()
return Self.response("[{\"provider\":\"antigravity\",\"call\":\(call)}]")
}
#expect(recovered.status == .ok)
#expect(Self.bodyString(recovered).contains("\"call\":3"))
}
@Test
func `cost refresh timeout serves the last good payload`() async throws {
let cache = CLIServeResponseCache()
let counter = ServeTestCounter()
let first = await CodexBarCLI.cachedServeResponse(
key: "cost:",
cache: cache,
refreshInterval: 0.01,
requestTimeout: 1)
{
let call = await counter.increment()
return Self.response("[{\"provider\":\"codex\",\"call\":\(call)}]")
}
try? await Task.sleep(nanoseconds: 30_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "cost:",
cache: cache,
refreshInterval: 0.01,
requestTimeout: 0.01)
{
_ = await counter.increment()
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[{\"provider\":\"codex\",\"call\":2}]")
}
#expect(timedOut.status == .ok)
let firstRows = try Self.jsonRows(first)
let timedOutRows = try Self.jsonRows(timedOut)
#expect(firstRows.first?["provider"] as? String == "codex")
#expect(timedOutRows.first?["provider"] as? String == "codex")
#expect(firstRows.first?["call"] as? Int == 1)
#expect(timedOutRows.first?["call"] as? Int == 1)
#expect(await counter.current() == 2)
}
@Test
func `cost refresh keeps fresh providers while replacing timed out rows`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "cost:",
cache: cache,
refreshInterval: 0.01,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":1},
{"provider":"claude","call":1}
]
""")
}
try? await Task.sleep(nanoseconds: 30_000_000)
let partial = await CodexBarCLI.cachedServeResponse(
key: "cost:",
cache: cache,
refreshInterval: 0.01,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"claude","error":{"message":"claude cost refresh timed out"}}
]
""")
}
let partialRows = try Self.jsonRows(partial)
#expect(Self.row(partialRows, provider: "codex")?["call"] as? Int == 2)
#expect(Self.row(partialRows, provider: "claude")?["call"] as? Int == 1)
#expect(partialRows.allSatisfy { $0["error"] == nil })
try? await Task.sleep(nanoseconds: 30_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "cost:",
cache: cache,
refreshInterval: 0.01,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response(#"[{"provider":"codex","call":3}]"#)
}
let timeoutRows = try Self.jsonRows(timedOut)
#expect(Self.row(timeoutRows, provider: "codex")?["call"] as? Int == 2)
#expect(Self.row(timeoutRows, provider: "claude")?["call"] as? Int == 1)
}
@Test
func `serve cache replaces only failed provider account rows`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","account":"personal","call":1},
{"provider":"antigravity","account":"work","call":1},
{"provider":"antigravity","account":"personal","call":1}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let refreshed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","account":"personal","call":2},
{"provider":"antigravity","account":"work","error":{"message":"transient"}},
{"provider":"antigravity","account":"personal","call":2}
]
""")
}
let rows = try Self.jsonRows(refreshed)
#expect(Self.row(rows, provider: "codex", account: "personal")?["call"] as? Int == 2)
#expect(Self.row(rows, provider: "antigravity", account: "work")?["call"] as? Int == 1)
#expect(Self.row(rows, provider: "antigravity", account: "personal")?["call"] as? Int == 2)
#expect(rows.allSatisfy { $0["error"] == nil })
}
@Test
func `serve cache retains newer per-row success across all-error refresh`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":1},
{"provider":"antigravity","call":1}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","error":{"message":"transient"}},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
let rows = try Self.jsonRows(failed)
#expect(Self.row(rows, provider: "codex")?["call"] as? Int == 2)
#expect(Self.row(rows, provider: "antigravity")?["call"] as? Int == 1)
}
@Test
func `serve cache fails closed on timeout after merged rows`() async {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":1},
{"provider":"antigravity","call":1}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[]")
}
#expect(timedOut.status == .gatewayTimeout)
#expect(!Self.bodyString(timedOut).contains("\"call\":1"))
#expect(!Self.bodyString(timedOut).contains("\"call\":2"))
}
@Test
func `serve cache fails closed on timeout after a partial refresh`() async {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(#"[{"provider":"codex","call":1}]"#)
}
try? await Task.sleep(nanoseconds: 100_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[]")
}
#expect(timedOut.status == .gatewayTimeout)
#expect(!Self.bodyString(timedOut).contains("\"call\":2"))
#expect(!Self.bodyString(timedOut).contains("antigravity"))
}
@Test
func `serve cache does not reconstruct usage rows after timeout`() async {
let cache = CLIServeResponseCache()
let policy = CLIServeResponseCache.CachePolicy(ttl: 0, staleTTL: 10)
let startedAt = Date(timeIntervalSince1970: 1000)
_ = await cache.completeFetch(
Self.response(
"""
[
{"provider":"codex","call":1},
{"provider":"antigravity","call":1}
]
"""),
for: "usage:",
policy: policy,
now: startedAt,
shouldCache: true)
let partialAt = startedAt.addingTimeInterval(9)
_ = await cache.completeFetch(
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"antigravity","error":{"message":"transient"}}
]
"""),
for: "usage:",
policy: policy,
now: partialAt,
shouldCache: false)
let timeoutAt = startedAt.addingTimeInterval(11)
let timedOut = await cache.completeFetch(
Self.response(#"{"error":"request timed out"}"#, status: .gatewayTimeout),
for: "usage:",
policy: policy,
now: timeoutAt,
shouldCache: false)
#expect(timedOut.status == .gatewayTimeout)
#expect(Self.bodyString(timedOut).contains("request timed out"))
#expect(!Self.bodyString(timedOut).contains("\"call\":2"))
}
@Test
func `serve cache preserves newer row when another failed row has no fallback`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(#"[{"provider":"codex","call":1}]"#)
}
try? await Task.sleep(nanoseconds: 100_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","call":2},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","error":{"message":"transient"}},
{"provider":"antigravity","error":{"message":"transient"}}
]
""")
}
let rows = try Self.jsonRows(failed)
#expect(Self.row(rows, provider: "codex")?["call"] as? Int == 2)
#expect(Self.row(rows, provider: "antigravity")?["error"] != nil)
}
@Test
func `serve cache keeps fresh rows when a failed row has no stale match`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(#"[{"provider":"codex","account":"personal","call":1}]"#)
}
try? await Task.sleep(nanoseconds: 100_000_000)
let refreshed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","account":"personal","call":2},
{"provider":"antigravity","account":"work","error":{"message":"transient"}}
]
""")
}
let rows = try Self.jsonRows(refreshed)
#expect(Self.row(rows, provider: "codex", account: "personal")?["call"] as? Int == 2)
#expect(Self.row(rows, provider: "antigravity", account: "work")?["error"] != nil)
}
@Test
func `serve cache does not merge duplicate provider account labels`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","account":"shared","slot":"first","call":1},
{"provider":"codex","account":"shared","slot":"second","call":1}
]
""")
}
try? await Task.sleep(nanoseconds: 100_000_000)
let refreshed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{
"provider":"codex",
"account":"shared",
"slot":"first",
"error":{"message":"transient"}
},
{"provider":"codex","account":"shared","slot":"second","call":2}
]
""")
}
let rows = try Self.jsonRows(refreshed)
let first = rows.first { $0["slot"] as? String == "first" }
let second = rows.first { $0["slot"] as? String == "second" }
#expect(first?["error"] != nil)
#expect(second?["call"] as? Int == 2)
}
@Test
func `serve cache follows stable account identity across label changes`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"codex","account":"old label","call":1}]"#,
usageCacheKeys: ["account-1"])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"codex","account":"new label","error":{"message":"transient"}}]"#,
usageCacheKeys: ["account-1"])
}
let row = try #require(Self.jsonRows(failed).first)
#expect(row["account"] as? String == "old label")
#expect(row["call"] as? Int == 1)
}
@Test
func `serve cache does not reuse a label for a different account identity`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"codex","account":"shared","call":1}]"#,
usageCacheKeys: ["account-1"])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let refreshed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
"""
[
{"provider":"codex","account":"shared","error":{"message":"transient"}},
{"provider":"antigravity","account":"work","call":2}
]
""",
usageCacheKeys: ["account-2", "account-3"])
}
let rows = try Self.jsonRows(refreshed)
#expect(Self.row(rows, provider: "codex", account: "shared")?["error"] != nil)
#expect(Self.row(rows, provider: "antigravity", account: "work")?["call"] as? Int == 2)
}
@Test
func `serve cache does not use whole fallback after an account switch`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"codex","account":"shared","call":1}]"#,
usageCacheKeys: ["account-1"])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"codex","account":"shared","error":{"message":"transient"}}]"#,
usageCacheKeys: ["account-2"])
}
let row = try #require(Self.jsonRows(failed).first)
#expect(row["call"] == nil)
#expect(row["error"] != nil)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response(
#"[{"provider":"codex","account":"shared","call":3}]"#,
usageCacheKeys: ["account-2"])
}
#expect(timedOut.status == .gatewayTimeout)
#expect(!Self.bodyString(timedOut).contains("\"call\":1"))
}
@Test
func `serve cache prunes accounts absent from a successful snapshot`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(#"[{"provider":"codex","account":"shared","call":1}]"#)
}
try? await Task.sleep(nanoseconds: 100_000_000)
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(#"[{"provider":"antigravity","account":"work","call":2}]"#)
}
try? await Task.sleep(nanoseconds: 100_000_000)
let refreshed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response("""
[
{"provider":"codex","account":"shared","error":{"message":"transient"}},
{"provider":"antigravity","account":"work","call":3}
]
""")
}
let rows = try Self.jsonRows(refreshed)
#expect(Self.row(rows, provider: "codex", account: "shared")?["error"] != nil)
#expect(Self.row(rows, provider: "antigravity", account: "work")?["call"] as? Int == 3)
}
@Test
func `serve cache fails closed when all-error rows have ambiguous identities`() async throws {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
"""
[
{"provider":"codex","account":"shared","slot":"first","call":1},
{"provider":"codex","account":"shared","slot":"second","call":1},
{"provider":"antigravity","account":"work","call":1}
]
""",
usageCacheKeys: [nil, nil, nil])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let failed = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
"""
[
{
"provider":"codex",
"account":"shared",
"slot":"first",
"error":{"message":"transient"}
},
{
"provider":"codex",
"account":"shared",
"slot":"second",
"error":{"message":"transient"}
},
{"provider":"antigravity","account":"work","error":{"message":"transient"}}
]
""",
usageCacheKeys: [nil, nil, nil])
}
let rows = try Self.jsonRows(failed)
#expect(rows.count == 3)
#expect(rows.allSatisfy { $0["call"] == nil })
#expect(rows.allSatisfy { $0["error"] != nil })
}
@Test
func `serve cache does not whole-fallback ambiguous usage after timeout`() async {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
#"[{"provider":"antigravity","account":"first@example.com","call":1}]"#,
usageCacheKeys: [nil])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response(
#"[{"provider":"antigravity","account":"second@example.com","call":2}]"#,
usageCacheKeys: [nil])
}
#expect(timedOut.status == .gatewayTimeout)
#expect(!Self.bodyString(timedOut).contains("first@example.com"))
#expect(!Self.bodyString(timedOut).contains("\"call\":1"))
}
@Test
func `serve cache mixed identities do not enable timeout fallback`() async {
let cache = CLIServeResponseCache()
_ = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 1)
{
Self.response(
"""
[
{"provider":"codex","account":"stable@example.com","call":1},
{"provider":"antigravity","account":"ambient@example.com","call":1}
]
""",
usageCacheKeys: ["account-1", nil])
}
try? await Task.sleep(nanoseconds: 100_000_000)
let timedOut = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0.05,
requestTimeout: 0.01)
{
try? await Task.sleep(nanoseconds: 200_000_000)
return Self.response("[]", usageCacheKeys: [])
}
#expect(timedOut.status == .gatewayTimeout)
#expect(!Self.bodyString(timedOut).contains("stable@example.com"))
#expect(!Self.bodyString(timedOut).contains("ambient@example.com"))
}
@Test
func `serve stale ttl is bounded and disabled without caching`() {
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: 0) == 0)
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: 1) == 300)
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: 60) == 600)
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: 1800) == 3600)
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: 86401) == 3600)
#expect(CodexBarCLI.serveStaleTTL(refreshInterval: .infinity) == 3600)
}
@Test
func `serve cache prunes stale variants from old configurations`() async {
let cache = CLIServeResponseCache()
let startedAt = Date(timeIntervalSince1970: 1000)
let policy = CLIServeResponseCache.CachePolicy(
ttl: 0,
staleTTL: CLIServeResponseCache.maximumStaleTTL)
_ = await cache.completeFetch(
Self.response(#"{"status":"ok"}"#),
for: "config:old",
policy: policy,
now: startedAt,
shouldCache: true)
_ = await cache.completeFetch(
Self.response(
#"[{"provider":"codex","call":1}]"#,
usageCacheKeys: ["account-1"]),
for: "usage:old",
policy: policy,
now: startedAt,
shouldCache: true)
#expect(await cache.cachedStaleVariantCount() == 2)
let expiredAt = startedAt.addingTimeInterval(CLIServeResponseCache.maximumStaleTTL + 1)
_ = await cache.cachedResponse(for: "config:new", now: expiredAt)
#expect(await cache.cachedStaleVariantCount() == 0)
_ = await cache.completeFetch(
Self.response(#"{"status":"ok"}"#),
for: "config:new",
policy: policy,
now: expiredAt,
shouldCache: false)
}
@Test
func `serve helper idle window outlives the refresh cadence`() {
#expect(CodexBarCLI.serveCLISessionIdleWindow(refreshInterval: 0) == 180)
#expect(CodexBarCLI.serveCLISessionIdleWindow(refreshInterval: 60) == 180)
#expect(CodexBarCLI.serveCLISessionIdleWindow(refreshInterval: 300) == 360)
}
@Test
func `local HTTP server stops its accept loop`() async throws {
let listening = ServeListeningSignal()
let server = CLILocalHTTPServer(host: "127.0.0.1", port: 0) { _ in
Self.response(#"{"status":"ok"}"#)
}
let task = Task {
try await server.run {
listening.signal()
}
}
await listening.wait()
server.stop()
try await task.value
}
@Test
func `serve request timeout zero disables the deadline`() async {
let cache = CLIServeResponseCache()
let response = await CodexBarCLI.cachedServeResponse(
key: "usage:",
cache: cache,
refreshInterval: 0,
requestTimeout: 0)
{
try? await Task.sleep(nanoseconds: 80_000_000)
return Self.response("[{\"provider\":\"codex\",\"slow\":true}]")
}
#expect(response.status == .ok)
#expect(Self.bodyString(response).contains("\"slow\":true"))
}
private static func parsedRequest(host: String) throws -> CLILocalHTTPRequest {
let raw = "GET /usage?provider=claude HTTP/1.1\r\nHost: \(host)\r\n\r\n"
return try CLILocalHTTPRequest.parse(Data(raw.utf8)).get()
}
private static func expectParseFailure(raw: String, _ expected: CLILocalHTTPRequestParseError) {
switch CLILocalHTTPRequest.parse(Data(raw.utf8)) {
case .success:
Issue.record("Expected \(expected)")
case let .failure(error):
#expect(error == expected)
}
}
private static func response(
_ body: String,
status: CLIHTTPStatus = .ok,
usageCacheKeys: [String?]? = nil) -> CLILocalHTTPResponse
{
let data = Data(body.utf8)
return CLILocalHTTPResponse(
status: status,
body: data,
usageCacheKeys: usageCacheKeys ?? Self.syntheticUsageCacheKeys(data))
}
private static func bodyString(_ response: CLILocalHTTPResponse) -> String {
String(data: response.body, encoding: .utf8) ?? ""
}
private static func jsonRows(_ response: CLILocalHTTPResponse) throws -> [[String: Any]] {
try #require(JSONSerialization.jsonObject(with: response.body) as? [[String: Any]])
}
private static func row(
_ rows: [[String: Any]],
provider: String,
account: String) -> [String: Any]?
{
rows.first {
$0["provider"] as? String == provider
&& $0["account"] as? String == account
}
}
private static func row(_ rows: [[String: Any]], provider: String) -> [String: Any]? {
rows.first { $0["provider"] as? String == provider }
}
private static func syntheticUsageCacheKeys(_ data: Data) -> [String?]? {
guard let rows = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] else { return nil }
return rows.map { row in
guard let provider = row["provider"] as? String else { return nil }
let account = row["account"] as? String ?? "default"
return "test:\(provider):\(account)"
}
}
private static func codexVisibleAccount(
id: String,
email: String = "user@example.com",
workspaceAccountID: String?,
authFingerprint: String?,
storedAccountID: UUID?) -> CodexVisibleAccount
{
CodexVisibleAccount(
id: id,
email: email,
workspaceAccountID: workspaceAccountID,
authFingerprint: authFingerprint,
storedAccountID: storedAccountID,
selectionSource: .liveSystem,
isActive: true,
isLive: true,
canReauthenticate: true,
canRemove: false)
}
}
private actor ServeTestCounter {
private var value = 0
func increment() -> Int {
self.value += 1
return self.value
}
func current() -> Int {
self.value
}
}
private final class ServeListeningSignal: @unchecked Sendable {
private let lock = NSLock()
private var continuation: CheckedContinuation<Void, Never>?
private var isSignaled = false
func signal() {
let continuation = self.lock.withLock {
self.isSignaled = true
defer { self.continuation = nil }
return self.continuation
}
continuation?.resume()
}
func wait() async {
await withCheckedContinuation { continuation in
let shouldResume = self.lock.withLock {
guard !self.isSignaled else { return true }
self.continuation = continuation
return false
}
if shouldResume {
continuation.resume()
}
}
}
}