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

259 lines
11 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct CursorEnterpriseUsageTests {
@Test
func `legacy provider cost snapshot decodes without personal spend`() throws {
let json = """
{
"used": 12.5,
"limit": 100,
"currencyCode": "USD",
"period": "Monthly",
"resetsAt": null,
"nextRegenAmount": null,
"updatedAt": 0
}
"""
let snapshot = try JSONDecoder().decode(ProviderCostSnapshot.self, from: Data(json.utf8))
#expect(snapshot.used == 12.5)
#expect(snapshot.personalUsed == nil)
}
@Test
func `parses enterprise overall and pooled usage summary`() throws {
// Live Cursor Enterprise payload (sanitized). The Pro/Hobby `plan` block is absent;
// instead Cursor reports `individualUsage.overall` (personal cap) and `teamUsage.pooled`
// (shared team pool). Both blocks use cents like the existing `plan` block.
let json = """
{
"billingCycleStart": "2026-04-01T00:00:00.000Z",
"billingCycleEnd": "2026-05-01T00:00:00.000Z",
"membershipType": "enterprise",
"limitType": "team",
"isUnlimited": false,
"individualUsage": {
"overall": {
"enabled": true,
"used": 7384,
"limit": 10000,
"remaining": 2616
}
},
"teamUsage": {
"onDemand": {
"enabled": true,
"used": 0,
"limit": null,
"remaining": null
},
"pooled": {
"enabled": true,
"used": 12725135,
"limit": 28122000,
"remaining": 15396865
}
}
}
"""
let data = try #require(json.data(using: .utf8))
let summary = try JSONDecoder().decode(CursorUsageSummary.self, from: data)
#expect(summary.membershipType == "enterprise")
#expect(summary.limitType == "team")
#expect(summary.individualUsage?.plan == nil)
#expect(summary.individualUsage?.overall?.used == 7384)
#expect(summary.individualUsage?.overall?.limit == 10000)
#expect(summary.individualUsage?.overall?.remaining == 2616)
#expect(summary.teamUsage?.pooled?.used == 12_725_135)
#expect(summary.teamUsage?.pooled?.limit == 28_122_000)
}
@Test
func `enterprise overall drives headline percent and dollars`() throws {
// Regression: Cursor Enterprise/Team accounts ship `individualUsage.overall` instead of
// `individualUsage.plan`. Without a model for `overall`, the parser used to report 0%
// (i.e. the menu showed "100% remaining"). The personal cap must take precedence over
// any team pool, and USD figures must reflect the same source.
let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))
.parseUsageSummary(
CursorUsageSummary(
billingCycleStart: "2026-04-01T00:00:00.000Z",
billingCycleEnd: "2026-05-01T00:00:00.000Z",
membershipType: "enterprise",
limitType: "team",
isUnlimited: false,
autoModelSelectedDisplayMessage: nil,
namedModelSelectedDisplayMessage: nil,
individualUsage: CursorIndividualUsage(
plan: nil,
onDemand: nil,
overall: CursorOverallUsage(enabled: true, used: 7384, limit: 10000, remaining: 2616)),
teamUsage: CursorTeamUsage(
onDemand: CursorOnDemandUsage(enabled: true, used: 0, limit: nil, remaining: nil),
pooled: CursorPooledUsage(
enabled: true,
used: 12_725_135,
limit: 28_122_000,
remaining: 15_396_865))),
userInfo: nil,
rawJSON: nil)
// Headline: $73.84 / $100 -> 73.84% (matches Cursor's own dashboard).
// Allow a tiny tolerance for floating-point division (7384/10000 * 100).
#expect(abs(snapshot.planPercentUsed - 73.84) < 0.0001)
#expect(snapshot.planUsedUSD == 73.84)
#expect(snapshot.planLimitUSD == 100.0)
#expect(snapshot.autoPercentUsed == nil)
#expect(snapshot.apiPercentUsed == nil)
let primaryPercent = try #require(snapshot.toUsageSnapshot().primary?.usedPercent)
#expect(abs(primaryPercent - 73.84) < 0.0001)
}
@Test
func `enterprise pooled fallback used when no individual data`() {
// When Cursor only reports a shared team pool (no `plan`, no `overall`) we should still surface
// a non-zero headline so the menu reflects pool consumption rather than appearing "all clear".
let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))
.parseUsageSummary(
CursorUsageSummary(
billingCycleStart: nil,
billingCycleEnd: nil,
membershipType: "enterprise",
limitType: "team",
isUnlimited: false,
autoModelSelectedDisplayMessage: nil,
namedModelSelectedDisplayMessage: nil,
individualUsage: nil,
teamUsage: CursorTeamUsage(
onDemand: nil,
pooled: CursorPooledUsage(
enabled: true,
used: 12_725_135,
limit: 28_122_000,
remaining: 15_396_865))),
userInfo: nil,
rawJSON: nil)
#expect(snapshot.planPercentUsed > 45.0)
#expect(snapshot.planPercentUsed < 45.5)
#expect(snapshot.planUsedUSD == 127_251.35)
#expect(snapshot.planLimitUSD == 281_220.0)
}
@Test
func `team on-demand pool is the budget and personal spend rides along`() {
// Live team-plan payload (sanitized): the user's own on-demand spend has no personal limit,
// so the team pool is the headline budget. The personal spend must still be surfaced.
let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))
.parseUsageSummary(
CursorUsageSummary(
billingCycleStart: "2026-06-01T00:00:00.000Z",
billingCycleEnd: "2026-07-01T00:00:00.000Z",
membershipType: "enterprise",
limitType: "team",
isUnlimited: false,
autoModelSelectedDisplayMessage: nil,
namedModelSelectedDisplayMessage: nil,
individualUsage: CursorIndividualUsage(
plan: CursorPlanUsage(
enabled: true,
used: 2000,
limit: 2000,
remaining: 0,
breakdown: nil,
autoPercentUsed: 0,
apiPercentUsed: 100,
totalPercentUsed: 100),
onDemand: CursorOnDemandUsage(enabled: true, used: 4471, limit: nil, remaining: nil),
overall: nil),
teamUsage: CursorTeamUsage(
onDemand: CursorOnDemandUsage(
enabled: true,
used: 1_311_125,
limit: 2_000_000,
remaining: 688_875),
pooled: nil)),
userInfo: nil,
rawJSON: nil)
let cost = snapshot.toUsageSnapshot().providerCost
#expect(cost?.used == 13111.25) // team pool used
#expect(cost?.limit == 20000.0) // team pool limit
#expect(cost?.personalUsed == 44.71) // this account's own on-demand spend
}
@Test
func `personal on-demand limit keeps personal budget with no rider`() {
// When the user has their own on-demand limit, that is the budget and there is no separate rider.
let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))
.parseUsageSummary(
CursorUsageSummary(
billingCycleStart: nil,
billingCycleEnd: nil,
membershipType: "pro",
limitType: "user",
isUnlimited: false,
autoModelSelectedDisplayMessage: nil,
namedModelSelectedDisplayMessage: nil,
individualUsage: CursorIndividualUsage(
plan: nil,
onDemand: CursorOnDemandUsage(enabled: true, used: 4471, limit: 10000, remaining: 5529),
overall: nil),
teamUsage: nil),
userInfo: nil,
rawJSON: nil)
let cost = snapshot.toUsageSnapshot().providerCost
#expect(cost?.used == 44.71)
#expect(cost?.limit == 100.0)
#expect(cost?.personalUsed == nil)
}
@Test
func `existing plan block still wins over overall and pooled`() {
// Guard against future drift: when Cursor sends both legacy `plan` and the newer `overall`
// blocks, the existing percent precedence must remain intact.
let snapshot = CursorStatusProbe(browserDetection: BrowserDetection(cacheTTL: 0))
.parseUsageSummary(
CursorUsageSummary(
billingCycleStart: nil,
billingCycleEnd: nil,
membershipType: "pro",
limitType: "user",
isUnlimited: false,
autoModelSelectedDisplayMessage: nil,
namedModelSelectedDisplayMessage: nil,
individualUsage: CursorIndividualUsage(
plan: CursorPlanUsage(
enabled: true,
used: 1500,
limit: 5000,
remaining: 3500,
breakdown: nil,
autoPercentUsed: nil,
apiPercentUsed: nil,
totalPercentUsed: 30.0),
onDemand: nil,
overall: CursorOverallUsage(enabled: true, used: 7384, limit: 10000, remaining: 2616)),
teamUsage: CursorTeamUsage(
onDemand: nil,
pooled: CursorPooledUsage(
enabled: true,
used: 12_725_135,
limit: 28_122_000,
remaining: 15_396_865))),
userInfo: nil,
rawJSON: nil)
#expect(snapshot.planPercentUsed == 30.0)
#expect(snapshot.planUsedUSD == 15.0)
#expect(snapshot.planLimitUSD == 50.0)
}
}