Files
nucleic/Tests/NucleicCoreTests/SubscriptionUsageTests.swift
T

112 lines
4.9 KiB
Swift

import Foundation
import Testing
@testable import NucleicCore
/// Locks the parse of Anthropic's `/api/oauth/usage` response (subscription quota).
/// The body below mirrors captures from before and after the July 2026 `limits[]` migration;
/// re-capture and diff if the indicator ever reads wrong after a CLI/API change.
struct SubscriptionUsageTests {
private static let sample = Data("""
{
"five_hour": { "utilization": 37.0, "resets_at": "2026-06-15T04:29:59.851919+00:00" },
"seven_day": { "utilization": 14.0, "resets_at": "2026-06-21T01:59:59.851939+00:00" },
"seven_day_oauth_apps": null,
"seven_day_opus": null,
"seven_day_sonnet": { "utilization": 0.0, "resets_at": null },
"limits": [
{ "kind": "session", "percent": 38, "resets_at": "2026-06-15T04:29:59.851919+00:00", "is_active": false },
{ "kind": "weekly_all", "percent": 15, "resets_at": "2026-06-21T01:59:59.851939+00:00", "is_active": false },
{ "kind": "weekly_scoped", "percent": 68, "resets_at": "2026-06-21T01:59:59.851939+00:00",
"scope": { "model": { "id": null, "display_name": "Fable" } }, "is_active": true },
{ "kind": "weekly_scoped", "percent": 12, "resets_at": null,
"scope": { "model": { "display_name": "Sonnet" } }, "is_active": false }
],
"extra_usage": { "is_enabled": false, "monthly_limit": null }
}
""".utf8)
@Test func parsesWindowsAndPeak() throws {
let usage = try SubscriptionUsageFetcher.parse(Self.sample)
// Generic limits[] is authoritative over legacy flat buckets.
#expect(usage.fiveHour?.utilization == 38.0)
#expect(usage.fiveHour?.resetsAt != nil)
#expect(usage.sevenDay?.utilization == 15.0)
#expect(usage.sevenDayFable?.utilization == 68.0)
// A null per-model window is absent, not zero.
#expect(usage.sevenDayOpus == nil)
// A present window with a null reset keeps the utilization but no date.
#expect(usage.sevenDaySonnet?.utilization == 12.0)
#expect(usage.sevenDaySonnet?.resetsAt == nil)
// The headline tracks the most-constrained window.
#expect(usage.peakUtilization == 68.0)
}
@Test func microsecondResetTimestampParses() throws {
let usage = try SubscriptionUsageFetcher.parse(Self.sample)
let resets = try #require(usage.fiveHour?.resetsAt)
// 2026-06-15T04:29:59.851919Z — verify it landed in the right minute.
let components = Calendar(identifier: .gregorian)
.dateComponents(in: TimeZone(identifier: "UTC")!, from: resets)
#expect(components.year == 2026)
#expect(components.month == 6)
#expect(components.day == 15)
#expect(components.hour == 4)
#expect(components.minute == 29)
}
@Test func legacyFlatWindowsRemainSupported() throws {
let usage = try SubscriptionUsageFetcher.parse(Data("""
{
"five_hour": { "utilization": 37.0, "resets_at": null },
"seven_day": { "utilization": 14.0, "resets_at": null },
"seven_day_opus": { "utilization": 9.0, "resets_at": null }
}
""".utf8))
#expect(usage.fiveHour?.utilization == 37)
#expect(usage.sevenDay?.utilization == 14)
#expect(usage.sevenDayFable == nil)
#expect(usage.sevenDayOpus?.utilization == 9)
}
@Test func malformedGenericEntriesAreIgnored() throws {
let usage = try SubscriptionUsageFetcher.parse(Data("""
{
"five_hour": { "utilization": 8.0, "resets_at": null },
"limits": [
{ "kind": "weekly_scoped", "percent": null,
"scope": { "model": { "display_name": "Fable" } } },
{ "kind": "weekly_scoped", "percent": 45, "scope": null }
]
}
""".utf8))
#expect(usage.fiveHour?.utilization == 8)
#expect(usage.sevenDayFable == nil)
}
@Test func lapsedWindowReadsAsEmptyUntilRefetched() {
let reset = Date(timeIntervalSince1970: 1_000_000)
let window = UsageWindow(utilization: 73.0, resetsAt: reset)
// Before the reset the cached snapshot is current.
#expect(window.utilization(at: reset.addingTimeInterval(-60)) == 73.0)
// At/after the reset the window has rolled over; the stale 73% reads as 0 until
// the next poll refreshes it, instead of sticking at the pre-reset value.
#expect(window.utilization(at: reset) == 0)
#expect(window.utilization(at: reset.addingTimeInterval(60)) == 0)
}
@Test func windowWithoutResetIsReportedAsIs() {
let window = UsageWindow(utilization: 42.0, resetsAt: nil)
#expect(window.utilization(at: Date(timeIntervalSince1970: 1_000_000)) == 42.0)
}
@Test func malformedBodyThrows() {
#expect(throws: (any Error).self) {
try SubscriptionUsageFetcher.parse(Data("not json".utf8))
}
}
}