201 lines
7.9 KiB
Swift
201 lines
7.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import CodexBarCore
|
|
|
|
struct CopilotUsageFetcherTests {
|
|
@Test
|
|
func `fetchGitHubIdentity uses shared client`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
guard request.value(forHTTPHeaderField: "Authorization") == "token abc123" else {
|
|
throw URLError(.userAuthenticationRequired)
|
|
}
|
|
let response = try HTTPURLResponse(
|
|
url: #require(request.url),
|
|
statusCode: 200,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"])!
|
|
return (Data(#"{"login":"testuser","id":123}"#.utf8), response)
|
|
}
|
|
|
|
let identity = try await CopilotUsageFetcher.fetchGitHubIdentity(token: "abc123", transport: transport)
|
|
|
|
#expect(identity.login == "testuser")
|
|
#expect(identity.id == 123)
|
|
let requests = await transport.requests()
|
|
#expect(requests.count == 1)
|
|
#expect(requests.first?.url?.host == "api.github.com")
|
|
}
|
|
|
|
@Test
|
|
func `fetch returns unavailable snapshot for business token billing placeholders`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
#expect(request.value(forHTTPHeaderField: "Authorization") == "token gh-token")
|
|
let response = try HTTPURLResponse(
|
|
url: #require(request.url),
|
|
statusCode: 200,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"])!
|
|
let data = Data(
|
|
"""
|
|
{
|
|
"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"
|
|
}
|
|
}
|
|
}
|
|
""".utf8)
|
|
return (data, response)
|
|
}
|
|
let fetcher = CopilotUsageFetcher(token: "gh-token", transport: transport)
|
|
|
|
let snapshot = try await fetcher.fetch()
|
|
|
|
#expect(snapshot.primary == nil)
|
|
#expect(snapshot.secondary == nil)
|
|
#expect(snapshot.identity?.loginMethod == "Business")
|
|
}
|
|
|
|
@Test
|
|
func `fetch keeps explicitly unlimited chat quota`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
#expect(request.value(forHTTPHeaderField: "Authorization") == "token gh-token")
|
|
let response = try HTTPURLResponse(
|
|
url: #require(request.url),
|
|
statusCode: 200,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"])!
|
|
let data = Data(
|
|
"""
|
|
{
|
|
"copilot_plan": "individual",
|
|
"quota_reset_date": "2026-07-01",
|
|
"quota_snapshots": {
|
|
"chat_messages": {
|
|
"entitlement": 0,
|
|
"remaining": 0,
|
|
"quota_id": "chat_messages",
|
|
"unlimited": true
|
|
}
|
|
}
|
|
}
|
|
""".utf8)
|
|
return (data, response)
|
|
}
|
|
let fetcher = CopilotUsageFetcher(token: "gh-token", transport: transport)
|
|
|
|
let snapshot = try await fetcher.fetch()
|
|
|
|
#expect(snapshot.primary == nil)
|
|
#expect(snapshot.secondary?.usedPercent == 0)
|
|
#expect(snapshot.secondary?.resetsAt == nil)
|
|
#expect(snapshot.identity?.loginMethod == "Individual")
|
|
}
|
|
|
|
@Test
|
|
func `fetch attaches quota reset date to copilot windows`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
#expect(request.value(forHTTPHeaderField: "Authorization") == "token gh-token")
|
|
let response = try HTTPURLResponse(
|
|
url: #require(request.url),
|
|
statusCode: 200,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: ["Content-Type": "application/json"])!
|
|
let data = Data(
|
|
"""
|
|
{
|
|
"copilot_plan": "individual",
|
|
"quota_reset_date": "2026-07-01",
|
|
"quota_snapshots": {
|
|
"premium_interactions": {
|
|
"entitlement": 500,
|
|
"remaining": 125,
|
|
"percent_remaining": 25,
|
|
"quota_id": "premium_interactions"
|
|
},
|
|
"chat": {
|
|
"entitlement": 300,
|
|
"remaining": 240,
|
|
"percent_remaining": 80,
|
|
"quota_id": "chat"
|
|
}
|
|
}
|
|
}
|
|
""".utf8)
|
|
return (data, response)
|
|
}
|
|
let fetcher = CopilotUsageFetcher(token: "gh-token", transport: transport)
|
|
let expectedReset = try #require(CopilotUsageFetcher.parseQuotaResetDate("2026-07-01"))
|
|
|
|
let snapshot = try await fetcher.fetch()
|
|
|
|
#expect(snapshot.primary?.usedPercent == 75)
|
|
#expect(snapshot.primary?.resetsAt == expectedReset)
|
|
#expect(snapshot.secondary?.usedPercent == 20)
|
|
#expect(snapshot.secondary?.resetsAt == expectedReset)
|
|
}
|
|
|
|
@Test
|
|
func `makeRateWindow drops business token billing placeholder quota`() {
|
|
// entitlement=0/remaining=0/percent_remaining=100 must not become a "0% used"
|
|
// rate window for Copilot Business token-based billing accounts. (#1258)
|
|
let placeholder = CopilotUsageResponse.QuotaSnapshot(
|
|
entitlement: 0,
|
|
remaining: 0,
|
|
percentRemaining: 100,
|
|
quotaId: "premium_interactions")
|
|
#expect(CopilotUsageFetcher.makeRateWindow(from: placeholder) == nil)
|
|
}
|
|
|
|
@Test
|
|
func `makeRateWindow keeps real quota window`() {
|
|
let real = CopilotUsageResponse.QuotaSnapshot(
|
|
entitlement: 500,
|
|
remaining: 125,
|
|
percentRemaining: 25,
|
|
quotaId: "premium_interactions")
|
|
let window = CopilotUsageFetcher.makeRateWindow(from: real)
|
|
#expect(window?.usedPercent == 75)
|
|
}
|
|
|
|
@Test
|
|
func `makeRateWindow carries reset date`() {
|
|
let resetDate = Date(timeIntervalSince1970: 1_783_468_800)
|
|
let real = CopilotUsageResponse.QuotaSnapshot(
|
|
entitlement: 500,
|
|
remaining: 125,
|
|
percentRemaining: 25,
|
|
quotaId: "premium_interactions")
|
|
|
|
let window = CopilotUsageFetcher.makeRateWindow(from: real, resetsAt: resetDate)
|
|
|
|
#expect(window?.usedPercent == 75)
|
|
#expect(window?.resetsAt == resetDate)
|
|
}
|
|
|
|
@Test
|
|
func `parseQuotaResetDate supports date only and ISO timestamps`() throws {
|
|
let dateOnly = try #require(ISO8601DateFormatter().date(from: "2026-07-01T00:00:00Z"))
|
|
let iso = try #require(ISO8601DateFormatter().date(from: "2026-07-01T08:30:45Z"))
|
|
let fractionalFormatter = ISO8601DateFormatter()
|
|
fractionalFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
let fractionalISO = try #require(fractionalFormatter.date(from: "2026-07-01T08:30:45.123Z"))
|
|
|
|
#expect(CopilotUsageFetcher.parseQuotaResetDate("2026-07-01") == dateOnly)
|
|
#expect(CopilotUsageFetcher.parseQuotaResetDate("2026-07-01T08:30:45Z") == iso)
|
|
#expect(CopilotUsageFetcher.parseQuotaResetDate("2026-07-01T08:30:45.123Z") == fractionalISO)
|
|
#expect(CopilotUsageFetcher.parseQuotaResetDate(" ") == nil)
|
|
}
|
|
}
|