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

462 lines
17 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCore
struct FactorySettingsReaderTests {
@Test
func `reads FACTORY_API_KEY from environment`() {
let key = FactorySettingsReader.apiKey(
environment: [FactorySettingsReader.apiTokenKey: " fk-env-key "])
#expect(key == "fk-env-key")
}
@Test
func `strips quotes from environment API key`() {
let key = FactorySettingsReader.apiKey(
environment: [FactorySettingsReader.apiTokenKey: "\"fk-quoted\""])
#expect(key == "fk-quoted")
}
@Test
func `falls back to factory dot env file`() throws {
let home = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-factory-home-\(UUID().uuidString)", isDirectory: true)
let factoryDir = home.appendingPathComponent(".factory", isDirectory: true)
try FileManager.default.createDirectory(at: factoryDir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: home) }
let envFile = factoryDir.appendingPathComponent(".env")
try "export FACTORY_API_KEY=fk-from-dotenv\n".write(to: envFile, atomically: true, encoding: .utf8)
let key = FactorySettingsReader.apiKey(
environment: ["HOME": home.path])
#expect(key == "fk-from-dotenv")
}
@Test
func `environment wins over factory dot env`() throws {
let home = FileManager.default.temporaryDirectory
.appendingPathComponent("codexbar-factory-home-\(UUID().uuidString)", isDirectory: true)
let factoryDir = home.appendingPathComponent(".factory", isDirectory: true)
try FileManager.default.createDirectory(at: factoryDir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: home) }
let envFile = factoryDir.appendingPathComponent(".env")
try "FACTORY_API_KEY=fk-from-dotenv\n".write(to: envFile, atomically: true, encoding: .utf8)
let key = FactorySettingsReader.apiKey(
environment: [
FactorySettingsReader.apiTokenKey: "fk-env",
"HOME": home.path,
])
#expect(key == "fk-env")
}
@Test
func `skips dotenv when HOME is absent`() {
let key = FactorySettingsReader.apiKey(environment: [:])
#expect(key == nil)
}
@Test
func `parses factory dotenv variants`() {
#expect(
FactorySettingsReader.parseFactoryAPIKey(fromDotEnv: "FACTORY_API_KEY=fk-plain") == "fk-plain")
#expect(
FactorySettingsReader.parseFactoryAPIKey(fromDotEnv: "export FACTORY_API_KEY='fk-single'")
== "fk-single")
#expect(
FactorySettingsReader.parseFactoryAPIKey(fromDotEnv: "# comment\nFACTORY_API_KEY=\"fk-double\"")
== "fk-double")
#expect(FactorySettingsReader.parseFactoryAPIKey(fromDotEnv: "OTHER=1\n") == nil)
}
}
struct FactoryAPIFetchStrategyTests {
private struct StubClaudeFetcher: ClaudeUsageFetching {
func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {
throw ClaudeUsageError.parseFailed("stub")
}
func debugRawProbe(model _: String) async -> String {
"stub"
}
func detectVersion() -> String? {
nil
}
}
private func makeContext(
env: [String: String] = [:],
sourceMode: ProviderSourceMode = .api) -> ProviderFetchContext
{
ProviderFetchContext(
runtime: .cli,
sourceMode: sourceMode,
includeCredits: false,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: env,
settings: nil,
fetcher: UsageFetcher(environment: env),
claudeFetcher: StubClaudeFetcher(),
browserDetection: BrowserDetection(cacheTTL: 0))
}
@Test
func `descriptor prefers api then web in auto mode`() async {
let strategies = await FactoryProviderDescriptor.descriptor.fetchPlan.pipeline
.resolveStrategies(self.makeContext(sourceMode: .auto))
#expect(strategies.map(\.id) == ["factory.api", "factory.web"])
}
@Test
func `legacy cli source aliases auto strategies`() async {
let modes = FactoryProviderDescriptor.descriptor.fetchPlan.sourceModes
#expect(modes.contains(.cli))
#expect(modes.contains(.api))
#expect(modes.contains(.web))
let cli = await FactoryProviderDescriptor.descriptor.fetchPlan.pipeline
.resolveStrategies(self.makeContext(sourceMode: .cli))
let auto = await FactoryProviderDescriptor.descriptor.fetchPlan.pipeline
.resolveStrategies(self.makeContext(sourceMode: .auto))
#expect(cli.map(\.id) == auto.map(\.id))
#expect(cli.map(\.id) == ["factory.api", "factory.web"])
}
@Test
func `descriptor isolates api and web source modes`() async {
let api = await FactoryProviderDescriptor.descriptor.fetchPlan.pipeline
.resolveStrategies(self.makeContext(sourceMode: .api))
let web = await FactoryProviderDescriptor.descriptor.fetchPlan.pipeline
.resolveStrategies(self.makeContext(sourceMode: .web))
#expect(api.map(\.id) == ["factory.api"])
#expect(web.map(\.id) == ["factory.web"])
}
@Test
func `api strategy available in api mode without key`() async {
let strategy = FactoryAPIFetchStrategy()
#expect(await strategy.isAvailable(self.makeContext(sourceMode: .api)))
}
@Test
func `api strategy skipped in auto mode without key`() async {
let strategy = FactoryAPIFetchStrategy()
#expect(await !strategy.isAvailable(self.makeContext(sourceMode: .auto)))
}
@Test
func `api strategy available in auto mode when key present`() async {
let strategy = FactoryAPIFetchStrategy()
let context = self.makeContext(
env: [FactorySettingsReader.apiTokenKey: "fk-test"],
sourceMode: .auto)
#expect(await strategy.isAvailable(context))
}
@Test
func `api strategy falls back in auto and cli but not explicit api mode`() {
let strategy = FactoryAPIFetchStrategy()
#expect(strategy.shouldFallback(
on: FactoryStatusProbeError.unauthorizedAPIKey,
context: self.makeContext(sourceMode: .auto)))
#expect(strategy.shouldFallback(
on: FactoryStatusProbeError.unauthorizedAPIKey,
context: self.makeContext(sourceMode: .cli)))
#expect(!strategy.shouldFallback(
on: FactoryStatusProbeError.unauthorizedAPIKey,
context: self.makeContext(sourceMode: .api)))
}
@Test
func `api strategy surfaces missing key in api mode`() async {
let strategy = FactoryAPIFetchStrategy()
do {
_ = try await strategy.fetch(self.makeContext(env: [:], sourceMode: .api))
Issue.record("Expected missingAPIKey")
} catch let error as FactoryStatusProbeError {
#expect(error == .missingAPIKey)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
}
struct FactoryAPIKeyProbeFetchTests {
@Test
func `fetch with api key uses bearer billing limits path`() async throws {
let transport = FactoryAPIKeyStubTransport()
transport.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if url.host == "api.factory.ai", url.path == "/api/app/auth/me" {
let body = """
{
"organization": {
"id": "org_1",
"name": "Acme",
"subscription": {
"factoryTier": "team",
"orbSubscription": {
"plan": { "name": "Team", "id": "plan_1" },
"status": "active"
}
}
}
}
"""
return Self.makeResponse(url: url, body: body)
}
if url.host == "api.factory.ai", url.path == "/api/billing/limits" {
let body = """
{
"usesTokenRateLimitsBilling": true,
"limits": {
"standard": {
"fiveHour": { "usedPercent": 12, "secondsRemaining": 3600 },
"weekly": { "usedPercent": 34, "secondsRemaining": 86400 },
"monthly": { "usedPercent": 56, "secondsRemaining": 604800 }
}
},
"extraUsageBalanceCents": 0,
"extraUsageAllowed": false,
"tokenRateLimitsRolloutEligible": true
}
"""
return Self.makeResponse(url: url, body: body)
}
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
let probe = FactoryStatusProbe(
timeout: 1.0,
browserDetection: BrowserDetection(
homeDirectory: "/tmp/codexbar-empty-browser-home",
cacheTTL: 0,
fileExists: { _ in false },
directoryContents: { _ in nil }),
transport: transport)
let snapshot = try await probe.fetch(apiKey: "fk-test-key")
let usage = snapshot.toUsageSnapshot()
#expect(usage.primary?.usedPercent == 12)
#expect(usage.secondary?.usedPercent == 34)
#expect(usage.tertiary?.usedPercent == 56)
#expect(transport.requests.contains { request in
request.url?.path == "/api/billing/limits"
&& request.value(forHTTPHeaderField: "Authorization") == "Bearer fk-test-key"
})
}
@Test
func `fetch with api key maps 401 to notLoggedIn for strategy remapping`() async {
let transport = FactoryAPIKeyStubTransport()
transport.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if url.path == "/api/app/auth/me" {
return Self.makeResponse(url: url, body: "{}", statusCode: 401)
}
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
let probe = FactoryStatusProbe(
timeout: 1.0,
browserDetection: BrowserDetection(
homeDirectory: "/tmp/codexbar-empty-browser-home",
cacheTTL: 0,
fileExists: { _ in false },
directoryContents: { _ in nil }),
transport: transport)
do {
_ = try await probe.fetch(apiKey: "fk-bad")
Issue.record("Expected notLoggedIn")
} catch let error as FactoryStatusProbeError {
#expect(error == .notLoggedIn)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test
func `preserves api host 401 over app host 404 for unauthorized mapping`() async {
let transport = FactoryAPIKeyStubTransport()
transport.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if url.host == "api.factory.ai", url.path == "/api/app/auth/me" {
return Self.makeResponse(url: url, body: "{}", statusCode: 401)
}
if url.host == "app.factory.ai", url.path == "/api/app/auth/me" {
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
let probe = FactoryStatusProbe(
timeout: 1.0,
browserDetection: BrowserDetection(
homeDirectory: "/tmp/codexbar-empty-browser-home",
cacheTTL: 0,
fileExists: { _ in false },
directoryContents: { _ in nil }),
transport: transport)
do {
_ = try await probe.fetch(apiKey: "fk-bad")
Issue.record("Expected notLoggedIn")
} catch let error as FactoryStatusProbeError {
#expect(error == .notLoggedIn)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test
func `preserves api host 403 over app host 404 for unauthorized mapping`() async {
let transport = FactoryAPIKeyStubTransport()
transport.handler = { request in
guard let url = request.url else { throw URLError(.badURL) }
if url.host == "api.factory.ai", url.path == "/api/app/auth/me" {
return Self.makeResponse(url: url, body: #"{"detail":"forbidden"}"#, statusCode: 403)
}
if url.host == "app.factory.ai", url.path == "/api/app/auth/me" {
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
return Self.makeResponse(url: url, body: "{}", statusCode: 404)
}
let probe = FactoryStatusProbe(
timeout: 1.0,
browserDetection: BrowserDetection(
homeDirectory: "/tmp/codexbar-empty-browser-home",
cacheTTL: 0,
fileExists: { _ in false },
directoryContents: { _ in nil }),
transport: transport)
do {
_ = try await probe.fetch(apiKey: "fk-bad")
Issue.record("Expected networkError HTTP 403")
} catch let error as FactoryStatusProbeError {
switch error {
case let .networkError(message):
#expect(message.contains("HTTP 403"))
default:
Issue.record("Expected networkError, got \(error)")
}
} catch {
Issue.record("Unexpected error: \(error)")
}
}
@Test
func `api strategy falls back on recoverable api failures in auto mode`() {
let strategy = FactoryAPIFetchStrategy()
let autoContext = ProviderFetchContext(
runtime: .cli,
sourceMode: .auto,
includeCredits: false,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: [:],
settings: nil,
fetcher: UsageFetcher(environment: [:]),
claudeFetcher: StubClaudeFetcher(),
browserDetection: BrowserDetection(cacheTTL: 0))
let apiContext = ProviderFetchContext(
runtime: .cli,
sourceMode: .api,
includeCredits: false,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: [:],
settings: nil,
fetcher: UsageFetcher(environment: [:]),
claudeFetcher: StubClaudeFetcher(),
browserDetection: BrowserDetection(cacheTTL: 0))
#expect(strategy.shouldFallback(
on: FactoryStatusProbeError.networkError("HTTP 500"),
context: autoContext))
#expect(strategy.shouldFallback(
on: FactoryStatusProbeError.parseFailed("bad json"),
context: autoContext))
#expect(strategy.shouldFallback(
on: FactoryStatusProbeError.unauthorizedAPIKey,
context: autoContext))
#expect(strategy.shouldFallback(
on: URLError(.timedOut),
context: autoContext))
#expect(!strategy.shouldFallback(
on: CancellationError(),
context: autoContext))
#expect(!strategy.shouldFallback(
on: FactoryStatusProbeError.networkError("HTTP 500"),
context: apiContext))
#expect(!strategy.shouldFallback(
on: FactoryStatusProbeError.parseFailed("bad json"),
context: apiContext))
}
@Test
func `empty api key throws missingAPIKey`() async {
let probe = FactoryStatusProbe(
browserDetection: BrowserDetection(cacheTTL: 0),
transport: FactoryAPIKeyStubTransport())
do {
_ = try await probe.fetch(apiKey: " ")
Issue.record("Expected missingAPIKey")
} catch let error as FactoryStatusProbeError {
#expect(error == .missingAPIKey)
} catch {
Issue.record("Unexpected error: \(error)")
}
}
private struct StubClaudeFetcher: ClaudeUsageFetching {
func loadLatestUsage(model _: String) async throws -> ClaudeUsageSnapshot {
throw ClaudeUsageError.parseFailed("stub")
}
func debugRawProbe(model _: String) async -> String {
"stub"
}
func detectVersion() -> String? {
nil
}
}
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))
}
}
private final class FactoryAPIKeyStubTransport: ProviderHTTPTransport, @unchecked Sendable {
var handler: ((URLRequest) throws -> (HTTPURLResponse, Data))?
var requests: [URLRequest] = []
func data(for request: URLRequest) async throws -> (Data, URLResponse) {
self.requests.append(request)
guard let handler else {
throw URLError(.badServerResponse)
}
let (response, data) = try handler(request)
return (data, response)
}
}