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

179 lines
6.3 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct ElevenLabsUsageFetcherTests {
@Test
func `parses subscription response into usage snapshot`() throws {
let body = #"""
{
"tier": "creator",
"character_count": 25000,
"character_limit": 100000,
"voice_slots_used": 2,
"voice_limit": 10,
"professional_voice_slots_used": 1,
"professional_voice_limit": 2,
"current_overage": {"amount": "0", "currency": "usd"},
"status": "active",
"next_character_count_reset_unix": 1738356858
}
"""#
let snapshot = try ElevenLabsUsageFetcher._parseSnapshotForTesting(
Data(body.utf8),
updatedAt: Date(timeIntervalSince1970: 1))
let usage = snapshot.toUsageSnapshot()
#expect(snapshot.characterCount == 25000)
#expect(snapshot.characterLimit == 100_000)
#expect(snapshot.usedPercent == 25)
#expect(snapshot.remainingCharacters == 75000)
#expect(usage.primary?.usedPercent == 25)
#expect(usage.primary?.resetDescription == "25,000 / 100,000 credits")
#expect(usage.primary?.resetsAt == Date(timeIntervalSince1970: 1_738_356_858))
#expect(usage.loginMethod(for: .elevenlabs) == "Creator")
#expect(usage.extraRateWindows?.count == 2)
}
@Test
func `fetch usage sends xi api key header`() async throws {
let registered = URLProtocol.registerClass(ElevenLabsStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(ElevenLabsStubURLProtocol.self)
}
ElevenLabsStubURLProtocol.handler = nil
}
ElevenLabsStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
#expect(url.path == "/v1/user/subscription")
#expect(request.value(forHTTPHeaderField: "xi-api-key") == "xi-test")
#expect(request.timeoutInterval == 15)
let body = #"""
{
"tier": "starter",
"character_count": 1000,
"character_limit": 10000,
"status": "active"
}
"""#
return Self.makeResponse(url: url, body: body, statusCode: 200)
}
let usage = try await ElevenLabsUsageFetcher.fetchUsage(
apiKey: " xi-test ",
environment: [ElevenLabsSettingsReader.apiURLEnvironmentKey: "https://elevenlabs.test"])
#expect(usage.characterCount == 1000)
#expect(usage.characterLimit == 10000)
#expect(usage.usedPercent == 10)
}
@Test
func `fetch usage accepts versioned API base with trailing slash`() async throws {
let registered = URLProtocol.registerClass(ElevenLabsStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(ElevenLabsStubURLProtocol.self)
}
ElevenLabsStubURLProtocol.handler = nil
}
ElevenLabsStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
#expect(url.path == "/v1/user/subscription")
let body = #"""
{
"tier": "starter",
"character_count": 1000,
"character_limit": 10000,
"status": "active"
}
"""#
return Self.makeResponse(url: url, body: body, statusCode: 200)
}
let usage = try await ElevenLabsUsageFetcher.fetchUsage(
apiKey: "xi-test",
environment: [ElevenLabsSettingsReader.apiURLEnvironmentKey: "https://elevenlabs.test/v1/"])
#expect(usage.characterCount == 1000)
}
@Test
func `non success fetch throws generic HTTP error`() async throws {
let registered = URLProtocol.registerClass(ElevenLabsStubURLProtocol.self)
defer {
if registered {
URLProtocol.unregisterClass(ElevenLabsStubURLProtocol.self)
}
ElevenLabsStubURLProtocol.handler = nil
}
ElevenLabsStubURLProtocol.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
return Self.makeResponse(url: url, body: #"{"detail":"bad xi-test"}"#, statusCode: 500)
}
do {
_ = try await ElevenLabsUsageFetcher.fetchUsage(
apiKey: "xi-test",
environment: [ElevenLabsSettingsReader.apiURLEnvironmentKey: "https://elevenlabs.test"])
Issue.record("Expected ElevenLabsUsageError.apiError")
} catch let error as ElevenLabsUsageError {
guard case let .apiError(message) = error else {
Issue.record("Expected apiError, got \(error)")
return
}
#expect(message == "HTTP 500")
}
}
private static func makeResponse(
url: URL,
body: String,
statusCode: Int = 200) -> (HTTPURLResponse, Data)
{
let response = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "application/json"])!
return (response, Data(body.utf8))
}
}
final class ElevenLabsStubURLProtocol: URLProtocol {
nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
override static func canInit(with request: URLRequest) -> Bool {
request.url?.host == "elevenlabs.test"
}
override static func canonicalRequest(for request: URLRequest) -> URLRequest {
request
}
override func startLoading() {
guard let handler = Self.handler else {
self.client?.urlProtocol(self, didFailWithError: URLError(.badServerResponse))
return
}
do {
let (response, data) = try handler(self.request)
self.client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
self.client?.urlProtocol(self, didLoad: data)
self.client?.urlProtocolDidFinishLoading(self)
} catch {
self.client?.urlProtocol(self, didFailWithError: error)
}
}
override func stopLoading() {}
}