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

764 lines
35 KiB
Swift

import Foundation
#if canImport(SQLite3)
import Testing
@testable import CodexBarCore
struct CostUsageScannerPriorityTests {
@Test
func `codex daily report applies gpt55 priority rates`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let iso2 = env.isoString(for: day.addingTimeInterval(2))
let iso3 = env.isoString(for: day.addingTimeInterval(3))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "standard-turn"]],
self.tokenCount(timestamp: iso2, input: 100, cached: 20, output: 10),
["type": "event_msg", "timestamp": iso3, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso3, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso3)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let standardCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
let priorityCost = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - (standardCost + priorityCost)) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.costUSD ?? 0) - (standardCost + priorityCost)) < 0.000_000_001)
#expect(abs((breakdown.standardCostUSD ?? 0) - standardCost) < 0.000_000_001)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(breakdown.standardTokens == 110)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex daily report keeps cached priority surcharge without live sqlite metadata`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1)
var refreshOptions = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
refreshOptions.refreshMinIntervalSeconds = 0
_ = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: refreshOptions)
var cachedOptions = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: env.root.appendingPathComponent("missing.sqlite"))
cachedOptions.refreshMinIntervalSeconds = 60
let cached = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: cachedOptions)
let priorityCost = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
#expect(abs((cached.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
let breakdown = try #require(cached.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex daily report rescans when priority metadata appears`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
var missingOptions = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
missingOptions.refreshMinIntervalSeconds = 0
let first = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: missingOptions)
let baseCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((first.summary?.totalCostUSD ?? 0) - baseCost) < 0.000_000_001)
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1)
var liveOptions = missingOptions
liveOptions.refreshMinIntervalSeconds = 60
let rescanned = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: liveOptions)
let priorityCost = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
#expect(abs((rescanned.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
}
@Test
func `codex daily report ignores unrelated priority wal changes`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let first = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let baseCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((first.summary?.totalCostUSD ?? 0) - baseCost) < 0.000_000_001)
let walURL = URL(fileURLWithPath: dbURL.path + "-wal")
try Data("wal-changed".utf8).write(to: walURL)
options.refreshMinIntervalSeconds = 60
let cached = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day.addingTimeInterval(1),
options: options)
#expect(abs((cached.summary?.totalCostUSD ?? 0) - baseCost) < 0.000_000_001)
}
@Test
func `codex daily report reprices cached file when priority turn appears`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let first = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let baseCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((first.summary?.totalCostUSD ?? 0) - baseCost) < 0.000_000_001)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1)
options.refreshMinIntervalSeconds = 60
let repriced = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day.addingTimeInterval(61),
options: options)
let priorityCost = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
#expect(abs((repriced.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
}
@Test
func `codex daily report applies gpt54 priority rates`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let iso2 = env.isoString(for: day.addingTimeInterval(2))
let iso3 = env.isoString(for: day.addingTimeInterval(3))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.4"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "standard-turn"]],
self.tokenCount(timestamp: iso2, input: 100, cached: 20, output: 10),
["type": "event_msg", "timestamp": iso3, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso3, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso3, model: "gpt-5.4")
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let standardCost = (80.0 * 2.5e-6) + (20.0 * 2.5e-7) + (10.0 * 1.5e-5)
let priorityCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - (standardCost + priorityCost)) < 0.000_000_001)
}
@Test
func `codex daily report prices priority alias with completed response model`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "codex-auto-review"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "codex-auto-review")
try CostUsageScannerCodexPriorityTests.insertTestLog(
dbURL: dbURL,
timestamp: iso1,
body: "thread_id=thread turn.id=priority-turn websocket event: "
+ #"{"type":"response.completed","response":{"model":"gpt-5.4"}}"#)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let priorityCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex daily report totals use completed model priority cost`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.4"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "gpt-5.4")
try CostUsageScannerCodexPriorityTests.insertTestLog(
dbURL: dbURL,
timestamp: iso1,
body: "thread_id=thread turn.id=priority-turn websocket event: "
+ #"{"type":"response.completed","response":{"model":"gpt-5.5"}}"#)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let priorityCost = (80.0 * 1.25e-5) + (20.0 * 1.25e-6) + (10.0 * 7.5e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.costUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
}
@Test
func `codex daily report reprices cached priority alias when completed model arrives`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "codex-auto-review"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "codex-auto-review")
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let first = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
#expect(first.summary?.totalCostUSD == nil)
try CostUsageScannerCodexPriorityTests.insertTestLog(
dbURL: dbURL,
timestamp: iso1,
body: "thread_id=thread turn.id=priority-turn websocket event: "
+ #"{"type":"response.completed","response":{"model":"gpt-5.4"}}"#)
options.refreshMinIntervalSeconds = 60
let repriced = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day.addingTimeInterval(61),
options: options)
let priorityCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((repriced.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
let breakdown = try #require(repriced.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex daily report falls back to session model for unpriced priority alias`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.4"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "codex-auto-review")
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let priorityCost = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - priorityCost) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - priorityCost) < 0.000_000_001)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex daily report keeps base cost when sqlite metadata is missing`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: env.root.appendingPathComponent("missing.sqlite"))
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let expected = (80.0 * 5e-6) + (20.0 * 5e-7) + (10.0 * 3e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.costUSD ?? 0) - expected) < 0.000_000_001)
#expect(breakdown.standardCostUSD == nil)
#expect(breakdown.priorityCostUSD == nil)
#expect(breakdown.standardTokens == nil)
#expect(breakdown.priorityTokens == nil)
}
@Test
func `codex daily report attributes base priced priority rows to fast bucket`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.4-nano"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 100, cached: 20, output: 10),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "gpt-5.4-nano")
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let expected = (80.0 * 2e-7) + (20.0 * 2e-8) + (10.0 * 1.25e-6)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
#expect(abs((breakdown.costUSD ?? 0) - expected) < 0.000_000_001)
#expect(breakdown.standardCostUSD == nil)
#expect(abs((breakdown.priorityCostUSD ?? 0) - expected) < 0.000_000_001)
#expect(breakdown.standardTokens == nil)
#expect(breakdown.priorityTokens == 110)
}
@Test
func `codex pricing skips priority surcharge for long context rows`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let iso2 = env.isoString(for: day.addingTimeInterval(2))
let iso3 = env.isoString(for: day.addingTimeInterval(3))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "standard-turn"]],
self.tokenCount(timestamp: iso1, input: 272_001, cached: 0, output: 10),
["type": "event_msg", "timestamp": iso2, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso2, input: 300_000, cached: 0, output: 5),
self.tokenCount(timestamp: iso3, input: 100_001, cached: 0, output: 5),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso2)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let standardTurnBase = (272_001.0 * 1e-5) + (10.0 * 4.5e-5)
let standardFirstRow = (300_000.0 * 1e-5) + (5.0 * 4.5e-5)
let prioritySecondRow = (100_001.0 * 1.25e-5) + (5.0 * 7.5e-5)
let expected = standardTurnBase + standardFirstRow + prioritySecondRow
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
}
@Test
func `codex gpt56 long context rows keep base cost in priority bucket`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.6-sol"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 272_001, cached: 100_000, output: 5),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1, model: "gpt-5.6-sol")
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let expected = (172_001.0 * 1e-5) + (100_000.0 * 1e-6) + (5.0 * 4.5e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - expected) < 0.000_000_001)
#expect(breakdown.standardCostUSD == nil)
#expect(breakdown.priorityTokens == 272_006)
}
@Test
func `codex pricing applies priority surcharge when cached reads exceed limit but input stays under it`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.tokenCount(timestamp: iso1, input: 200_000, cached: 100_000, output: 5),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso1)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
// cached input is a subset of input, so the 272K priority limit applies to the 200K
// input alone (not input+cached). Input stays under the limit, so the priority surcharge
// applies at priority rates, and only the 100K non-cached input is billed at the input rate.
let expected = (100_000.0 * 1.25e-5) + (100_000.0 * 1.25e-6) + (5.0 * 7.5e-5)
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
let breakdown = try #require(report.data.first?.modelBreakdowns?.first)
#expect(abs((breakdown.priorityCostUSD ?? 0) - expected) < 0.000_000_001)
#expect(breakdown.priorityTokens == 200_005)
}
@Test
func `codex cumulative totals do not trigger long context pricing`() throws {
let env = try CostUsageTestEnvironment()
defer { env.cleanup() }
let day = try env.makeLocalNoon(year: 2026, month: 5, day: 10)
let iso0 = env.isoString(for: day)
let iso1 = env.isoString(for: day.addingTimeInterval(1))
let iso2 = env.isoString(for: day.addingTimeInterval(2))
let iso3 = env.isoString(for: day.addingTimeInterval(3))
let entries: [[String: Any]] = [
["type": "turn_context", "timestamp": iso0, "payload": ["model": "gpt-5.5"]],
["type": "event_msg", "timestamp": iso1, "payload": ["type": "task_started", "turn_id": "standard-turn"]],
self.totalTokenCount(timestamp: iso1, input: 120_000, cached: 60000, output: 100),
self.totalTokenCount(timestamp: iso2, input: 240_000, cached: 120_000, output: 200),
["type": "event_msg", "timestamp": iso3, "payload": ["type": "task_started", "turn_id": "priority-turn"]],
self.totalTokenCount(timestamp: iso3, input: 360_000, cached: 180_000, output: 300),
]
_ = try env.writeCodexSessionFile(day: day, filename: "session.jsonl", contents: env.jsonl(entries))
let dbURL = env.root.appendingPathComponent("logs_2.sqlite")
try CostUsageScannerCodexPriorityTests.createTestLogsDatabase(at: dbURL)
try self.insertPriorityTrace(dbURL: dbURL, timestamp: iso3)
var options = CostUsageScanner.Options(
codexSessionsRoot: env.codexSessionsRoot,
cacheRoot: env.cacheRoot,
codexTraceDatabaseURL: dbURL)
options.refreshMinIntervalSeconds = 0
let report = CostUsageScanner.loadDailyReport(
provider: .codex,
since: day,
until: day,
now: day,
options: options)
let standardRow = (Double(60000) * 5e-6) + (Double(60000) * 5e-7) + (Double(100) * 3e-5)
let priorityRow = (Double(60000) * 1.25e-5) + (Double(60000) * 1.25e-6)
+ (Double(100) * 7.5e-5)
let expected = standardRow + standardRow + priorityRow
#expect(abs((report.summary?.totalCostUSD ?? 0) - expected) < 0.000_000_001)
}
private func tokenCount(timestamp: String, input: Int, cached: Int, output: Int) -> [String: Any] {
[
"type": "event_msg",
"timestamp": timestamp,
"payload": [
"type": "token_count",
"info": [
"last_token_usage": [
"input_tokens": input,
"cached_input_tokens": cached,
"output_tokens": output,
],
],
],
]
}
private func totalTokenCount(timestamp: String, input: Int, cached: Int, output: Int) -> [String: Any] {
[
"type": "event_msg",
"timestamp": timestamp,
"payload": [
"type": "token_count",
"info": [
"total_token_usage": [
"input_tokens": input,
"cached_input_tokens": cached,
"output_tokens": output,
],
],
],
]
}
private func insertPriorityTrace(dbURL: URL, timestamp: String, model: String = "gpt-5.5") throws {
try CostUsageScannerCodexPriorityTests.insertTestLog(
dbURL: dbURL,
timestamp: timestamp,
body: "thread_id=thread turn.id=priority-turn websocket request: "
+ #"{"type":"response.create","model":""# + model + #"","service_tier":"priority"}"#)
}
}
#endif