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(report.summary?.totalCostUSD == standardCost + priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.costUSD == standardCost + priorityCost) #expect(breakdown.standardCostUSD == standardCost) #expect(breakdown.priorityCostUSD == priorityCost) #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(cached.summary?.totalCostUSD == priorityCost) let breakdown = try #require(cached.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(first.summary?.totalCostUSD == baseCost) 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(rescanned.summary?.totalCostUSD == priorityCost) } @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(first.summary?.totalCostUSD == baseCost) 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(cached.summary?.totalCostUSD == baseCost) } @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(first.summary?.totalCostUSD == baseCost) 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(repriced.summary?.totalCostUSD == priorityCost) } @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(report.summary?.totalCostUSD == standardCost + priorityCost) } @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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.costUSD == priorityCost) #expect(breakdown.priorityCostUSD == priorityCost) } @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(repriced.summary?.totalCostUSD == priorityCost) let breakdown = try #require(repriced.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == expected)Lines 1–500 of 680. Load more
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(report.summary?.totalCostUSD == standardCost + priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.costUSD == standardCost + priorityCost) #expect(breakdown.standardCostUSD == standardCost) #expect(breakdown.priorityCostUSD == priorityCost) #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(cached.summary?.totalCostUSD == priorityCost) let breakdown = try #require(cached.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(first.summary?.totalCostUSD == baseCost) 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(rescanned.summary?.totalCostUSD == priorityCost) } @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(first.summary?.totalCostUSD == baseCost) 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(cached.summary?.totalCostUSD == baseCost) } @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(first.summary?.totalCostUSD == baseCost) 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(repriced.summary?.totalCostUSD == priorityCost) } @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(report.summary?.totalCostUSD == standardCost + priorityCost) } @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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.costUSD == priorityCost) #expect(breakdown.priorityCostUSD == priorityCost) } @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(repriced.summary?.totalCostUSD == priorityCost) let breakdown = try #require(repriced.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == priorityCost) let breakdown = try #require(report.data.first?.modelBreakdowns?.first) #expect(breakdown.priorityCostUSD == priorityCost) #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(report.summary?.totalCostUSD == expected)