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

319 lines
12 KiB
Swift

import CodexBarCore
import Foundation
import Testing
@testable import CodexBar
extension HistoricalUsagePaceTests {
private static let dashboardTimeZone: TimeZone = .current
static func linearCurve(end: Double) -> [Double] {
let clampedEnd = max(0, min(100, end))
let count = CodexHistoricalDataset.gridPointCount
return (0..<count).map { index in
let u = Double(index) / Double(count - 1)
return clampedEnd * u
}
}
static func outlierCurve() -> [Double] {
let count = CodexHistoricalDataset.gridPointCount
return (0..<count).map { index in
let u = Double(index) / Double(count - 1)
return min(100, 80 + (20 * u))
}
}
static func makeTempURL() -> URL {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-historical-tests", isDirectory: true)
.appendingPathComponent(UUID().uuidString, isDirectory: true)
return root.appendingPathComponent("usage-history.jsonl", isDirectory: false)
}
static func syntheticBreakdown(
endingAt endDate: Date,
days: Int,
dailyCredits: Double,
overridesByDayOffset: [Int: Double] = [:]) -> [OpenAIDashboardDailyBreakdown]
{
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = Self.dashboardTimeZone
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = Self.dashboardTimeZone
formatter.dateFormat = "yyyy-MM-dd"
let endDay = calendar.startOfDay(for: endDate)
return (0..<days).compactMap { offset in
guard let date = calendar.date(byAdding: .day, value: -offset, to: endDay) else { return nil }
let day = formatter.string(from: date)
let credits = overridesByDayOffset[offset] ?? dailyCredits
return OpenAIDashboardDailyBreakdown(
day: day,
services: [OpenAIDashboardServiceUsage(service: "CLI", creditsUsed: credits)],
totalCreditsUsed: credits)
}
}
static func gregorianDate(year: Int, month: Int, day: Int, hour: Int) -> Date {
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = Self.dashboardTimeZone
var components = DateComponents()
components.calendar = calendar
components.timeZone = Self.dashboardTimeZone
components.year = year
components.month = month
components.day = day
components.hour = hour
components.minute = 0
components.second = 0
guard let date = calendar.date(from: components) else {
preconditionFailure("Invalid Gregorian date components")
}
return date
}
static func dayStart(for key: String) -> Date? {
let components = key.split(separator: "-", omittingEmptySubsequences: true)
guard components.count == 3,
let year = Int(components[0]),
let month = Int(components[1]),
let day = Int(components[2])
else {
return nil
}
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = Self.dashboardTimeZone
var dateComponents = DateComponents()
dateComponents.calendar = calendar
dateComponents.timeZone = Self.dashboardTimeZone
dateComponents.year = year
dateComponents.month = month
dateComponents.day = day
return calendar.date(from: dateComponents)
}
static func normalizeReset(_ value: Date) -> Date {
let bucket = 5 * 60.0
let rounded = (value.timeIntervalSinceReferenceDate / bucket).rounded() * bucket
return Date(timeIntervalSinceReferenceDate: rounded)
}
static func readHistoricalRecords(from fileURL: URL) throws -> [HistoricalUsageRecord] {
let data = try Data(contentsOf: fileURL)
let text = String(data: data, encoding: .utf8) ?? ""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return text
.split(separator: "\n", omittingEmptySubsequences: true)
.compactMap { line in
try? decoder.decode(HistoricalUsageRecord.self, from: Data(line.utf8))
}
}
static func writeHistoricalFixture(named name: String, to fileURL: URL) throws {
let fixtureURL = URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.appendingPathComponent("Fixtures", isDirectory: true)
.appendingPathComponent(name, isDirectory: false)
try FileManager.default.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true)
let data = try Data(contentsOf: fixtureURL)
try data.write(to: fileURL, options: .atomic)
}
static func writeHistoricalRecords(_ records: [HistoricalUsageRecord], to fileURL: URL) throws {
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.sortedKeys]
let lines = try records.map { record -> String in
let data = try encoder.encode(record)
guard let line = String(bytes: data, encoding: .utf8) else {
throw CocoaError(.fileWriteUnknown)
}
return line
}
try FileManager.default.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true)
try (lines.joined(separator: "\n") + "\n").write(to: fileURL, atomically: true, encoding: .utf8)
}
static func recordDedupKeyCount(_ records: [HistoricalUsageRecord]) -> Int {
struct Key: Hashable {
let resetsAt: Date
let sampledAt: Date
let windowMinutes: Int
let accountKey: String?
}
let keys = records.map { record in
Key(
resetsAt: record.resetsAt,
sampledAt: record.sampledAt,
windowMinutes: record.windowMinutes,
accountKey: record.accountKey)
}
return Set(keys).count
}
static func datasetCurveSignature(_ dataset: CodexHistoricalDataset?) -> String {
guard let dataset else { return "nil" }
return dataset.weeks
.sorted { lhs, rhs in lhs.resetsAt < rhs.resetsAt }
.map { week in
let curve = week.curve.map { String(format: "%.4f", $0) }.joined(separator: ",")
return "\(week.resetsAt.timeIntervalSinceReferenceDate)|\(week.windowMinutes)|\(curve)"
}
.joined(separator: "||")
}
static func liveAccount(
email: String,
identity: CodexIdentity = .unresolved) -> ObservedSystemCodexAccount
{
ObservedSystemCodexAccount(
email: email,
codexHomePath: "/Users/test/.codex",
observedAt: Date(),
identity: identity)
}
static func weeklySnapshot(
email: String? = nil,
usedPercent: Double,
resetsAt: Date,
updatedAt: Date) -> UsageSnapshot
{
UsageSnapshot(
primary: nil,
secondary: RateWindow(
usedPercent: usedPercent,
windowMinutes: 10080,
resetsAt: resetsAt,
resetDescription: nil),
tertiary: nil,
providerCost: nil,
updatedAt: updatedAt,
identity: email.map {
ProviderIdentitySnapshot(
providerID: .codex,
accountEmail: $0,
accountOrganization: nil,
loginMethod: "Pro")
})
}
static func recordCompleteWeek(
into store: HistoricalUsageHistoryStore,
resetsAt: Date,
accountKey: String?) async
{
let windowMinutes = 10080
let duration = TimeInterval(windowMinutes) * 60
let windowStart = resetsAt.addingTimeInterval(-duration)
let samples: [(u: Double, used: Double)] = [
(0.02, 3),
(0.10, 10),
(0.40, 40),
(0.60, 60),
(0.80, 80),
(0.98, 95),
]
for sample in samples {
_ = await store.recordCodexWeekly(
window: RateWindow(
usedPercent: sample.used,
windowMinutes: windowMinutes,
resetsAt: resetsAt,
resetDescription: nil),
sampledAt: windowStart.addingTimeInterval(sample.u * duration),
accountKey: accountKey)
}
}
static func waitForHistoricalRecords(
at fileURL: URL,
minimumCount: Int,
timeoutMilliseconds: UInt64 = 10000) async throws -> [HistoricalUsageRecord]
{
let deadline = ContinuousClock.now + .milliseconds(timeoutMilliseconds)
while ContinuousClock.now < deadline {
if let records = try? Self.readHistoricalRecords(from: fileURL), records.count >= minimumCount {
return records
}
try await Task.sleep(for: .milliseconds(25))
}
return try Self.readHistoricalRecords(from: fileURL)
}
@MainActor
static func waitForHistoricalWrite(
store: UsageStore,
at fileURL: URL,
minimumCount: Int,
expectedAccountKey: String?,
timeoutMilliseconds: UInt64 = 10000) async throws -> [HistoricalUsageRecord]
{
let deadline = ContinuousClock.now + .milliseconds(timeoutMilliseconds)
while ContinuousClock.now < deadline {
let records = (try? Self.readHistoricalRecords(from: fileURL)) ?? []
if records.count >= minimumCount,
store.codexHistoricalDatasetAccountKey == expectedAccountKey
{
return records
}
try await Task.sleep(for: .milliseconds(25))
}
return try Self.readHistoricalRecords(from: fileURL)
}
@MainActor
static func makeUsageStoreForHistoricalTests(
suite: String,
historicalUsageHistoryStore: HistoricalUsageHistoryStore) throws -> UsageStore
{
let defaults = try #require(UserDefaults(suiteName: suite))
defaults.removePersistentDomain(forName: suite)
let settings = SettingsStore(
userDefaults: defaults,
configStore: testConfigStore(suiteName: suite),
zaiTokenStore: NoopZaiTokenStore(),
syntheticTokenStore: NoopSyntheticTokenStore(),
codexCookieStore: InMemoryCookieHeaderStore(),
claudeCookieStore: InMemoryCookieHeaderStore(),
cursorCookieStore: InMemoryCookieHeaderStore(),
opencodeCookieStore: InMemoryCookieHeaderStore(),
factoryCookieStore: InMemoryCookieHeaderStore(),
minimaxCookieStore: InMemoryMiniMaxCookieStore(),
minimaxAPITokenStore: InMemoryMiniMaxAPITokenStore(),
kimiTokenStore: InMemoryKimiTokenStore(),
kimiK2TokenStore: InMemoryKimiK2TokenStore(),
augmentCookieStore: InMemoryCookieHeaderStore(),
ampCookieStore: InMemoryCookieHeaderStore(),
copilotTokenStore: InMemoryCopilotTokenStore(),
tokenAccountStore: InMemoryTokenAccountStore())
settings.historicalTrackingEnabled = true
let planHistoryStore = testPlanUtilizationHistoryStore(
suiteName: "HistoricalUsagePaceTests-\(UUID().uuidString)")
let store = UsageStore(
fetcher: UsageFetcher(environment: [:]),
browserDetection: BrowserDetection(cacheTTL: 0),
settings: settings,
historicalUsageHistoryStore: historicalUsageHistoryStore,
planUtilizationHistoryStore: planHistoryStore)
store._cancelPlanUtilizationHistoryLoadForTesting()
return store
}
@MainActor
static func makeUsageStoreForBackfillTests(suite: String, historyFileURL: URL) throws -> UsageStore {
try self.makeUsageStoreForHistoricalTests(
suite: suite,
historicalUsageHistoryStore: HistoricalUsageHistoryStore(fileURL: historyFileURL))
}
}