252 lines
8.5 KiB
Swift
252 lines
8.5 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import CodexBarCore
|
|
|
|
@Suite(.serialized)
|
|
struct PoeUsageFetcherTests {
|
|
@Test
|
|
func `parse snapshot extracts current point balance`() throws {
|
|
let json = #"{"current_point_balance": 1500}"#
|
|
let data = Data(json.utf8)
|
|
let snapshot = try PoeUsageFetcher._parseSnapshotForTesting(data)
|
|
#expect(snapshot.currentPointBalance == 1500)
|
|
}
|
|
|
|
@Test
|
|
func `parse snapshot accepts string-encoded balance`() throws {
|
|
let json = #"{"current_point_balance": "2500"}"#
|
|
let snapshot = try PoeUsageFetcher._parseSnapshotForTesting(Data(json.utf8))
|
|
#expect(snapshot.currentPointBalance == 2500)
|
|
}
|
|
|
|
@Test
|
|
func `parse snapshot returns nil balance when absent`() throws {
|
|
let json = #"{}"#
|
|
let snapshot = try PoeUsageFetcher._parseSnapshotForTesting(Data(json.utf8))
|
|
#expect(snapshot.currentPointBalance == nil)
|
|
}
|
|
|
|
@Test
|
|
func `parse snapshot throws on malformed JSON`() {
|
|
#expect {
|
|
_ = try PoeUsageFetcher._parseSnapshotForTesting(Data("not-json".utf8))
|
|
} throws: { error in
|
|
guard case PoeUsageError.parseFailed = error else { return false }
|
|
return true
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `snapshot maps balance to identity loginMethod only, not RateWindow`() {
|
|
let snapshot = PoeUsageSnapshot(
|
|
currentPointBalance: 500,
|
|
updatedAt: Date())
|
|
|
|
let unified = snapshot.toUsageSnapshot()
|
|
// No rate windows — balance is not a usage percentage
|
|
#expect(unified.primary == nil)
|
|
#expect(unified.secondary == nil)
|
|
#expect(unified.tertiary == nil)
|
|
// Balance lives in identity.loginMethod as "Balance: X points"
|
|
#expect(unified.identity?.providerID == .poe)
|
|
#expect(unified.identity?.loginMethod == "Balance: 500 points")
|
|
}
|
|
|
|
@Test
|
|
func `snapshot hides balance when balance is absent`() {
|
|
let snapshot = PoeUsageSnapshot(
|
|
currentPointBalance: nil,
|
|
updatedAt: Date())
|
|
|
|
let unified = snapshot.toUsageSnapshot()
|
|
#expect(unified.primary == nil)
|
|
#expect(unified.identity?.loginMethod == nil)
|
|
}
|
|
|
|
@Test
|
|
func `missing credentials fetch call throws missing credentials`() async {
|
|
do {
|
|
_ = try await PoeUsageFetcher.fetchUsage(apiKey: " ")
|
|
Issue.record("Expected missingCredentials error")
|
|
} catch let error as PoeUsageError {
|
|
guard case .missingCredentials = error else {
|
|
Issue.record("Expected .missingCredentials but got \(error)")
|
|
return
|
|
}
|
|
} catch {
|
|
Issue.record("Unexpected error: \(error)")
|
|
}
|
|
}
|
|
|
|
@Test
|
|
func `compact number formats thousands with no decimals`() {
|
|
#expect(PoeUsageSnapshot.compactNumber(1500) == "1,500")
|
|
#expect(PoeUsageSnapshot.compactNumber(999) == "999")
|
|
#expect(PoeUsageSnapshot.compactNumber(10000) == "10,000")
|
|
}
|
|
|
|
@Test
|
|
func `history page parser extracts entries and cursor`() throws {
|
|
let json = """
|
|
{
|
|
"data": [
|
|
{
|
|
"query_id": "a1",
|
|
"creation_time": 1717000000000000,
|
|
"bot_name": "GPT-4o",
|
|
"usage_type": "API",
|
|
"cost_points": 12.5,
|
|
"cost_usd": "0.03"
|
|
},
|
|
{
|
|
"query_id": "a2",
|
|
"creation_time": 1717003600,
|
|
"bot_name": "Claude Sonnet",
|
|
"usage_type": "Chat",
|
|
"cost_points": "8",
|
|
"usd": "0.02"
|
|
}
|
|
],
|
|
"next_cursor": "cursor-2"
|
|
}
|
|
"""
|
|
|
|
let parsed = try PoeUsageFetcher._parseHistoryPageForTesting(Data(json.utf8))
|
|
#expect(parsed.entries.count == 2)
|
|
#expect(parsed.entries[0].model == "GPT-4o")
|
|
#expect(parsed.entries[0].points == 12.5)
|
|
#expect(parsed.entries[0].id == "a1")
|
|
#expect(parsed.entries[1].costUSD == 0.02)
|
|
#expect(parsed.nextCursor == "cursor-2")
|
|
}
|
|
|
|
@Test
|
|
func `history parser derives cursor from has_more and last query id`() throws {
|
|
let json = """
|
|
{
|
|
"has_more": true,
|
|
"data": [
|
|
{
|
|
"query_id": "q-1",
|
|
"creation_time": 1717000000,
|
|
"bot_name": "GPT-4o",
|
|
"usage_type": "API",
|
|
"cost_points": 3
|
|
},
|
|
{
|
|
"query_id": "q-2",
|
|
"creation_time": 1717003600,
|
|
"bot_name": "Claude Sonnet",
|
|
"usage_type": "Chat",
|
|
"cost_points": 9
|
|
}
|
|
]
|
|
}
|
|
"""
|
|
|
|
let parsed = try PoeUsageFetcher._parseHistoryPageForTesting(Data(json.utf8))
|
|
#expect(parsed.entries.count == 2)
|
|
#expect(parsed.nextCursor == "q-2")
|
|
}
|
|
|
|
@Test
|
|
func `history daily aggregation groups by utc day`() {
|
|
let entries = [
|
|
PoeUsageHistorySnapshot.Entry(
|
|
id: "1",
|
|
createdAt: Date(timeIntervalSince1970: 1_717_000_000),
|
|
model: "GPT-4o",
|
|
usageType: "inference",
|
|
points: 10,
|
|
costUSD: 0.02),
|
|
PoeUsageHistorySnapshot.Entry(
|
|
id: "2",
|
|
createdAt: Date(timeIntervalSince1970: 1_717_000_100),
|
|
model: "GPT-4o",
|
|
usageType: "inference",
|
|
points: 5,
|
|
costUSD: 0.01),
|
|
]
|
|
|
|
let daily = PoeUsageFetcher._buildDailyBucketsForTesting(entries: entries)
|
|
#expect(daily.count == 1)
|
|
#expect(daily[0].requests == 2)
|
|
#expect(daily[0].points == 15)
|
|
#expect(daily[0].costUSD == 0.03)
|
|
}
|
|
|
|
@Test
|
|
func `fetch usage returns balance when points history endpoint fails`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
let url = try #require(request.url)
|
|
let path = url.path
|
|
let response: (Data, Int)
|
|
if path.contains("current_balance") {
|
|
response = (Data(#"{"current_point_balance": 1500}"#.utf8), 200)
|
|
} else if path.contains("points_history") {
|
|
// Simulate a 500 from the optional history endpoint.
|
|
response = (Data("server error".utf8), 500)
|
|
} else {
|
|
Issue.record("Unexpected request: \(url.absoluteString)")
|
|
return Self.httpResponse(url: nil, status: 0)
|
|
}
|
|
return Self.httpResponse(url: nil, status: response.1, body: response.0)
|
|
}
|
|
|
|
let snapshot = try await PoeUsageFetcher._fetchUsage(
|
|
apiKey: "test-key",
|
|
transport: transport)
|
|
|
|
#expect(snapshot.currentPointBalance == 1500)
|
|
// History should be nil, not propagate the failure.
|
|
#expect(snapshot.history == nil)
|
|
}
|
|
|
|
@Test
|
|
func `fetch usage surfaces history snapshot when history endpoint succeeds`() async throws {
|
|
let transport = ProviderHTTPTransportStub { request in
|
|
let url = try #require(request.url)
|
|
let path = url.path
|
|
if path.contains("current_balance") {
|
|
return Self.httpResponse(
|
|
url: nil,
|
|
status: 200,
|
|
body: Data(#"{"current_point_balance": 2500}"#.utf8))
|
|
}
|
|
if path.contains("points_history") {
|
|
return Self.httpResponse(
|
|
url: nil,
|
|
status: 200,
|
|
body: Data("""
|
|
{"data":[],"next_cursor":null}
|
|
""".utf8))
|
|
}
|
|
Issue.record("Unexpected request: \(url.absoluteString)")
|
|
return Self.httpResponse(url: nil, status: 0)
|
|
}
|
|
|
|
let snapshot = try await PoeUsageFetcher._fetchUsage(
|
|
apiKey: "test-key",
|
|
transport: transport)
|
|
|
|
#expect(snapshot.currentPointBalance == 2500)
|
|
// Empty history page still produces a non-nil snapshot with empty buckets.
|
|
#expect(snapshot.history == nil)
|
|
}
|
|
}
|
|
|
|
extension PoeUsageFetcherTests {
|
|
fileprivate static func httpResponse(
|
|
url: URL?,
|
|
status: Int,
|
|
body: Data = Data()) -> (Data, URLResponse)
|
|
{
|
|
let response = HTTPURLResponse(
|
|
url: url ?? URL(string: "https://example.invalid")!,
|
|
statusCode: status,
|
|
httpVersion: "HTTP/1.1",
|
|
headerFields: nil) ?? HTTPURLResponse()
|
|
return (body, response)
|
|
}
|
|
}
|