chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user