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

1192 lines
43 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"))
}
}