312 lines
11 KiB
Swift
312 lines
11 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import CodexBarCore
|
|
|
|
struct CodexAdditionalRateLimitsTests {
|
|
@Test
|
|
func `maps additional spark limit into a named extra rate window`() 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": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 30,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000,
|
|
"reset_after_seconds": 12345
|
|
},
|
|
"secondary_window": {
|
|
"used_percent": 100,
|
|
"reset_at": 1767407914,
|
|
"limit_window_seconds": 604800,
|
|
"reset_after_seconds": 56789
|
|
}
|
|
}
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
let creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
let snapshot = try #require(mapped)
|
|
// Primary/weekly behavior is unchanged.
|
|
#expect(snapshot.primary?.usedPercent == 22)
|
|
#expect(snapshot.secondary?.usedPercent == 43)
|
|
// Spark surfaces as distinct 5-hour and weekly extra windows.
|
|
let extras = try #require(snapshot.extraRateWindows)
|
|
#expect(extras.count == 2)
|
|
let spark = try #require(extras.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(extras.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 `keeps valid spark window when an additional limit sibling is malformed`() throws {
|
|
let json = """
|
|
{
|
|
"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": [
|
|
"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 creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let mapped = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
let snapshot = try #require(mapped)
|
|
// Valid primary/weekly fields do not regress.
|
|
#expect(snapshot.primary?.usedPercent == 22)
|
|
#expect(snapshot.secondary?.usedPercent == 43)
|
|
// The malformed siblings are skipped, but the valid Spark entry survives.
|
|
let extras = try #require(snapshot.extraRateWindows)
|
|
#expect(extras.count == 1)
|
|
#expect(extras.first?.id == "codex-spark")
|
|
#expect(extras.first?.window.usedPercent == 30)
|
|
}
|
|
|
|
@Test
|
|
func `keeps primary usage when every additional limit element is malformed`() throws {
|
|
let json = """
|
|
{
|
|
"rate_limit": {
|
|
"primary_window": { "used_percent": 22, "reset_at": 1766948068, "limit_window_seconds": 18000 }
|
|
},
|
|
"additional_rate_limits": ["garbage", 1, true]
|
|
}
|
|
"""
|
|
let creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
#expect(snapshot?.primary?.usedPercent == 22)
|
|
#expect(snapshot?.extraRateWindows == nil)
|
|
}
|
|
|
|
@Test
|
|
func `omits extra rate windows when additional limits are absent`() throws {
|
|
let json = """
|
|
{
|
|
"rate_limit": {
|
|
"primary_window": {
|
|
"used_percent": 22,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
let creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
#expect(snapshot?.primary?.usedPercent == 22)
|
|
#expect(snapshot?.extraRateWindows == nil)
|
|
}
|
|
|
|
@Test
|
|
func `tolerates malformed additional limits while keeping primary window`() throws {
|
|
let json = """
|
|
{
|
|
"rate_limit": {
|
|
"primary_window": {
|
|
"used_percent": 22,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
},
|
|
"additional_rate_limits": "unexpected"
|
|
}
|
|
"""
|
|
let creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
#expect(snapshot?.primary?.usedPercent == 22)
|
|
#expect(snapshot?.extraRateWindows == nil)
|
|
}
|
|
|
|
@Test
|
|
func `skips additional limits without a usable window`() throws {
|
|
let json = """
|
|
{
|
|
"rate_limit": {
|
|
"primary_window": {
|
|
"used_percent": 22,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
},
|
|
"additional_rate_limits": [
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Spark",
|
|
"metered_feature": "gpt_5_3_codex_spark",
|
|
"rate_limit": null
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
let creds = CodexOAuthCredentials(
|
|
accessToken: "access",
|
|
refreshToken: "refresh",
|
|
idToken: nil,
|
|
accountId: nil,
|
|
lastRefresh: Date())
|
|
let snapshot = try CodexOAuthFetchStrategy._mapUsageForTesting(Data(json.utf8), credentials: creds)
|
|
#expect(snapshot?.primary?.usedPercent == 22)
|
|
#expect(snapshot?.extraRateWindows == nil)
|
|
}
|
|
|
|
@Test
|
|
func `maps non spark additional limit using a slugged id and api label`() throws {
|
|
let now = Date(timeIntervalSince1970: 1_800_000_000)
|
|
let json = """
|
|
[
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Mini",
|
|
"metered_feature": "gpt_5_3_codex_mini",
|
|
"rate_limit": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 12,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Mini",
|
|
"metered_feature": "gpt_5_3_codex_mini",
|
|
"rate_limit": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 99,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
}
|
|
}
|
|
]
|
|
"""
|
|
let entries = try JSONDecoder().decode([CodexUsageResponse.AdditionalRateLimit].self, from: Data(json.utf8))
|
|
let windows = CodexAdditionalRateLimitMapper.extraRateWindows(from: entries, now: now)
|
|
// Duplicate ids collapse to the first occurrence.
|
|
#expect(windows.count == 1)
|
|
let window = try #require(windows.first)
|
|
#expect(window.id == "codex-gpt-5-3-codex-mini")
|
|
#expect(window.title == "GPT-5.3-Codex-Mini")
|
|
#expect(window.window.usedPercent == 12)
|
|
}
|
|
|
|
@Test
|
|
func `dedupes split spark entries by window kind`() throws {
|
|
let now = Date(timeIntervalSince1970: 1_800_000_000)
|
|
let json = """
|
|
[
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Spark",
|
|
"metered_feature": "gpt_5_3_codex_spark",
|
|
"rate_limit": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 20,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Spark Weekly",
|
|
"metered_feature": "gpt_5_3_codex_spark",
|
|
"rate_limit": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 80,
|
|
"reset_at": 1767407914,
|
|
"limit_window_seconds": 604800
|
|
}
|
|
}
|
|
},
|
|
{
|
|
"limit_name": "GPT-5.3-Codex-Spark Duplicate",
|
|
"metered_feature": "gpt_5_3_codex_spark",
|
|
"rate_limit": {
|
|
"allowed": true,
|
|
"limit_reached": false,
|
|
"primary_window": {
|
|
"used_percent": 99,
|
|
"reset_at": 1766948068,
|
|
"limit_window_seconds": 18000
|
|
}
|
|
}
|
|
}
|
|
]
|
|
"""
|
|
let entries = try JSONDecoder().decode([CodexUsageResponse.AdditionalRateLimit].self, from: Data(json.utf8))
|
|
let windows = CodexAdditionalRateLimitMapper.extraRateWindows(from: entries, now: now)
|
|
|
|
#expect(windows.map(\.id) == ["codex-spark", "codex-spark-weekly"])
|
|
#expect(windows.first?.window.usedPercent == 20)
|
|
#expect(windows.last?.window.usedPercent == 80)
|
|
}
|
|
}
|