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

164 lines
5.8 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
struct CostUsageCacheTests {
@Test
func `cache file URL uses provider artifact versions`() {
let root = URL(fileURLWithPath: "/tmp/codexbar-cost-cache", isDirectory: true)
let codexURL = CostUsageCacheIO.cacheFileURL(provider: .codex, cacheRoot: root)
let claudeURL = CostUsageCacheIO.cacheFileURL(provider: .claude, cacheRoot: root)
let vertexURL = CostUsageCacheIO.cacheFileURL(provider: .vertexai, cacheRoot: root)
#expect(codexURL.lastPathComponent == "codex-v9.json")
#expect(claudeURL.lastPathComponent == "claude-v4.json")
#expect(vertexURL.lastPathComponent == "vertexai-v4.json")
}
@Test
func `cache load requires matching producer key`() throws {
let root = try self.makeTemporaryCacheRoot()
defer { try? FileManager.default.removeItem(at: root) }
var cache = CostUsageCache()
cache.lastScanUnixMs = 123
cache.days = ["2026-05-18": ["gpt-5.5": [1, 2, 3]]]
CostUsageCacheIO.save(
provider: .codex,
cache: cache,
cacheRoot: root,
producerKey: "codex:cu:p1111111111111111")
let loaded = CostUsageCacheIO.load(
provider: .codex,
cacheRoot: root,
producerKey: "codex:cu:p1111111111111111")
#expect(loaded.producerKey == "codex:cu:p1111111111111111")
#expect(loaded.lastScanUnixMs == 123)
#expect(loaded.days["2026-05-18"]?["gpt-5.5"] == [1, 2, 3])
let stale = CostUsageCacheIO.load(
provider: .codex,
cacheRoot: root,
producerKey: "codex:cu:p2222222222222222")
#expect(stale.lastScanUnixMs == 0)
#expect(stale.files.isEmpty)
#expect(stale.days.isEmpty)
}
@Test
func `legacy cache without producer key is ignored`() throws {
let root = try self.makeTemporaryCacheRoot()
defer { try? FileManager.default.removeItem(at: root) }
let url = CostUsageCacheIO.cacheFileURL(provider: .codex, cacheRoot: root)
try FileManager.default.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
let legacy = """
{
"version": 1,
"lastScanUnixMs": 999,
"files": {},
"days": {
"2026-05-18": {
"gpt-5": [1, 0, 0]
}
}
}
"""
try legacy.write(to: url, atomically: false, encoding: .utf8)
let loaded = CostUsageCacheIO.load(
provider: .codex,
cacheRoot: root,
producerKey: "codex:cu:p1111111111111111")
#expect(loaded.lastScanUnixMs == 0)
#expect(loaded.days.isEmpty)
}
@Test
func `current codex cache rejects pre interleave containment producers`() throws {
// Interleave containment (#2037) changed cumulative delta semantics, so caches from
// previously compatible parser hashes must be rebuilt instead of reused.
let root = try self.makeTemporaryCacheRoot()
defer { try? FileManager.default.removeItem(at: root) }
for legacyProducerKey in ["codex:cu:p3c27f997569eb3c5", "codex:cu:pc54070a94f6419ea"] {
var cache = CostUsageCache()
cache.lastScanUnixMs = 123
cache.days = ["2026-05-18": ["gpt-5.5": [1, 2, 3]]]
CostUsageCacheIO.save(
provider: .codex,
cache: cache,
cacheRoot: root,
producerKey: legacyProducerKey)
let loaded = CostUsageCacheIO.load(provider: .codex, cacheRoot: root)
#expect(loaded.lastScanUnixMs == 0)
#expect(loaded.days.isEmpty)
}
}
@Test
func `non codex cache does not require producer key`() throws {
let root = try self.makeTemporaryCacheRoot()
defer { try? FileManager.default.removeItem(at: root) }
let url = CostUsageCacheIO.cacheFileURL(provider: .claude, cacheRoot: root)
try FileManager.default.createDirectory(
at: url.deletingLastPathComponent(),
withIntermediateDirectories: true)
let legacy = """
{
"version": 1,
"lastScanUnixMs": 999,
"files": {},
"days": {
"2026-05-18": {
"claude-sonnet-4-5": [1, 0, 0]
}
}
}
"""
try legacy.write(to: url, atomically: false, encoding: .utf8)
let loaded = CostUsageCacheIO.load(provider: .claude, cacheRoot: root)
#expect(loaded.lastScanUnixMs == 999)
#expect(loaded.days["2026-05-18"]?["claude-sonnet-4-5"] == [1, 0, 0])
}
@Test
func `current producer key uses generated parser hash for codex only`() {
let codexKey = CostUsageCacheIO.currentProducerKey(
provider: .codex,
parserHash: "abc1234567890def")
let standaloneKey = CostUsageCacheIO.currentProducerKey(
provider: .claude,
parserHash: "abc1234567890def")
#expect(codexKey == "codex:cu:pabc1234567890def")
#expect(standaloneKey == nil)
}
@Test
func `generated parser hash is stable short lowercase hex`() {
let hash = CodexParserHash.value
#expect(hash.range(of: #"^[0-9a-f]{16}$"#, options: .regularExpression) != nil)
#expect(CostUsageCacheIO.currentProducerKey(provider: .codex) == "codex:cu:p\(hash)")
}
private func makeTemporaryCacheRoot() throws -> URL {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-cost-cache-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
return root
}
}