import CodexBarCore import Foundation import Testing @Suite(.serialized) struct OpenCodeUsageFetcherErrorTests { @Test func extractsApiErrorFromUppercaseHTMLTitle() async throws { let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self) defer { if registered { URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self) } OpenCodeStubURLProtocol.handler = nil } OpenCodeStubURLProtocol.handler = { request in guard let url = request.url else { throw URLError(.badURL) } let body = "403 Forbiddendenied" return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: "text/html") } do { _ = try await OpenCodeUsageFetcher.fetchUsage( cookieHeader: "auth=test", timeout: 2, workspaceIDOverride: "wrk_TEST123") Issue.record("Expected OpenCodeUsageError.apiError") } catch let error as OpenCodeUsageError { switch error { case let .apiError(message): #expect(message.contains("HTTP 500")) #expect(message.contains("403 Forbidden")) default: Issue.record("Expected apiError, got: \(error)") } } } @Test func extractsApiErrorFromDetailField() async throws { let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self) defer { if registered { URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self) } OpenCodeStubURLProtocol.handler = nil } OpenCodeStubURLProtocol.handler = { request in guard let url = request.url else { throw URLError(.badURL) } let body = #"{"detail":"Workspace missing"}"# return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: "application/json") } do { _ = try await OpenCodeUsageFetcher.fetchUsage( cookieHeader: "auth=test", timeout: 2, workspaceIDOverride: "wrk_TEST123") Issue.record("Expected OpenCodeUsageError.apiError") } catch let error as OpenCodeUsageError { switch error { case let .apiError(message): #expect(message.contains("HTTP 500")) #expect(message.contains("Workspace missing")) default: Issue.record("Expected apiError, got: \(error)") } } } @Test func subscriptionGetNullSkipsPostAndReturnsGracefulError() async throws { let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self) defer { if registered { URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self) } OpenCodeStubURLProtocol.handler = nil } var methods: [String] = [] var urls: [URL] = [] var queries: [String] = [] var contentTypes: [String] = [] OpenCodeStubURLProtocol.handler = { request in guard let url = request.url else { throw URLError(.badURL) } methods.append(request.httpMethod ?? "GET") urls.append(url) queries.append(url.query ?? "") contentTypes.append(request.value(forHTTPHeaderField: "Content-Type") ?? "") if request.httpMethod?.uppercased() == "GET" { return Self.makeResponse(url: url, body: "null", statusCode: 200, contentType: "application/json") } let body = #"{"status":500,"unhandled":true,"message":"HTTPError"}"# return Self.makeResponse(url: url, body: body, statusCode: 500, contentType: "application/json") } do { _ = try await OpenCodeUsageFetcher.fetchUsage( cookieHeader: "auth=test", timeout: 2, workspaceIDOverride: "wrk_TEST123") Issue.record("Expected OpenCodeUsageError.apiError") } catch let error as OpenCodeUsageError { switch error { case let .apiError(message): #expect(message.contains("No subscription usage data")) #expect(message.contains("wrk_TEST123")) default: Issue.record("Expected apiError, got: \(error)") } } #expect(methods == ["GET"]) #expect(queries[0].contains("id=")) #expect(queries[0].contains("wrk_TEST123")) #expect(urls[0].path == "/_server") #expect(contentTypes[0].isEmpty) } @Test func subscriptionGetPayloadDoesNotFallbackToPost() async throws { let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self) defer { if registered { URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self) } OpenCodeStubURLProtocol.handler = nil } var methods: [String] = [] OpenCodeStubURLProtocol.handler = { request in guard let url = request.url else { throw URLError(.badURL) } methods.append(request.httpMethod ?? "GET") let body = """ { "rollingUsage": { "usagePercent": 17, "resetInSec": 600 }, "weeklyUsage": { "usagePercent": 75, "resetInSec": 7200 } } """ return Self.makeResponse(url: url, body: body, statusCode: 200, contentType: "application/json") } let snapshot = try await OpenCodeUsageFetcher.fetchUsage( cookieHeader: "auth=test", timeout: 2, workspaceIDOverride: "wrk_TEST123") #expect(snapshot.rollingUsagePercent == 17) #expect(snapshot.weeklyUsagePercent == 75) #expect(methods == ["GET"]) } @Test func subscriptionGetMissingFieldsFallsBackToPost() async throws { let registered = URLProtocol.registerClass(OpenCodeStubURLProtocol.self) defer { if registered { URLProtocol.unregisterClass(OpenCodeStubURLProtocol.self) } OpenCodeStubURLProtocol.handler = nil } var methods: [String] = [] OpenCodeStubURLProtocol.handler = { request in guard let url = request.url else { throw URLError(.badURL) } methods.append(request.httpMethod ?? "GET") if request.httpMethod?.uppercased() == "GET" { return Self.makeResponse( url: url, body: #"{"ok":true}"#, statusCode: 200, contentType: "application/json") } let body = """ { "rollingUsage": { "usagePercent": 22, "resetInSec": 300 }, "weeklyUsage": { "usagePercent": 44, "resetInSec": 3600 } } """ return Self.makeResponse( url: url, body: body, statusCode: 200, contentType: "application/json") } let snapshot = try await OpenCodeUsageFetcher.fetchUsage( cookieHeader: "auth=test", timeout: 2, workspaceIDOverride: "wrk_TEST123") #expect(snapshot.rollingUsagePercent == 22) #expect(snapshot.weeklyUsagePercent == 44) #expect(methods == ["GET", "POST"]) } private static func makeResponse( url: URL, body: String, statusCode: Int, contentType: String) -> (HTTPURLResponse, Data) { let response = HTTPURLResponse( url: url, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: ["Content-Type": contentType])! return (response, Data(body.utf8)) } } final class OpenCodeStubURLProtocol: URLProtocol { nonisolated(unsafe) static var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))? override static func canInit(with request: URLRequest) -> Bool { request.url?.host == "opencode.ai" } 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() {} }