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

382 lines
14 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
private final class AntigravityQuotaSummaryPathRecorder: @unchecked Sendable {
private let lock = NSLock()
private var paths: [String] = []
func append(_ path: String) {
self.lock.lock()
self.paths.append(path)
self.lock.unlock()
}
func snapshot() -> [String] {
self.lock.lock()
let snapshot = self.paths
self.lock.unlock()
return snapshot
}
}
struct AntigravityQuotaSummaryTests {
@Test
func `parses quota summary response into two model groups with session before weekly windows`() throws {
let snapshot = try AntigravityStatusProbe.parseQuotaSummaryResponse(
Data(antigravityQuotaSummaryJSON().utf8))
#expect(snapshot.modelQuotas.isEmpty)
let usage = try snapshot.toUsageSnapshot()
let windows = try #require(usage.extraRateWindows)
#expect(windows.map(\.id) == [
"antigravity-quota-summary-gemini-5h",
"antigravity-quota-summary-gemini-weekly",
"antigravity-quota-summary-3p-5h",
"antigravity-quota-summary-3p-weekly",
])
#expect(windows.map(\.title) == [
"Gemini 5-hour",
"Gemini weekly",
"Claude/GPT 5-hour",
"Claude/GPT weekly",
])
#expect(windows.map(\.window.windowMinutes) == [300, 10080, 300, 10080])
#expect(windows.map { $0.window.remainingPercent.rounded() } == [91, 82, 73, 64])
#expect(windows.map(\.usageKnown) == [true, true, true, true])
let expectedDates = [
ISO8601DateFormatter().date(from: "2026-06-15T11:39:34Z"),
ISO8601DateFormatter().date(from: "2026-06-19T08:45:39Z"),
ISO8601DateFormatter().date(from: "2026-06-15T12:52:10Z"),
ISO8601DateFormatter().date(from: "2026-06-20T00:39:54Z"),
]
#expect(windows.map(\.window.resetsAt) == expectedDates)
#expect(usage.primary?.remainingPercent.rounded() == 82)
#expect(usage.secondary?.remainingPercent.rounded() == 64)
#expect(usage.tertiary == nil)
}
@Test
func `parses quota summary oneof remaining value shape`() throws {
let json = """
{
"groups": [
{
"displayName": "Gemini Models",
"buckets": [
{
"bucketId": "gemini-weekly",
"displayName": "Weekly Limit",
"remaining": { "case": "remainingFraction", "value": 0.5 }
}
]
}
]
}
"""
let snapshot = try AntigravityStatusProbe.parseQuotaSummaryResponse(Data(json.utf8))
let usage = try snapshot.toUsageSnapshot()
#expect(usage.extraRateWindows?.first?.window.remainingPercent == 50)
}
@Test
func `fetch snapshot prefers quota summary endpoint and merges identity`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(endpoints: [endpoint], timeout: 1),
send: { payload, _, _ in
paths.append(payload.path)
if payload.path.contains("GetUserStatus") {
return Data(antigravityUserStatusJSON().utf8)
}
return Data(antigravityQuotaSummaryJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
])
#expect(usage.extraRateWindows?.count == 4)
#expect(usage.identity?.accountEmail == "test@example.com")
#expect(usage.identity?.loginMethod == "Pro")
}
@Test
func `fetch snapshot keeps quota summary when identity endpoint fails`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(endpoints: [endpoint], timeout: 1),
send: { payload, _, _ in
paths.append(payload.path)
if payload.path.contains("GetUserStatus") {
return Data(#"{"code":16}"#.utf8)
}
return Data(antigravityQuotaSummaryJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
])
#expect(usage.extraRateWindows?.count == 4)
#expect(usage.identity?.accountEmail == nil)
}
@Test
func `fetch snapshot falls back to user status when quota summary is unavailable`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(endpoints: [endpoint], timeout: 1),
send: { payload, _, _ in
paths.append(payload.path)
if payload.path.contains("RetrieveUserQuotaSummary") {
return Data(#"{"code":16}"#.utf8)
}
return Data(antigravityUserStatusJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
])
#expect(usage.primary?.remainingPercent.rounded() == 90)
}
@Test
func `fetch snapshot falls back when quota summary has no known usage buckets`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(endpoints: [endpoint], timeout: 1),
send: { payload, _, _ in
paths.append(payload.path)
if payload.path.contains("RetrieveUserQuotaSummary") {
return Data(antigravityQuotaSummaryWithoutKnownUsageJSON().utf8)
}
return Data(antigravityUserStatusJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
])
#expect(usage.primary?.remainingPercent.rounded() == 90)
}
@Test
func `quota summary timeout reserves deadline for legacy fallback`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(
endpoints: [endpoint],
timeout: 1,
deadline: Date().addingTimeInterval(2)),
send: { payload, _, timeout in
paths.append(payload.path)
if payload.path.contains("RetrieveUserQuotaSummary") {
try await Task.sleep(for: .seconds(timeout))
throw AntigravityStatusProbeError.timedOut
}
return Data(antigravityUserStatusJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
])
#expect(usage.primary?.remainingPercent.rounded() == 90)
}
@Test
func `user status timeout reserves deadline for command model fallback`() async throws {
let endpoint = AntigravityStatusProbe.AntigravityConnectionEndpoint(
scheme: "https",
port: 64440,
csrfToken: "token",
source: .languageServer)
let paths = AntigravityQuotaSummaryPathRecorder()
let snapshot = try await AntigravityStatusProbe.fetchSnapshot(
context: AntigravityStatusProbe.RequestContext(
endpoints: [endpoint],
timeout: 1,
deadline: Date().addingTimeInterval(2)),
send: { payload, _, timeout in
paths.append(payload.path)
if payload.path.contains("RetrieveUserQuotaSummary") {
throw AntigravityStatusProbeError.apiError("unsupported")
}
if payload.path.contains("GetUserStatus") {
try await Task.sleep(for: .seconds(timeout))
throw AntigravityStatusProbeError.timedOut
}
return Data(antigravityCommandModelConfigJSON().utf8)
})
let usage = try snapshot.toUsageSnapshot()
#expect(paths.snapshot() == [
"/exa.language_server_pb.LanguageServerService/RetrieveUserQuotaSummary",
"/exa.language_server_pb.LanguageServerService/GetUserStatus",
"/exa.language_server_pb.LanguageServerService/GetCommandModelConfigs",
])
#expect(usage.primary?.remainingPercent.rounded() == 90)
}
}
private func antigravityQuotaSummaryJSON() -> String {
"""
{
"response": {
"description": "Within each group, models share a weekly limit and a 5-hour limit.",
"groups": [
{
"displayName": "Gemini Models",
"description": "Models within this group: Gemini Flash, Gemini Pro",
"buckets": [
{
"bucketId": "gemini-weekly",
"displayName": "Weekly Limit",
"remaining": { "remainingFraction": 0.82 },
"description": "You have used some of your weekly limit, it will fully refresh in 5 days, 11 hours.",
"resetTime": "2026-06-19T08:45:39Z"
},
{
"bucketId": "gemini-5h",
"displayName": "Five Hour Limit",
"remaining": { "remainingFraction": 0.91 },
"description": "You have used some of your 5-hour limit, it will fully refresh in 4 hours.",
"resetTime": "2026-06-15T11:39:34Z"
}
]
},
{
"displayName": "Claude and GPT models",
"description": "Models within this group: Claude Opus, Claude Sonnet, GPT-OSS",
"buckets": [
{
"bucketId": "3p-weekly",
"displayName": "Weekly Limit",
"remaining": { "remainingFraction": 0.64 },
"description": "You have used some of your weekly limit, it will fully refresh in 6 days, 22 hours.",
"resetTime": "2026-06-20T00:39:54Z"
},
{
"bucketId": "3p-5h",
"displayName": "Five Hour Limit",
"remaining": { "remainingFraction": 0.73 },
"description": "You have used some of your 5-hour limit, it will fully refresh in 3 hours, 38 minutes.",
"resetTime": "2026-06-15T12:52:10Z"
}
]
}
]
}
}
"""
}
private func antigravityQuotaSummaryWithoutKnownUsageJSON() -> String {
"""
{
"response": {
"groups": [
{
"displayName": "Gemini Models",
"buckets": [
{
"bucketId": "gemini-weekly",
"displayName": "Weekly Limit",
"description": "Refreshes later."
},
{
"bucketId": "gemini-5h",
"displayName": "Five Hour Limit",
"disabled": true,
"remaining": { "remainingFraction": 0.5 }
}
]
}
]
}
}
"""
}
private func antigravityUserStatusJSON() -> String {
"""
{
"code": 0,
"userStatus": {
"email": "test@example.com",
"planStatus": {
"planInfo": {
"planName": "Pro"
}
},
"cascadeModelConfigData": {
"clientModelConfigs": [
{
"label": "Gemini 3 Pro Low",
"modelOrAlias": { "model": "gemini-3-pro-low" },
"quotaInfo": { "remainingFraction": 0.9, "resetTime": "2025-12-24T10:00:00Z" }
}
]
}
}
}
"""
}
private func antigravityCommandModelConfigJSON() -> String {
"""
{
"clientModelConfigs": [
{
"label": "Gemini 3 Pro Low",
"modelOrAlias": { "model": "gemini-3-pro-low" },
"quotaInfo": { "remainingFraction": 0.9, "resetTime": "2025-12-24T10:00:00Z" }
}
]
}
"""
}