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

233 lines
9.8 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
/// Dashboard-path coverage for Codex `additional_rate_limits` (e.g. GPT-5.3-Codex-Spark): the
/// OpenAI web dashboard usage API decodes the same `wham/usage` JSON as the OAuth path, so Spark
/// limits must survive the `dashboardAPIData -> DashboardSnapshotComponents -> OpenAIDashboardSnapshot
/// -> fromAttachedDashboard -> UsageSnapshot.extraRateWindows` chain without disturbing the
/// existing primary/weekly/credits/plan mapping.
struct OpenAIDashboardSparkTests {
private static func response(from json: String) throws -> CodexUsageResponse {
try JSONDecoder().decode(CodexUsageResponse.self, from: Data(json.utf8))
}
@Test
func `dashboard api data maps additional spark limit into extra windows`() throws {
let json = """
{
"plan_type": "pro",
"rate_limit": {
"primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 },
"secondary_window": { "used_percent": 43, "reset_at": 1767407914, "limit_window_seconds": 604800 }
},
"additional_rate_limits": [
{
"limit_name": "GPT-5.3-Codex-Spark",
"metered_feature": "gpt_5_3_codex_spark",
"rate_limit": {
"primary_window": { "used_percent": 30, "reset_at": 1766948068, "limit_window_seconds": 18000 },
"secondary_window": { "used_percent": 100, "reset_at": 1767407914, "limit_window_seconds": 604800 }
}
}
]
}
"""
let response = try Self.response(from: json)
let apiData = OpenAIDashboardFetcher.dashboardAPIData(from: response)
// Primary/weekly/credits/plan continue to map exactly as before.
#expect(apiData.primaryLimit?.usedPercent == 22)
#expect(apiData.secondaryLimit?.usedPercent == 43)
#expect(apiData.accountPlan == "pro")
// Spark surfaces with stable ids/titles for the additional 5-hour and weekly windows.
#expect(apiData.extraRateWindows.count == 2)
let spark = try #require(apiData.extraRateWindows.first)
#expect(spark.id == "codex-spark")
#expect(spark.title == "Codex Spark 5-hour")
#expect(spark.window.usedPercent == 30)
#expect(spark.window.windowMinutes == 300)
#expect(spark.window.resetsAt != nil)
let weekly = try #require(apiData.extraRateWindows.last)
#expect(weekly.id == "codex-spark-weekly")
#expect(weekly.title == "Codex Spark Weekly")
#expect(weekly.window.usedPercent == 100)
#expect(weekly.window.windowMinutes == 10080)
#expect(weekly.window.resetsAt != nil)
}
@Test
func `dashboard api data has empty extra windows when additional limits are absent`() throws {
let json = """
{
"rate_limit": {
"primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }
}
}
"""
let response = try Self.response(from: json)
let apiData = OpenAIDashboardFetcher.dashboardAPIData(from: response)
#expect(apiData.primaryLimit?.usedPercent == 22)
#expect(apiData.extraRateWindows.isEmpty)
}
@Test
func `dashboard api data tolerates non array additional limits while keeping primary`() throws {
let json = """
{
"rate_limit": {
"primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }
},
"additional_rate_limits": "unexpected"
}
"""
let response = try Self.response(from: json)
let apiData = OpenAIDashboardFetcher.dashboardAPIData(from: response)
#expect(apiData.primaryLimit?.usedPercent == 22)
#expect(apiData.extraRateWindows.isEmpty)
}
@Test
func `dashboard api data keeps valid spark when a malformed sibling is present`() throws {
// Lossy per-element decode (shared with the OAuth path via CodexUsageResponse) means a single
// malformed entry cannot discard its valid siblings.
let json = """
{
"rate_limit": {
"primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }
},
"additional_rate_limits": [
"garbage-not-an-object",
{
"limit_name": "GPT-5.3-Codex-Spark",
"metered_feature": "gpt_5_3_codex_spark",
"rate_limit": {
"primary_window": { "used_percent": 30, "reset_at": 1766948068, "limit_window_seconds": 18000 }
}
},
42
]
}
"""
let response = try Self.response(from: json)
let apiData = OpenAIDashboardFetcher.dashboardAPIData(from: response)
#expect(apiData.primaryLimit?.usedPercent == 22)
#expect(apiData.extraRateWindows.count == 1)
#expect(apiData.extraRateWindows.first?.id == "codex-spark")
#expect(apiData.extraRateWindows.first?.window.usedPercent == 30)
}
@Test
func `dashboard snapshot exposes extra rate windows via to usage snapshot`() throws {
let now = Date(timeIntervalSince1970: 1_766_948_000)
let snapshot = OpenAIDashboardSnapshot(
signedInEmail: "codex@example.com",
codeReviewRemainingPercent: nil,
creditEvents: [],
dailyBreakdown: [],
usageBreakdown: [],
creditsPurchaseURL: nil,
primaryLimit: RateWindow(
usedPercent: 22,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(3600),
resetDescription: nil),
secondaryLimit: RateWindow(
usedPercent: 43,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(6 * 24 * 60 * 60),
resetDescription: nil),
extraRateWindows: [
NamedRateWindow(
id: "codex-spark",
title: "Codex Spark 5-hour",
window: RateWindow(
usedPercent: 30,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(60 * 60),
resetDescription: nil)),
NamedRateWindow(
id: "codex-spark-weekly",
title: "Codex Spark Weekly",
window: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(6 * 24 * 60 * 60),
resetDescription: nil)),
],
updatedAt: now)
let usage = try #require(snapshot.toUsageSnapshot(provider: .codex))
// Primary/weekly behavior preserved.
#expect(usage.primary?.usedPercent == 22)
#expect(usage.secondary?.usedPercent == 43)
// Spark surfaces through UsageSnapshot.extraRateWindows for dashboard-source users.
let extras = try #require(usage.extraRateWindows)
#expect(extras.map(\.id) == ["codex-spark", "codex-spark-weekly"])
#expect(extras.first?.window.usedPercent == 30)
#expect(extras.last?.window.usedPercent == 100)
}
@Test
func `dashboard snapshot codable round trips extra rate windows`() throws {
let now = Date(timeIntervalSince1970: 1_766_948_000)
let snapshot = OpenAIDashboardSnapshot(
signedInEmail: nil,
codeReviewRemainingPercent: nil,
creditEvents: [],
dailyBreakdown: [],
usageBreakdown: [],
creditsPurchaseURL: nil,
extraRateWindows: [
NamedRateWindow(
id: "codex-spark",
title: "Codex Spark 5-hour",
window: RateWindow(
usedPercent: 30,
windowMinutes: 300,
resetsAt: now.addingTimeInterval(60 * 60),
resetDescription: nil)),
NamedRateWindow(
id: "codex-spark-weekly",
title: "Codex Spark Weekly",
window: RateWindow(
usedPercent: 100,
windowMinutes: 10080,
resetsAt: now.addingTimeInterval(6 * 24 * 60 * 60),
resetDescription: nil)),
],
updatedAt: now)
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let data = try encoder.encode(snapshot)
let decoded = try decoder.decode(OpenAIDashboardSnapshot.self, from: data)
#expect(decoded.extraRateWindows?.map(\.id) == ["codex-spark", "codex-spark-weekly"])
#expect(decoded.extraRateWindows?.first?.window.usedPercent == 30)
#expect(decoded.extraRateWindows?.last?.window.usedPercent == 100)
}
@Test
func `dashboard snapshot decoder preserves absence of extra rate windows`() throws {
// Older cached snapshots predate the field; decoding such payloads must yield nil and never
// throw, so existing dashboard caches keep working.
let json = """
{
"signedInEmail": "codex@example.com",
"codeReviewRemainingPercent": null,
"creditEvents": [],
"dailyBreakdown": [],
"usageBreakdown": [],
"creditsPurchaseURL": null,
"updatedAt": "2026-04-30T19:27:07Z"
}
"""
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let snapshot = try decoder.decode(OpenAIDashboardSnapshot.self, from: Data(json.utf8))
#expect(snapshot.extraRateWindows == nil)
}
}