import Foundationimport Testing@testable import CodexBar@testable import CodexBarCore struct ChutesProviderTests { @Test func `settings reader trims quoted API key`() { let token = ChutesSettingsReader.apiKey(environment: [ ChutesSettingsReader.apiKeyEnvironmentKey: " 'chutes-test' ", ]) #expect(token == "chutes-test") } @Test func `config API key projects into Chutes environment`() { let config = ProviderConfig(id: .chutes, apiKey: "chutes-config-token") let env = ProviderConfigEnvironment.applyAPIKeyOverride( base: [:], provider: .chutes, config: config) #expect(env[ChutesSettingsReader.apiKeyEnvironmentKey] == "chutes-config-token") #expect(ChutesSettingsReader.apiKey(environment: env) == "chutes-config-token") #expect(ProviderConfigEnvironment.supportsAPIKeyOverride(for: .chutes)) } @Test func `fetch usage maps active subscription monthly and rolling windows`() async throws { let now = Date(timeIntervalSince1970: 1_800_000_000) let rollingReset = try Self.date("2026-06-13T18:00:00Z") let monthlyReset = try Self.date("2026-07-01T00:00:00Z") let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) #expect(url.path == "/users/me/subscription_usage") #expect(request.httpMethod == "GET") #expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer chutes-key") #expect(request.value(forHTTPHeaderField: "Accept") == "application/json") #expect(request.timeoutInterval == 15) let body = #""" { "subscription": { "active": true, "plan_name": "Pro", "current_period_end": "2026-07-01T00:00:00Z" }, "monthly": { "used": 250, "limit": 1000, "resets_at": "2026-07-01T00:00:00Z", "unit": "credits" }, "rolling_window": { "requests": 40, "limit": 100, "window_minutes": 240, "reset_at": "2026-06-13T18:00:00Z", "unit": "requests" } } """# return Self.makeResponse(url: url, body: body) } let snapshot = try await ChutesUsageFetcher.fetchUsage( apiKey: " chutes-key ", environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"], transport: transport, now: now) let usage = snapshot.toUsageSnapshot() #expect(usage.primary?.usedPercent == 40) #expect(usage.primary?.windowMinutes == 240) #expect(usage.primary?.resetsAt == rollingReset) #expect(usage.primary?.resetDescription == "40/100 requests") #expect(usage.secondary?.usedPercent == 25) #expect(usage.secondary?.resetsAt == monthlyReset) #expect(usage.secondary?.resetDescription == "250/1000 credits") #expect(usage.subscriptionRenewsAt == monthlyReset) #expect(usage.loginMethod(for: .chutes) == "Pro") let requests = await transport.requests() #expect(requests.count == 1) } @Test func `no active subscription falls back to quotas endpoint`() async throws { let now = Date(timeIntervalSince1970: 1_800_000_000) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) switch url.path { case "/users/me/subscription_usage": return Self.makeResponse(url: url, body: #""" { "subscription": { "active": false, "status": "free" } } """#) case "/users/me/quotas": return Self.makeResponse(url: url, body: #""" [ { "chute_id": "0", "is_default": true, "quota": 100 } ] """#) case "/users/me/quota_usage/0": return Self.makeResponse(url: url, body: #""" { "quota": 100, "used": 10 } """#) default: throw URLError(.badURL) } } let snapshot = try await ChutesUsageFetcher.fetchUsage( apiKey: "chutes-key", environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"], transport: transport, now: now) let usage = snapshot.toUsageSnapshot() #expect(usage.primary?.usedPercent == 10) #expect(usage.primary?.resetDescription == "10/100 credits") #expect(usage.secondary == nil) #expect(usage.loginMethod(for: .chutes) == "No active subscription") let requests = await transport.requests() let paths = requests.compactMap { $0.url?.path } #expect(paths == [ "/users/me/subscription_usage", "/users/me/quotas", "/users/me/quota_usage/0", ]) } @Test func `wrapped quota list fetches per quota usage`() async throws { let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) switch url.path { case "/users/me/subscription_usage": return Self.makeResponse(url: url, body: #"{"subscription":{"active":false}}"#) case "/users/me/quotas": return Self.makeResponse(url: url, body: #""" { "data": [ { "chute_id": "wrapped", "quota": 200 } ] } """#) case "/users/me/quota_usage/wrapped": return Self.makeResponse(url: url, body: #"{"quota":200,"used":50}"#) default: throw URLError(.badURL) } } let snapshot = try await ChutesUsageFetcher.fetchUsage( apiKey: "chutes-key", environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"], transport: transport) #expect(snapshot.toUsageSnapshot().primary?.usedPercent == 25) let requests = await transport.requests() #expect(requests.compactMap { $0.url?.path } == [ "/users/me/subscription_usage", "/users/me/quotas", "/users/me/quota_usage/wrapped", ]) } @Test func `partial subscription usage fills missing rolling window from quotas`() async throws { let now = Date(timeIntervalSince1970: 1_800_000_000) let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) switch url.path { case "/users/me/subscription_usage": return Self.makeResponse(url: url, body: #""" { "subscription": { "active": true, "plan_name": "Pro", "current_period_end": "2026-07-01T00:00:00Z" }, "monthly": { "used": 250, "limit": 1000, "unit": "credits" } } """#) case "/users/me/quotas": return Self.makeResponse(url: url, body: #""" { "rolling_window": { "requests": 40, "limit": 100, "window_minutes": 240, "unit": "requests" } } """#) default: throw URLError(.badURL) } } let snapshot = try await ChutesUsageFetcher.fetchUsage( apiKey: "chutes-key", environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"], transport: transport, now: now) let usage = snapshot.toUsageSnapshot() #expect(usage.primary?.usedPercent == 40) #expect(usage.primary?.windowMinutes == 240) #expect(usage.primary?.resetDescription == "40/100 requests") #expect(usage.secondary?.usedPercent == 25) #expect(usage.secondary?.resetDescription == "250/1000 credits") #expect(usage.loginMethod(for: .chutes) == "Pro") let requests = await transport.requests() let paths = requests.compactMap { $0.url?.path } #expect(paths == ["/users/me/subscription_usage", "/users/me/quotas"]) } @Test func `missing usage fields returns no data snapshot without decode failure`() throws { let data = Data(#"{"subscription":{"active":true},"unexpected":{"nested":true}}"#.utf8) let snapshot = try ChutesUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123)) let usage = snapshot.toUsageSnapshot() #expect(!snapshot.hasUsageData) #expect(usage.primary == nil) #expect(usage.secondary == nil) #expect(usage.loginMethod(for: .chutes) == nil) } @Test func `identical usage values keep distinct quota windows`() throws { let data = Data(#""" { "quotas": [ { "used": 0, "limit": 100, "window_minutes": 240 }, { "used": 0, "limit": 100, "window_minutes": 43200 } ] } """#.utf8) let snapshot = try ChutesUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123)) let usage = snapshot.toUsageSnapshot() #expect(usage.primary?.usedPercent == 0) #expect(usage.primary?.windowMinutes == 240) #expect(usage.secondary?.usedPercent == 0) #expect(usage.secondary?.windowMinutes == 43200) } @Test func `exact percent value of one stays one percent`() throws { let usedData = Data(#""" { "rolling_window": { "usage_percent": 1 } } """#.utf8) let remainingData = Data(#""" { "rolling_window": { "percent_remaining": 1 } } """#.utf8) let usedSnapshot = try ChutesUsageParser.parse( data: usedData, now: Date(timeIntervalSince1970: 123)) let remainingSnapshot = try ChutesUsageParser.parse( data: remainingData, now: Date(timeIntervalSince1970: 123)) #expect(usedSnapshot.toUsageSnapshot().primary?.usedPercent == 1) #expect(remainingSnapshot.toUsageSnapshot().primary?.usedPercent == 99) } @Test func `auth failure surfaces invalid credentials`() async { let transport = ProviderHTTPTransportStub { request in let url = try #require(request.url) return Self.makeResponse(url: url, body: #"{"detail":"unauthorized"}"#, statusCode: 401) } await #expect { _ = try await ChutesUsageFetcher.fetchUsage( apiKey: "bad-key", environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"], transport: transport) } throws: { error in guard case ChutesUsageError.invalidCredentials = error else { return false } return true } } @Test func `descriptor and app implementation registry include Chutes`() throws { let descriptor = ProviderDescriptorRegistry.descriptor(for: .chutes) #expect(descriptor.metadata.displayName == "Chutes") #expect(ProviderDescriptorRegistry.all.contains { $0.id == .chutes }) let implementation = try #require(ProviderImplementationRegistry.implementation(for: .chutes)) #expect(implementation is ChutesProviderImplementation) } private static func makeResponse( url: URL, body: String, statusCode: Int = 200) -> (Data, URLResponse) { let response = HTTPURLResponse( url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": "application/json"])! return (Data(body.utf8), response) } private static func date(_ text: String) throws -> Date { let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withInternetDateTime] return try #require(formatter.date(from: text)) }}
import Foundation
import Testing
@testable import CodexBar
@testable import CodexBarCore
struct ChutesProviderTests {
@Test
func `settings reader trims quoted API key`() {
let token = ChutesSettingsReader.apiKey(environment: [
ChutesSettingsReader.apiKeyEnvironmentKey: " 'chutes-test' ",
])
#expect(token == "chutes-test")
}
@Test
func `config API key projects into Chutes environment`() {
let config = ProviderConfig(id: .chutes, apiKey: "chutes-config-token")
let env = ProviderConfigEnvironment.applyAPIKeyOverride(
base: [:],
provider: .chutes,
config: config)
#expect(env[ChutesSettingsReader.apiKeyEnvironmentKey] == "chutes-config-token")
#expect(ChutesSettingsReader.apiKey(environment: env) == "chutes-config-token")
#expect(ProviderConfigEnvironment.supportsAPIKeyOverride(for: .chutes))
}
@Test
func `fetch usage maps active subscription monthly and rolling windows`() async throws {
let now = Date(timeIntervalSince1970: 1_800_000_000)
let rollingReset = try Self.date("2026-06-13T18:00:00Z")
let monthlyReset = try Self.date("2026-07-01T00:00:00Z")
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
#expect(url.path == "/users/me/subscription_usage")
#expect(request.httpMethod == "GET")
#expect(request.value(forHTTPHeaderField: "Authorization") == "Bearer chutes-key")
#expect(request.value(forHTTPHeaderField: "Accept") == "application/json")
#expect(request.timeoutInterval == 15)
let body = #"""
{
"subscription": {
"active": true,
"plan_name": "Pro",
"current_period_end": "2026-07-01T00:00:00Z"
},
"monthly": {
"used": 250,
"limit": 1000,
"resets_at": "2026-07-01T00:00:00Z",
"unit": "credits"
},
"rolling_window": {
"requests": 40,
"limit": 100,
"window_minutes": 240,
"reset_at": "2026-06-13T18:00:00Z",
"unit": "requests"
}
}
"""#
return Self.makeResponse(url: url, body: body)
}
let snapshot = try await ChutesUsageFetcher.fetchUsage(
apiKey: " chutes-key ",
environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"],
transport: transport,
now: now)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 40)
#expect(usage.primary?.windowMinutes == 240)
#expect(usage.primary?.resetsAt == rollingReset)
#expect(usage.primary?.resetDescription == "40/100 requests")
#expect(usage.secondary?.usedPercent == 25)
#expect(usage.secondary?.resetsAt == monthlyReset)
#expect(usage.secondary?.resetDescription == "250/1000 credits")
#expect(usage.subscriptionRenewsAt == monthlyReset)
#expect(usage.loginMethod(for: .chutes) == "Pro")
let requests = await transport.requests()
#expect(requests.count == 1)
}
@Test
func `no active subscription falls back to quotas endpoint`() async throws {
let now = Date(timeIntervalSince1970: 1_800_000_000)
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
switch url.path {
case "/users/me/subscription_usage":
return Self.makeResponse(url: url, body: #"""
{
"subscription": {
"active": false,
"status": "free"
}
}
"""#)
case "/users/me/quotas":
return Self.makeResponse(url: url, body: #"""
[
{
"chute_id": "0",
"is_default": true,
"quota": 100
}
]
"""#)
case "/users/me/quota_usage/0":
return Self.makeResponse(url: url, body: #"""
{
"quota": 100,
"used": 10
}
"""#)
default:
throw URLError(.badURL)
}
}
let snapshot = try await ChutesUsageFetcher.fetchUsage(
apiKey: "chutes-key",
environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"],
transport: transport,
now: now)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 10)
#expect(usage.primary?.resetDescription == "10/100 credits")
#expect(usage.secondary == nil)
#expect(usage.loginMethod(for: .chutes) == "No active subscription")
let requests = await transport.requests()
let paths = requests.compactMap { $0.url?.path }
#expect(paths == [
"/users/me/subscription_usage",
"/users/me/quotas",
"/users/me/quota_usage/0",
])
}
@Test
func `wrapped quota list fetches per quota usage`() async throws {
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
switch url.path {
case "/users/me/subscription_usage":
return Self.makeResponse(url: url, body: #"{"subscription":{"active":false}}"#)
case "/users/me/quotas":
return Self.makeResponse(url: url, body: #"""
{
"data": [
{
"chute_id": "wrapped",
"quota": 200
}
]
}
"""#)
case "/users/me/quota_usage/wrapped":
return Self.makeResponse(url: url, body: #"{"quota":200,"used":50}"#)
default:
throw URLError(.badURL)
}
}
let snapshot = try await ChutesUsageFetcher.fetchUsage(
apiKey: "chutes-key",
environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"],
transport: transport)
#expect(snapshot.toUsageSnapshot().primary?.usedPercent == 25)
let requests = await transport.requests()
#expect(requests.compactMap { $0.url?.path } == [
"/users/me/subscription_usage",
"/users/me/quotas",
"/users/me/quota_usage/wrapped",
])
}
@Test
func `partial subscription usage fills missing rolling window from quotas`() async throws {
let now = Date(timeIntervalSince1970: 1_800_000_000)
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
switch url.path {
case "/users/me/subscription_usage":
return Self.makeResponse(url: url, body: #"""
{
"subscription": {
"active": true,
"plan_name": "Pro",
"current_period_end": "2026-07-01T00:00:00Z"
},
"monthly": {
"used": 250,
"limit": 1000,
"unit": "credits"
}
}
"""#)
case "/users/me/quotas":
return Self.makeResponse(url: url, body: #"""
{
"rolling_window": {
"requests": 40,
"limit": 100,
"window_minutes": 240,
"unit": "requests"
}
}
"""#)
default:
throw URLError(.badURL)
}
}
let snapshot = try await ChutesUsageFetcher.fetchUsage(
apiKey: "chutes-key",
environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"],
transport: transport,
now: now)
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 40)
#expect(usage.primary?.windowMinutes == 240)
#expect(usage.primary?.resetDescription == "40/100 requests")
#expect(usage.secondary?.usedPercent == 25)
#expect(usage.secondary?.resetDescription == "250/1000 credits")
#expect(usage.loginMethod(for: .chutes) == "Pro")
let requests = await transport.requests()
let paths = requests.compactMap { $0.url?.path }
#expect(paths == ["/users/me/subscription_usage", "/users/me/quotas"])
}
@Test
func `missing usage fields returns no data snapshot without decode failure`() throws {
let data = Data(#"{"subscription":{"active":true},"unexpected":{"nested":true}}"#.utf8)
let snapshot = try ChutesUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123))
let usage = snapshot.toUsageSnapshot()
#expect(!snapshot.hasUsageData)
#expect(usage.primary == nil)
#expect(usage.secondary == nil)
#expect(usage.loginMethod(for: .chutes) == nil)
}
@Test
func `identical usage values keep distinct quota windows`() throws {
let data = Data(#"""
{
"quotas": [
{
"used": 0,
"limit": 100,
"window_minutes": 240
},
{
"used": 0,
"limit": 100,
"window_minutes": 43200
}
]
}
"""#.utf8)
let snapshot = try ChutesUsageParser.parse(data: data, now: Date(timeIntervalSince1970: 123))
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 0)
#expect(usage.primary?.windowMinutes == 240)
#expect(usage.secondary?.usedPercent == 0)
#expect(usage.secondary?.windowMinutes == 43200)
}
@Test
func `exact percent value of one stays one percent`() throws {
let usedData = Data(#"""
{
"rolling_window": {
"usage_percent": 1
}
}
"""#.utf8)
let remainingData = Data(#"""
{
"rolling_window": {
"percent_remaining": 1
}
}
"""#.utf8)
let usedSnapshot = try ChutesUsageParser.parse(
data: usedData,
now: Date(timeIntervalSince1970: 123))
let remainingSnapshot = try ChutesUsageParser.parse(
data: remainingData,
now: Date(timeIntervalSince1970: 123))
#expect(usedSnapshot.toUsageSnapshot().primary?.usedPercent == 1)
#expect(remainingSnapshot.toUsageSnapshot().primary?.usedPercent == 99)
}
@Test
func `auth failure surfaces invalid credentials`() async {
let transport = ProviderHTTPTransportStub { request in
let url = try #require(request.url)
return Self.makeResponse(url: url, body: #"{"detail":"unauthorized"}"#, statusCode: 401)
}
await #expect {
_ = try await ChutesUsageFetcher.fetchUsage(
apiKey: "bad-key",
environment: [ChutesSettingsReader.apiURLEnvironmentKey: "https://chutes.test"],
transport: transport)
} throws: { error in
guard case ChutesUsageError.invalidCredentials = error else { return false }
return true
}
}
@Test
func `descriptor and app implementation registry include Chutes`() throws {
let descriptor = ProviderDescriptorRegistry.descriptor(for: .chutes)
#expect(descriptor.metadata.displayName == "Chutes")
#expect(ProviderDescriptorRegistry.all.contains { $0.id == .chutes })
let implementation = try #require(ProviderImplementationRegistry.implementation(for: .chutes))
#expect(implementation is ChutesProviderImplementation)
}
private static func makeResponse(
url: URL,
body: String,
statusCode: Int = 200) -> (Data, URLResponse)
{
let response = HTTPURLResponse(
url: url,
statusCode: statusCode,
httpVersion: "HTTP/1.1",
headerFields: ["Content-Type": "application/json"])!
return (Data(body.utf8), response)
}
private static func date(_ text: String) throws -> Date {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime]
return try #require(formatter.date(from: text))
}
}