231 lines
7.3 KiB
Swift
231 lines
7.3 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import CodexBarCore
|
|
|
|
struct WarpUsageFetcherTests {
|
|
@Test
|
|
func `parses snapshot and aggregates bonus credits`() throws {
|
|
let json = """
|
|
{
|
|
"data": {
|
|
"user": {
|
|
"__typename": "UserOutput",
|
|
"user": {
|
|
"requestLimitInfo": {
|
|
"isUnlimited": false,
|
|
"nextRefreshTime": "2026-02-28T19:16:33.462988Z",
|
|
"requestLimit": 1500,
|
|
"requestsUsedSinceLastRefresh": 5
|
|
},
|
|
"bonusGrants": [
|
|
{
|
|
"requestCreditsGranted": 20,
|
|
"requestCreditsRemaining": 10,
|
|
"expiration": "2026-03-01T10:00:00Z"
|
|
}
|
|
],
|
|
"workspaces": [
|
|
{
|
|
"bonusGrantsInfo": {
|
|
"grants": [
|
|
{
|
|
"requestCreditsGranted": "15",
|
|
"requestCreditsRemaining": "5",
|
|
"expiration": "2026-03-15T10:00:00Z"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let snapshot = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
let formatter = ISO8601DateFormatter()
|
|
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
let expectedRefresh = formatter.date(from: "2026-02-28T19:16:33.462988Z")
|
|
let expectedExpiry = ISO8601DateFormatter().date(from: "2026-03-01T10:00:00Z")
|
|
|
|
#expect(snapshot.requestLimit == 1500)
|
|
#expect(snapshot.requestsUsed == 5)
|
|
#expect(snapshot.isUnlimited == false)
|
|
#expect(snapshot.nextRefreshTime != nil)
|
|
#expect(abs((snapshot.nextRefreshTime?.timeIntervalSince1970 ?? 0) -
|
|
(expectedRefresh?.timeIntervalSince1970 ?? 0))
|
|
< 0.5)
|
|
#expect(snapshot.bonusCreditsTotal == 35)
|
|
#expect(snapshot.bonusCreditsRemaining == 15)
|
|
#expect(snapshot.bonusNextExpirationRemaining == 10)
|
|
#expect(abs((snapshot.bonusNextExpiration?.timeIntervalSince1970 ?? 0) -
|
|
(expectedExpiry?.timeIntervalSince1970 ?? 0))
|
|
< 0.5)
|
|
}
|
|
|
|
@Test
|
|
func `graph QL errors throw API error`() {
|
|
let json = """
|
|
{
|
|
"errors": [
|
|
{ "message": "Unauthorized" }
|
|
]
|
|
}
|
|
"""
|
|
|
|
#expect {
|
|
_ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
} throws: { error in
|
|
guard case let WarpUsageError.apiError(code, message) = error else { return false }
|
|
return code == 200 && message.contains("Unauthorized")
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `null unlimited and string numerics parse safely`() throws {
|
|
let json = """
|
|
{
|
|
"data": {
|
|
"user": {
|
|
"__typename": "UserOutput",
|
|
"user": {
|
|
"requestLimitInfo": {
|
|
"isUnlimited": null,
|
|
"nextRefreshTime": "2026-02-28T19:16:33Z",
|
|
"requestLimit": "1500",
|
|
"requestsUsedSinceLastRefresh": "5"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
let snapshot = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
|
|
#expect(snapshot.isUnlimited == false)
|
|
#expect(snapshot.requestLimit == 1500)
|
|
#expect(snapshot.requestsUsed == 5)
|
|
#expect(snapshot.nextRefreshTime != nil)
|
|
}
|
|
|
|
@Test
|
|
func `unexpected typename returns parse error`() {
|
|
let json = """
|
|
{
|
|
"data": {
|
|
"user": {
|
|
"__typename": "AuthError"
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
#expect {
|
|
_ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
} throws: { error in
|
|
guard case let WarpUsageError.parseFailed(message) = error else { return false }
|
|
return message.contains("Unexpected user type")
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `missing request limit info returns parse error`() {
|
|
let json = """
|
|
{
|
|
"data": {
|
|
"user": {
|
|
"__typename": "UserOutput",
|
|
"user": {}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
#expect {
|
|
_ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
} throws: { error in
|
|
guard case let WarpUsageError.parseFailed(message) = error else { return false }
|
|
return message.contains("requestLimitInfo")
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `invalid root returns parse error`() {
|
|
let json = """
|
|
[{ "data": {} }]
|
|
"""
|
|
|
|
#expect {
|
|
_ = try WarpUsageFetcher._parseResponseForTesting(Data(json.utf8))
|
|
} throws: { error in
|
|
guard case let WarpUsageError.parseFailed(message) = error else { return false }
|
|
return message == "Root JSON is not an object."
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `to usage snapshot omits secondary when no bonus credits`() {
|
|
let source = WarpUsageSnapshot(
|
|
requestLimit: 100,
|
|
requestsUsed: 10,
|
|
nextRefreshTime: Date().addingTimeInterval(3600),
|
|
isUnlimited: false,
|
|
updatedAt: Date(),
|
|
bonusCreditsRemaining: 0,
|
|
bonusCreditsTotal: 0,
|
|
bonusNextExpiration: nil,
|
|
bonusNextExpirationRemaining: 0)
|
|
|
|
let snapshot = source.toUsageSnapshot()
|
|
#expect(snapshot.secondary == nil)
|
|
}
|
|
|
|
@Test
|
|
func `to usage snapshot keeps bonus window when bonus exists`() throws {
|
|
let source = WarpUsageSnapshot(
|
|
requestLimit: 100,
|
|
requestsUsed: 10,
|
|
nextRefreshTime: Date().addingTimeInterval(3600),
|
|
isUnlimited: false,
|
|
updatedAt: Date(),
|
|
bonusCreditsRemaining: 0,
|
|
bonusCreditsTotal: 20,
|
|
bonusNextExpiration: nil,
|
|
bonusNextExpirationRemaining: 0)
|
|
|
|
let snapshot = source.toUsageSnapshot()
|
|
let secondary = try #require(snapshot.secondary)
|
|
#expect(secondary.usedPercent == 100)
|
|
}
|
|
|
|
@Test
|
|
func `to usage snapshot unlimited primary does not show reset date`() throws {
|
|
let source = WarpUsageSnapshot(
|
|
requestLimit: 0,
|
|
requestsUsed: 0,
|
|
nextRefreshTime: Date().addingTimeInterval(3600),
|
|
isUnlimited: true,
|
|
updatedAt: Date(),
|
|
bonusCreditsRemaining: 0,
|
|
bonusCreditsTotal: 0,
|
|
bonusNextExpiration: nil,
|
|
bonusNextExpirationRemaining: 0)
|
|
|
|
let snapshot = source.toUsageSnapshot()
|
|
let primary = try #require(snapshot.primary)
|
|
#expect(primary.resetsAt == nil)
|
|
#expect(primary.resetDescription == "Unlimited")
|
|
}
|
|
|
|
@Test
|
|
func `api error summary includes plain text bodies`() {
|
|
// Regression: Warp edge returns 429 with a non-JSON body ("Rate exceeded.") when User-Agent is missing/wrong.
|
|
let summary = WarpUsageFetcher._apiErrorSummaryForTesting(
|
|
statusCode: 429,
|
|
data: Data("Rate exceeded.".utf8))
|
|
#expect(summary.contains("Rate exceeded."))
|
|
}
|
|
}
|