Files
2026-07-13 12:22:33 +08:00

578 lines
19 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
struct CopilotUsageModelsTests {
@Test
func `decodes quota snapshots payload`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"assigned_date": "2025-01-01",
"quota_reset_date": "2025-02-01",
"quota_snapshots": {
"premium_interactions": {
"entitlement": 500,
"remaining": 450,
"percent_remaining": 90,
"quota_id": "premium_interactions"
},
"chat": {
"entitlement": 300,
"remaining": 150,
"percent_remaining": 50,
"quota_id": "chat"
}
}
}
""")
#expect(response.copilotPlan == "free")
#expect(response.assignedDate == "2025-01-01")
#expect(response.quotaResetDate == "2025-02-01")
#expect(response.quotaSnapshots.premiumInteractions?.remaining == 450)
#expect(response.quotaSnapshots.chat?.remaining == 150)
}
@Test
func `decodes chat only quota snapshots payload`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 200,
"remaining": 75,
"percent_remaining": 37.5,
"quota_id": "chat"
}
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat?.quotaId == "chat")
#expect(response.quotaSnapshots.chat?.entitlement == 200)
#expect(response.quotaSnapshots.chat?.remaining == 75)
}
@Test
func `preserves missing date fields as nil`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 200,
"remaining": 75,
"percent_remaining": 37.5,
"quota_id": "chat"
}
}
}
""")
#expect(response.assignedDate == nil)
#expect(response.quotaResetDate == nil)
}
@Test
func `preserves explicit empty date fields`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"assigned_date": "",
"quota_reset_date": "",
"quota_snapshots": {
"chat": {
"entitlement": 200,
"remaining": 75,
"percent_remaining": 37.5,
"quota_id": "chat"
}
}
}
""")
#expect(response.assignedDate?.isEmpty == true)
#expect(response.quotaResetDate?.isEmpty == true)
}
@Test
func `decodes monthly and limited quota payload`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"monthly_quotas": {
"chat": "500",
"completions": 300
},
"limited_user_quotas": {
"chat": 125,
"completions": "75"
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
#expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
#expect(response.quotaSnapshots.premiumInteractions?.remaining == 75)
#expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 25)
#expect(response.quotaSnapshots.chat?.quotaId == "chat")
#expect(response.quotaSnapshots.chat?.entitlement == 500)
#expect(response.quotaSnapshots.chat?.remaining == 125)
#expect(response.quotaSnapshots.chat?.percentRemaining == 25)
}
@Test
func `does not assume full quota when limited quotas are missing`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"monthly_quotas": {
"chat": 500,
"completions": 300
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat == nil)
}
@Test
func `computes monthly fallback per quota only when limited value exists`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"monthly_quotas": {
"chat": 500,
"completions": 300
},
"limited_user_quotas": {
"completions": 60
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
#expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
#expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)
#expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)
#expect(response.quotaSnapshots.chat == nil)
}
@Test
func `merges direct and monthly fallback lanes when direct is partial`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 200,
"remaining": 75,
"percent_remaining": 37.5,
"quota_id": "chat"
}
},
"monthly_quotas": {
"chat": 500,
"completions": 300
},
"limited_user_quotas": {
"chat": 125,
"completions": 60
}
}
""")
#expect(response.quotaSnapshots.chat?.quotaId == "chat")
#expect(response.quotaSnapshots.chat?.entitlement == 200)
#expect(response.quotaSnapshots.chat?.remaining == 75)
#expect(response.quotaSnapshots.chat?.percentRemaining == 37.5)
#expect(response.quotaSnapshots.premiumInteractions?.quotaId == "completions")
#expect(response.quotaSnapshots.premiumInteractions?.entitlement == 300)
#expect(response.quotaSnapshots.premiumInteractions?.remaining == 60)
#expect(response.quotaSnapshots.premiumInteractions?.percentRemaining == 20)
}
@Test
func `decodes unknown quota snapshot keys using fallback`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"mystery_bucket": {
"entitlement": 100,
"remaining": 40,
"percent_remaining": 40,
"quota_id": "mystery_bucket"
}
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat?.quotaId == "mystery_bucket")
#expect(response.quotaSnapshots.chat?.entitlement == 100)
#expect(response.quotaSnapshots.chat?.remaining == 40)
#expect(response.quotaSnapshots.chat?.percentRemaining == 40)
}
@Test
func `ignores placeholder known snapshot when selecting unknown key fallback`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"premium_interactions": {},
"mystery_bucket": {
"entitlement": 100,
"remaining": 40,
"percent_remaining": 40,
"quota_id": "mystery_bucket"
}
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat?.quotaId == "mystery_bucket")
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
}
@Test
func `derives percent remaining when missing but entitlement exists`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 120,
"remaining": 30,
"quota_id": "chat"
}
}
}
""")
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
#expect(response.quotaSnapshots.chat?.percentRemaining == 25)
}
@Test
func `preserves over quota percent remaining`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "paid",
"quota_snapshots": {
"premium_interactions": {
"entitlement": 500,
"remaining": -75,
"percent_remaining": -15,
"quota_id": "premium_interactions"
}
}
}
""")
let snapshot = try #require(response.quotaSnapshots.premiumInteractions)
#expect(snapshot.percentRemaining == -15)
#expect(snapshot.usedPercent == 115)
#expect(snapshot.overQuotaUsedPercent == 115)
let window = try #require(CopilotUsageFetcher.makeRateWindow(from: snapshot))
#expect(window.usedPercent == 115)
#expect(window.resetDescription == "115% used")
}
@Test
func `derives over quota percent from negative remaining`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "paid",
"quota_snapshots": {
"chat": {
"entitlement": 500,
"remaining": -75,
"quota_id": "chat"
}
}
}
""")
let snapshot = try #require(response.quotaSnapshots.chat)
#expect(snapshot.hasPercentRemaining)
#expect(snapshot.percentRemaining == -15)
#expect(snapshot.usedPercent == 115)
#expect(snapshot.overQuotaUsedPercent == 115)
}
@Test
func `marks percent remaining as unavailable when underdetermined`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"remaining": 30,
"quota_id": "chat"
}
}
}
""")
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)
#expect(response.quotaSnapshots.chat?.percentRemaining == 0)
}
@Test
func `marks percent remaining as unavailable when remaining is missing`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 120,
"quota_id": "chat"
}
}
}
""")
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == false)
#expect(response.quotaSnapshots.chat?.percentRemaining == 0)
}
@Test
func `falls back to monthly when direct snapshot is missing remaining`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"entitlement": 120,
"quota_id": "chat"
}
},
"monthly_quotas": {
"chat": 400
},
"limited_user_quotas": {
"chat": 100
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat?.quotaId == "chat")
#expect(response.quotaSnapshots.chat?.entitlement == 400)
#expect(response.quotaSnapshots.chat?.remaining == 100)
#expect(response.quotaSnapshots.chat?.percentRemaining == 25)
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
}
@Test
func `falls back to monthly when direct snapshots lack computable percent`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"quota_snapshots": {
"chat": {
"remaining": 30,
"quota_id": "chat"
}
},
"monthly_quotas": {
"chat": 400
},
"limited_user_quotas": {
"chat": 100
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat?.quotaId == "chat")
#expect(response.quotaSnapshots.chat?.entitlement == 400)
#expect(response.quotaSnapshots.chat?.remaining == 100)
#expect(response.quotaSnapshots.chat?.percentRemaining == 25)
#expect(response.quotaSnapshots.chat?.hasPercentRemaining == true)
}
@Test
func `skips monthly fallback when monthly denominator is zero`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "free",
"monthly_quotas": {
"chat": 0
},
"limited_user_quotas": {
"chat": 0
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat == nil)
}
@Test
func `treats business token billing zero entitlement quotas as unavailable`() throws {
// GitHub Copilot Business token-based billing reports every quota as
// entitlement=0, remaining=0, percent_remaining=100. That previously rendered as a
// misleading "0% used" (100 - 100). A zero-entitlement quota carries no usage signal,
// so the snapshots must drop out instead of showing as usage. (#1258)
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "business",
"token_based_billing": true,
"quota_snapshots": {
"premium_interactions": {
"entitlement": 0,
"remaining": 0,
"percent_remaining": 100,
"quota_id": "premium_interactions"
},
"chat": {
"entitlement": 0,
"remaining": 0,
"percent_remaining": 100,
"quota_id": "chat"
},
"completions": {
"entitlement": 0,
"remaining": 0,
"percent_remaining": 100,
"quota_id": "completions"
}
}
}
""")
#expect(response.tokenBasedBilling)
#expect(response.quotaSnapshots.premiumInteractions == nil)
#expect(response.quotaSnapshots.chat == nil)
}
@Test
func `keeps unlimited chat fallback quota without percent remaining`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "individual",
"quota_snapshots": {
"premium_interactions": {
"entitlement": 200,
"remaining": 191,
"percent_remaining": 95.5,
"quota_id": "premium_interactions"
},
"chat_messages": {
"entitlement": 0,
"remaining": 0,
"quota_id": "chat_messages",
"unlimited": true
}
}
}
""")
#expect(response.quotaSnapshots.premiumInteractions?.quotaId == "premium_interactions")
#expect(response.quotaSnapshots.chat?.quotaId == "chat_messages")
#expect(response.quotaSnapshots.chat?.unlimited == true)
#expect(response.quotaSnapshots.chat?.usedPercent == 0)
}
@Test
func `unlimited quota overrides placeholder percent remaining`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "individual",
"quota_snapshots": {
"chat": {
"entitlement": 0,
"remaining": 0,
"percent_remaining": 0,
"quota_id": "chat",
"unlimited": true
}
}
}
""")
let chat = try #require(response.quotaSnapshots.chat)
#expect(chat.percentRemaining == 100)
#expect(chat.usedPercent == 0)
#expect(!chat.isPlaceholder)
}
@Test
func `flags zero entitlement snapshot as placeholder`() {
let snapshot = CopilotUsageResponse.QuotaSnapshot(
entitlement: 0,
remaining: 0,
percentRemaining: 100,
quotaId: "chat")
#expect(snapshot.isPlaceholder)
}
@Test
func `keeps fully consumed quota with positive entitlement`() {
// entitlement > 0 with remaining 0 is a real "100% used" window, not a placeholder.
let snapshot = CopilotUsageResponse.QuotaSnapshot(
entitlement: 500,
remaining: 0,
percentRemaining: 0,
quotaId: "premium_interactions")
#expect(!snapshot.isPlaceholder)
#expect(snapshot.usedPercent == 100)
}
@Test
func `keeps percent only quota snapshots available`() throws {
let response = try Self.decodeFixture(
"""
{
"copilot_plan": "business",
"quota_snapshots": {
"chat": {
"percent_remaining": 40,
"quota_id": "chat"
}
}
}
""")
#expect(response.quotaSnapshots.chat?.percentRemaining == 40)
#expect(response.quotaSnapshots.chat?.usedPercent == 60)
#expect(response.quotaSnapshots.chat?.isPlaceholder == false)
}
private static func decodeFixture(_ fixture: String) throws -> CopilotUsageResponse {
try JSONDecoder().decode(CopilotUsageResponse.self, from: Data(fixture.utf8))
}
}