项目文件夹

文件
2026-07-13 12:22:33 +08:00

1192 行
43 KiB
Swift

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import CodexBarCore
import Foundation
import Testing
@testable import CodexBarCLI
// swiftlint:disable:next type_body_length
struct CLISnapshotTests {
@Test
func `renders Gemini paid plan without changing acronym casing`() {
let identity = ProviderIdentitySnapshot(
providerID: .gemini,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "Gemini Code Assist in Google One AI Pro")
let snapshot = UsageSnapshot(
primary: nil,
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: identity)
let output = CLIRenderer.renderText(
provider: .gemini,
snapshot: snapshot,
credits: nil,
context: RenderContext(
header: "Gemini",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(CLIRenderer.planBadgeText(provider: .gemini, snapshot: snapshot) ==
"Gemini Code Assist in Google One AI Pro")
#expect(output.contains("Plan: Gemini Code Assist in Google One AI Pro"))
#expect(!output.contains("Google One Ai Pro"))
}
@Test
func `renders Factory token rate billing with time window labels`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 25, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: .init(usedPercent: 50, windowMinutes: 43200, resetsAt: nil, resetDescription: nil),
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .factory,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Droid (factory)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("5-hour: 88% left"))
#expect(output.contains("Weekly: 75% left"))
#expect(output.contains("Monthly: 50% left"))
#expect(!output.contains("Standard:"))
#expect(!output.contains("Premium:"))
}
@Test
func `renders Factory legacy billing with pool labels`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 12, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 25, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .factory,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Droid (factory)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Standard: 88% left"))
#expect(output.contains("Premium: 75% left"))
#expect(!output.contains("5-hour:"))
#expect(!output.contains("Monthly:"))
}
@Test
func `renders text snapshot for codex`() {
let identity = ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: "user@example.com",
accountOrganization: nil,
loginMethod: "pro")
let snap = UsageSnapshot(
primary: .init(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: "today at 3:00 PM"),
secondary: .init(usedPercent: 25, windowMinutes: 10080, resetsAt: nil, resetDescription: "Fri at 9:00 AM"),
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: identity)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: CreditsSnapshot(remaining: 42, events: [], updatedAt: Date()),
context: RenderContext(
header: "Codex 1.2.3 (codex-cli)",
status: ProviderStatusPayload(
indicator: .minor,
description: "Degraded performance",
updatedAt: Date(timeIntervalSince1970: 0),
url: "https://status.example.com"),
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Codex 1.2.3 (codex-cli)"))
#expect(output.contains("Status: Partial outage – Degraded performance"))
#expect(output.contains("Codex"))
#expect(output.contains("Session: 88% left"))
#expect(output.contains("Weekly: 75% left"))
#expect(output.contains("Credits: 42"))
#expect(output.contains("Account: user@example.com"))
#expect(output.contains("Plan: Pro 20x"))
}
@Test
func `renders Codex limit reset credits`() {
let now = Date()
let expiresAt = now.addingTimeInterval(7200)
let resetCredits = CodexRateLimitResetCreditsSnapshot(
credits: [
CodexRateLimitResetCredit(
id: "credit-1",
resetType: "codex_rate_limits",
status: .available,
grantedAt: Date(timeIntervalSince1970: 0),
expiresAt: expiresAt,
redeemStartedAt: nil,
redeemedAt: nil,
title: nil,
description: nil),
CodexRateLimitResetCredit(
id: "expired-credit",
resetType: "codex_rate_limits",
status: .available,
grantedAt: Date(timeIntervalSince1970: 0),
expiresAt: now,
redeemStartedAt: nil,
redeemedAt: nil,
title: nil,
description: nil),
],
availableCount: 99,
updatedAt: Date(timeIntervalSince1970: 0))
let snapshot = UsageSnapshot(
primary: .init(usedPercent: 10, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: nil,
tertiary: nil,
codexResetCredits: resetCredits,
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snapshot,
credits: nil,
context: RenderContext(
header: "Codex (oauth)",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(output.contains("Limit Reset Credits: 1 available"))
#expect(output.contains("Next reset credit expires"))
}
@Test
func `renders Codex prolite plan with multiplier display name`() {
let identity = ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: "user@example.com",
accountOrganization: nil,
loginMethod: "prolite")
let snap = UsageSnapshot(
primary: .init(usedPercent: 12, windowMinutes: 300, resetsAt: nil, resetDescription: "today at 3:00 PM"),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: identity)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 1.2.3 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Plan: Pro 5x"))
#expect(!output.contains("Plan: Pro Lite"))
#expect(!output.contains("Plan: Prolite"))
}
@Test
func `renders Codex plan only limits as unavailable`() {
let identity = ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: "user@example.com",
accountOrganization: nil,
loginMethod: "pro")
let snap = UsageSnapshot(
primary: nil,
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: identity)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 1.2.3 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Limits: not available"))
#expect(output.contains("Account: user@example.com"))
#expect(output.contains("Plan: Pro 20x"))
#expect(!output.contains("Session:"))
#expect(!output.contains("Weekly:"))
}
@Test
func `renders text snapshot for claude without weekly`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 2, windowMinutes: nil, resetsAt: nil, resetDescription: "3pm (Europe/Vienna)"),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .claude,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Claude Code 2.0.69 (claude)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Session: 98% left"))
#expect(!output.contains("Weekly:"))
}
@Test
func `renders Claude Max multiplier without uppercasing x`() {
let identity = ProviderIdentitySnapshot(
providerID: .claude,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "Claude Max 5x")
let snapshot = UsageSnapshot(
primary: .init(usedPercent: 2, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: identity)
let output = CLIRenderer.renderText(
provider: .claude,
snapshot: snapshot,
credits: nil,
context: RenderContext(
header: "Claude (oauth)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Plan: Claude Max 5x"))
#expect(!output.contains("Plan: Claude Max 5X"))
}
@Test
func `renders warp unlimited as detail not reset`() {
let meta = ProviderDescriptorRegistry.descriptor(for: .warp).metadata
let snap = UsageSnapshot(
primary: .init(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: "Unlimited"),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0),
identity: ProviderIdentitySnapshot(
providerID: .warp,
accountEmail: nil,
accountOrganization: nil,
loginMethod: nil))
let output = CLIRenderer.renderText(
provider: .warp,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Warp 0.0.0 (warp)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("\(meta.sessionLabel): 100% left"))
#expect(!output.contains("Resets Unlimited"))
#expect(output.contains("Unlimited"))
}
@Test
func `renders warp credits as detail and reset as date`() {
let meta = ProviderDescriptorRegistry.descriptor(for: .warp).metadata
let now = Date(timeIntervalSince1970: 0)
let snap = UsageSnapshot(
primary: .init(
usedPercent: 10,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(3600),
resetDescription: "10/100 credits"),
secondary: nil,
tertiary: nil,
updatedAt: now,
identity: ProviderIdentitySnapshot(
providerID: .warp,
accountEmail: nil,
accountOrganization: nil,
loginMethod: nil))
let output = CLIRenderer.renderText(
provider: .warp,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Warp 0.0.0 (warp)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("\(meta.sessionLabel): 90% left"))
#expect(output.contains("Resets"))
#expect(output.contains("10/100 credits"))
#expect(!output.contains("Resets 10/100 credits"))
}
@Test
func `renders crof dollar balance as detail not reset`() {
let meta = ProviderDescriptorRegistry.descriptor(for: .crof).metadata
let snap = CrofUsageSnapshot(
credits: 9.9999,
requestsPlan: 1000,
usableRequests: 998,
updatedAt: Date(timeIntervalSince1970: 0)).toUsageSnapshot()
let output = CLIRenderer.renderText(
provider: .crof,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Crof",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(output.contains("\(meta.sessionLabel): 99% left"))
#expect(output.contains("\(meta.weeklyLabel): 100% left"))
#expect(output.contains("$9.99"))
#expect(!output.contains("Resets $9.99"))
}
@Test
func `renders qoder reset and credit total separately`() {
let meta = ProviderDescriptorRegistry.descriptor(for: .qoder).metadata
let now = Date(timeIntervalSince1970: 0)
let snap = UsageSnapshot(
primary: .init(
usedPercent: 25,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(3600),
resetDescription: "125 / 500 credits"),
secondary: nil,
tertiary: nil,
updatedAt: now,
identity: ProviderIdentitySnapshot(
providerID: .qoder,
accountEmail: nil,
accountOrganization: nil,
loginMethod: nil))
let output = CLIRenderer.renderText(
provider: .qoder,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Qoder",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
#expect(output.contains("\(meta.sessionLabel): 75% left"))
#expect(output.contains("Resets in 1h"))
#expect(output.contains("125 / 500 credits"))
#expect(!output.contains("Resets 125 / 500 credits"))
}
@Test
func `renders kilo plan activity and fallback note`() {
let now = Date(timeIntervalSince1970: 0)
let identity = ProviderIdentitySnapshot(
providerID: .kilo,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "Kilo Pass Pro · Auto top-up: visa")
let snap = UsageSnapshot(
primary: .init(usedPercent: 40, windowMinutes: nil, resetsAt: nil, resetDescription: "40/100 credits"),
secondary: nil,
tertiary: nil,
updatedAt: now,
identity: identity)
let output = CLIRenderer.renderText(
provider: .kilo,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Kilo (cli)",
status: nil,
useColor: false,
resetStyle: .absolute,
notes: ["Using CLI fallback"]))
#expect(output.contains("Credits: 60% left"))
#expect(output.contains("40/100 credits"))
#expect(!output.contains("Resets 40/100 credits"))
#expect(output.contains("Plan: Kilo Pass Pro"))
#expect(output.contains("Activity: Auto top-up: visa"))
#expect(output.contains("Note: Using CLI fallback"))
}
@Test
func `renders kilo zero total edge state as detail`() {
let now = Date(timeIntervalSince1970: 0)
let snap = KiloUsageSnapshot(
creditsUsed: 0,
creditsTotal: 0,
creditsRemaining: 0,
planName: "Kilo Pass Pro",
autoTopUpEnabled: true,
autoTopUpMethod: "visa",
updatedAt: now).toUsageSnapshot()
let output = CLIRenderer.renderText(
provider: .kilo,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Kilo (api)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Credits: 0% left"))
#expect(output.contains("0/0 credits"))
#expect(!output.contains("Resets 0/0 credits"))
}
@Test
func `renders kilo auto top up only as activity without plan`() {
let now = Date(timeIntervalSince1970: 0)
let identity = ProviderIdentitySnapshot(
providerID: .kilo,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "Auto top-up: off")
let snap = UsageSnapshot(
primary: nil,
secondary: nil,
tertiary: nil,
updatedAt: now,
identity: identity)
let output = CLIRenderer.renderText(
provider: .kilo,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Kilo (cli)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Activity: Auto top-up: off"))
#expect(!output.contains("Plan: Auto top-up: off"))
}
@Test
func `renders pace line when weekly has reset`() {
let now = Date()
let snap = UsageSnapshot(
primary: nil,
secondary: .init(
usedPercent: 50,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(3 * 24 * 60 * 60),
resetDescription: nil),
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(output.contains("Pace:"))
}
@Test
func `configured work days affect weekly text and JSON pace`() throws {
var calendar = Calendar.current
calendar.timeZone = .current
let resetsAt = try #require(calendar.date(from: DateComponents(
calendar: calendar,
timeZone: calendar.timeZone,
year: 2026,
month: 6,
day: 14)))
let now = resetsAt.addingTimeInterval(-72 * 60 * 60)
let snap = UsageSnapshot(
primary: nil,
secondary: .init(
usedPercent: 60,
windowMinutes: 10080,
resetsAt: resetsAt,
resetDescription: nil),
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .countdown,
weeklyWorkDays: 5),
now: now)
#expect(output.contains("Pace: On pace | Expected 60% used | Lasts until reset"))
let pace = try #require(CLIRenderer.providerPacePayload(
provider: .codex,
snapshot: snap,
weeklyWorkDays: 5,
now: now)?.secondary)
#expect(pace.expectedUsedPercent == 60)
#expect(pace.summary == "On pace | Expected 60% used | Lasts until reset")
}
@Test
func `renders Ollama weekly pace line when weekly window has reset`() {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 0,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(4 * 3600),
resetDescription: nil),
secondary: .init(
usedPercent: 23,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(5 * 24 * 3600),
resetDescription: nil),
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .ollama,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Ollama (web)",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(output.contains("Weekly: 77% left"))
#expect(output.contains("Pace: 6% in reserve | Expected 29% used | Lasts until reset"))
#expect(!output.contains("1.5× headroom"))
}
@Test
func `hides Ollama weekly pace when weekly duration is missing`() {
let now = Date()
let snap = UsageSnapshot(
primary: nil,
secondary: .init(
usedPercent: 23,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(5 * 24 * 3600),
resetDescription: nil),
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .ollama,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Ollama (web)",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(output.contains("Weekly: 77% left"))
#expect(!output.contains("Pace:"))
}
@Test
func `renders session pace line when session window has reset`() {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 20,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(2 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
#expect(output.contains("Session: 80% left"))
// 2h remaining of a 5h window => 3h elapsed => 60% expected; even rate easily lasts to reset.
#expect(output.contains("Pace: 40% in reserve | Expected 60% used | Lasts until reset | 1.5× headroom"))
}
@Test
func `renders Claude session pace using five hour default window`() {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 20,
windowMinutes: nil,
resetsAt: now.addingTimeInterval(2 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .claude,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Claude Code 2.0.69 (claude)",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
// windowMinutes is nil, so the 5-hour (300 minute) session default must drive the pace.
#expect(output.contains("Pace: 40% in reserve | Expected 60% used | Lasts until reset"))
#expect(!output.contains("1.5× headroom"))
}
@Test
func `renders session pace deficit with run out estimate`() {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 50,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(4 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
// 1h elapsed of a 5h window => 20% expected vs 50% used => burning ahead of pace.
// Session mirrors the GUI's "Projected empty" wording (weekly uses "Runs out").
#expect(output.contains("Pace: 30% in deficit | Expected 20% used | Projected empty in"))
#expect(!output.contains("Runs out"))
}
@Test
func `renders session pace on track and lasts until reset`() {
let now = Date(timeIntervalSince1970: 1_700_000_000)
// Exactly halfway through a 5h window with 50% used => On pace (delta 0); the even rate
// means the quota lasts precisely to the reset.
let snap = UsageSnapshot(
primary: .init(
usedPercent: 50,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(2.5 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
#expect(output.contains("Pace: On pace | Expected 50% used | Lasts until reset"))
}
@Test
func `hides session pace for unsupported provider`() {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 20,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(2 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .zai,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "z.ai 0.0.0 (zai)",
status: nil,
useColor: false,
resetStyle: .countdown))
#expect(!output.contains("Pace:"))
}
@Test
func `hides session pace for non-session primary window`() {
let now = Date(timeIntervalSince1970: 1_700_000_000)
// Claude with no 5-hour data falls a 7-day window back into `primary`; it must not be
// paced as a "Session" (that would print "Projected empty …" over a weekly window).
let snap = UsageSnapshot(
primary: .init(
usedPercent: 50,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(3 * 24 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let output = CLIRenderer.renderText(
provider: .claude,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Claude Code 2.0.69 (claude)",
status: nil,
useColor: false,
resetStyle: .countdown),
now: now)
#expect(!output.contains("Pace:"))
#expect(CLIRenderer.providerPacePayload(provider: .claude, snapshot: snap, now: now) == nil)
}
@Test
func `renders JSON payload`() throws {
let snap = UsageSnapshot(
primary: .init(usedPercent: 50, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 10, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
let payload = ProviderPayload(
provider: .codex,
account: nil,
version: "1.2.3",
source: "codex-cli",
status: ProviderStatusPayload(
indicator: .none,
description: nil,
updatedAt: Date(timeIntervalSince1970: 1_700_000_010),
url: "https://status.example.com"),
usage: snap,
credits: nil,
antigravityPlanInfo: nil,
openaiDashboard: nil,
error: nil)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let data = try encoder.encode(payload)
guard let json = String(data: data, encoding: .utf8) else {
Issue.record("Failed to decode JSON payload")
return
}
#expect(json.contains("\"provider\":\"codex\""))
#expect(json.contains("\"version\":\"1.2.3\""))
#expect(json.contains("\"status\""))
#expect(json.contains("status.example.com"))
#expect(json.contains("\"primary\""))
#expect(json.contains("\"windowMinutes\":300"))
#expect(json.contains("1700000000"))
}
@Test
func `json pace rounds derived numbers to match usage precision`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
// 13000s elapsed of an 18000s (300m) window => 72.22% expected; used 79 => +6.78 deficit;
// projected empty in ~3455.7s. Derived fields must be emitted as whole numbers (no float noise).
let snap = UsageSnapshot(
primary: .init(
usedPercent: 79,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(5000),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let payload = ProviderPayload(
provider: .codex,
account: nil,
version: nil,
source: "codex-cli",
status: nil,
usage: snap,
credits: nil,
antigravityPlanInfo: nil,
openaiDashboard: nil,
error: nil,
pace: CLIRenderer.providerPacePayload(provider: .codex, snapshot: snap, now: now))
let data = try JSONEncoder().encode(payload)
let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any])
let primary = try #require((root["pace"] as? [String: Any])?["primary"] as? [String: Any])
#expect(primary["expectedUsedPercent"] as? Double == 72)
#expect(primary["deltaPercent"] as? Double == 7)
#expect(primary["etaSeconds"] as? Double == 3456)
// actualUsedPercent is not emitted; consumers read usage.primary.usedPercent.
#expect(primary["actualUsedPercent"] == nil)
}
@Test
func `json payload includes session and weekly pace with distinct wording`() throws {
let now = Date(timeIntervalSince1970: 1_700_000_000)
let snap = UsageSnapshot(
// 1h elapsed of a 5h window => 20% expected vs 50% used => deficit, runs out in 1h.
primary: .init(
usedPercent: 50,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(4 * 60 * 60),
resetDescription: nil),
// 5d elapsed of a 7d window => ~71% expected vs 90% used => deficit, runs out before reset.
secondary: .init(
usedPercent: 90,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(2 * 24 * 60 * 60),
resetDescription: nil),
tertiary: nil,
updatedAt: now)
let payload = ProviderPayload(
provider: .codex,
account: nil,
version: "1.2.3",
source: "codex-cli",
status: nil,
usage: snap,
credits: nil,
antigravityPlanInfo: nil,
openaiDashboard: nil,
error: nil,
pace: CLIRenderer.providerPacePayload(provider: .codex, snapshot: snap, now: now))
let data = try JSONEncoder().encode(payload)
let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any])
let pace = try #require(root["pace"] as? [String: Any])
let primary = try #require(pace["primary"] as? [String: Any])
#expect(primary["stage"] as? String == "farAhead")
#expect(primary["expectedUsedPercent"] as? Double == 20)
#expect(primary["deltaPercent"] as? Double == 30)
#expect(primary["willLastToReset"] as? Bool == false)
#expect(primary["etaSeconds"] as? Double == 3600)
#expect((primary["summary"] as? String)?
.contains("30% in deficit | Expected 20% used | Projected empty in") == true)
// actualUsedPercent is redundant with usage.usedPercent and is not emitted;
// runOutProbability is never set by the CLI, so both keys are omitted.
#expect(primary["actualUsedPercent"] == nil)
#expect(primary["runOutProbability"] == nil)
let secondary = try #require(pace["secondary"] as? [String: Any])
#expect(secondary["stage"] as? String == "farAhead")
#expect((secondary["summary"] as? String)?.contains("Runs out in") == true)
#expect((secondary["summary"] as? String)?.contains("Projected empty") == false)
}
@Test
func `json omits pace when not applicable`() throws {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 20,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(2 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
// z.ai is not a session/weekly pace provider, so no pace should be emitted.
let payload = ProviderPayload(
provider: .zai,
account: nil,
version: nil,
source: "zai",
status: nil,
usage: snap,
credits: nil,
antigravityPlanInfo: nil,
openaiDashboard: nil,
error: nil,
pace: CLIRenderer.providerPacePayload(provider: .zai, snapshot: snap, now: now))
let data = try JSONEncoder().encode(payload)
let json = try #require(String(data: data, encoding: .utf8))
#expect(!json.contains("\"pace\""))
}
@Test
func `json includes only session pace when weekly window missing`() throws {
let now = Date()
let snap = UsageSnapshot(
primary: .init(
usedPercent: 50,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(4 * 60 * 60),
resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: now)
let payload = ProviderPayload(
provider: .codex,
account: nil,
version: nil,
source: "codex-cli",
status: nil,
usage: snap,
credits: nil,
antigravityPlanInfo: nil,
openaiDashboard: nil,
error: nil,
pace: CLIRenderer.providerPacePayload(provider: .codex, snapshot: snap, now: now))
let data = try JSONEncoder().encode(payload)
let root = try #require(try JSONSerialization.jsonObject(with: data) as? [String: Any])
let pace = try #require(root["pace"] as? [String: Any])
#expect(pace["primary"] is [String: Any])
#expect(pace["secondary"] == nil)
}
@Test
func `encodes JSON with secondary null when missing`() throws {
let snap = UsageSnapshot(
primary: .init(usedPercent: 0, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 1_700_000_000))
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .secondsSince1970
let data = try encoder.encode(snap)
guard let json = String(data: data, encoding: .utf8) else {
Issue.record("Failed to decode JSON payload")
return
}
#expect(json.contains("\"secondary\":null"))
}
@Test
func `parses output format`() {
#expect(OutputFormat(argument: "json") == .json)
#expect(OutputFormat(argument: "TEXT") == .text)
#expect(OutputFormat(argument: "invalid") == nil)
}
@Test
func `defaults to usage when no command provided`() {
#expect(CodexBarCLI.effectiveArgv([]) == ["usage"])
#expect(CodexBarCLI.effectiveArgv(["--format", "json"]).first == "usage")
#expect(CodexBarCLI.effectiveArgv(["usage", "--format", "json"]).first == "usage")
}
@Test
func `status line is last and colored when TTY`() {
let identity = ProviderIdentitySnapshot(
providerID: .claude,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "pro")
let snap = UsageSnapshot(
primary: .init(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 0, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: nil,
updatedAt: Date(),
identity: identity)
let output = CLIRenderer.renderText(
provider: .claude,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Claude Code 2.0.58 (claude)",
status: ProviderStatusPayload(
indicator: .critical,
description: "Major outage",
updatedAt: nil,
url: "https://status.claude.com"),
useColor: true,
resetStyle: .absolute))
let lines = output.split(separator: "\n")
#expect(lines.last?.contains("Status: Critical issue – Major outage") == true)
#expect(output.contains("\u{001B}[31mStatus")) // red for critical
}
@Test
func `output has ansi when TTY even without status`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 1, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: nil,
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: true,
resetStyle: .absolute))
#expect(output.contains("\u{001B}["))
}
@Test
func `tty output colors header and usage`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 95, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 80, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: nil,
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.0.0 (codex-cli)",
status: nil,
useColor: true,
resetStyle: .absolute))
#expect(output.contains("\u{001B}[1;95m== Codex 0.0.0 (codex-cli) ==\u{001B}[0m"))
#expect(output.contains("Session: \u{001B}[31m5% left\u{001B}[0m")) // red <10% left
#expect(output.contains("Weekly: \u{001B}[33m20% left\u{001B}[0m")) // yellow <25% left
}
@Test
func `status line is plain when no TTY`() {
let identity = ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: nil,
accountOrganization: nil,
loginMethod: "pro")
let snap = UsageSnapshot(
primary: .init(usedPercent: 0, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 0, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: nil,
updatedAt: Date(),
identity: identity)
let output = CLIRenderer.renderText(
provider: .codex,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Codex 0.6.0 (codex-cli)",
status: ProviderStatusPayload(
indicator: .none,
description: "Operational",
updatedAt: nil,
url: "https://status.openai.com/"),
useColor: false,
resetStyle: .absolute))
#expect(!output.contains("\u{001B}["))
#expect(output.contains("Status: Operational – Operational"))
}
@Test
func `renders 5-hour tertiary row for zai`() {
let snap = UsageSnapshot(
primary: .init(usedPercent: 9, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
secondary: .init(usedPercent: 50, windowMinutes: nil, resetsAt: nil, resetDescription: nil),
tertiary: .init(usedPercent: 25, windowMinutes: 300, resetsAt: nil, resetDescription: nil),
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .zai,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "z.ai 0.0.0 (zai)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("5-hour:"))
#expect(output.contains("Tokens:"))
#expect(output.contains("MCP:"))
}
@Test
func `devin overage balance without primary window omits generic cost line`() {
let snap = UsageSnapshot(
primary: nil,
secondary: .init(usedPercent: 42, windowMinutes: 10080, resetsAt: nil, resetDescription: nil),
tertiary: nil,
providerCost: ProviderCostSnapshot(
used: 48.0,
limit: 0,
currencyCode: "USD",
period: "Extra usage balance",
updatedAt: Date(timeIntervalSince1970: 0)),
updatedAt: Date(timeIntervalSince1970: 0))
let output = CLIRenderer.renderText(
provider: .devin,
snapshot: snap,
credits: nil,
context: RenderContext(
header: "Devin (devin)",
status: nil,
useColor: false,
resetStyle: .absolute))
#expect(output.contains("Extra usage: $48.00"))
#expect(!output.contains("Cost:"))
#expect(!output.contains(" / 0.0"))
}
}