import CodexBarCore import Commander import Foundation import Testing @testable import CodexBarCLI struct CLICostTests { @Test func `cost json shortcut does not enable json logs`() throws { let signature = CodexBarCLI._costSignatureForTesting() let parser = CommandParser(signature: signature) let parsed = try parser.parse(arguments: ["--json"]) #expect(parsed.flags.contains("jsonShortcut")) #expect(!parsed.flags.contains("jsonOutput")) #expect(CodexBarCLI._decodeFormatForTesting(from: parsed) == .json) } @Test func `renders cost text snapshot`() { let snap = CostUsageTokenSnapshot( sessionTokens: 1200, sessionCostUSD: 1.25, last30DaysTokens: 9000, last30DaysCostUSD: 9.99, historyDays: 90, daily: [], updatedAt: Date(timeIntervalSince1970: 0)) let output = CodexBarCLI.renderCostText(provider: .claude, snapshot: snap, useColor: false) .replacingOccurrences(of: "\u{00A0}", with: " ") .replacingOccurrences(of: "$ ", with: "$") #expect(output.contains("Claude Cost (API-rate estimate)")) #expect(output.contains("Today: $1.25 · 1.2K tokens")) #expect(output.contains("Last 90 days: $9.99 · 9K tokens")) #expect(output.contains("cache read/write tokens")) #expect(output.contains("Claude Code /status")) } @Test func `encodes cost payload JSON`() throws { let payload = CostPayload( provider: "claude", source: "local", updatedAt: Date(timeIntervalSince1970: 1_700_000_000), sessionTokens: 100, sessionCostUSD: 0.5, historyDays: 90, last30DaysTokens: 200, last30DaysCostUSD: 1.5, daily: [ CostDailyEntryPayload( date: "2025-12-20", inputTokens: 10, outputTokens: 5, cacheReadTokens: 2, cacheCreationTokens: 3, totalTokens: 15, costUSD: 0.01, modelsUsed: ["claude-sonnet-4-20250514"], modelBreakdowns: [ CostModelBreakdownPayload( modelName: "claude-sonnet-4-20250514", costUSD: 0.01, totalTokens: 15), ]), ], totals: CostTotalsPayload( totalInputTokens: 10, totalOutputTokens: 5, cacheReadTokens: 2, cacheCreationTokens: 3, totalTokens: 15, totalCostUSD: 0.01), 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 cost payload JSON") return } #expect(json.contains("\"provider\":\"claude\"")) #expect(json.contains("\"source\":\"local\"")) #expect(json.contains("\"historyDays\":90")) #expect(json.contains("\"daily\"")) #expect(json.contains("\"totals\"")) #expect(json.contains("\"cacheReadTokens\":2")) #expect(json.contains("\"cacheCreationTokens\":3")) #expect(json.contains("\"totalCost\"")) #expect(json.contains("\"totalTokens\":15")) #expect(json.contains("1700000000")) } @Test func `encodes exact codex model I ds and zero cost breakdowns`() throws { let payload = CostPayload( provider: "codex", source: "local", updatedAt: Date(timeIntervalSince1970: 1_700_000_000), sessionTokens: 155, sessionCostUSD: 0, historyDays: 30, last30DaysTokens: 155, last30DaysCostUSD: 0, daily: [ CostDailyEntryPayload( date: "2025-12-21", inputTokens: 120, outputTokens: 15, cacheReadTokens: 20, cacheCreationTokens: nil, totalTokens: 155, costUSD: 0, modelsUsed: ["gpt-5.3-codex-spark", "gpt-5.2-codex"], modelBreakdowns: [ CostModelBreakdownPayload(modelName: "gpt-5.3-codex-spark", costUSD: 0, totalTokens: 15), CostModelBreakdownPayload(modelName: "gpt-5.2-codex", costUSD: 1.23, totalTokens: 140), ]), ], totals: CostTotalsPayload( totalInputTokens: 120, totalOutputTokens: 15, cacheReadTokens: 20, cacheCreationTokens: nil, totalTokens: 155, totalCostUSD: 0), 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 cost payload JSON") return } #expect(json.contains("\"gpt-5.3-codex-spark\"")) #expect(json.contains("\"gpt-5.2-codex\"")) #expect(!json.contains("\"gpt-5.2\"")) #expect(json.contains("\"cost\":0")) #expect(json.contains("\"totalTokens\":140")) } @Test func `cost estimate hint is stable string`() { let hint = UsageFormatter.costEstimateHint #expect(!hint.isEmpty) #expect(hint.contains("Estimated")) #expect(UsageFormatter.costEstimateHint(provider: .claude).contains("cache read/write tokens")) } }