import Foundationimport Testing@testable import CodexBarCore @Suite(.serialized)struct CodexBaselineCharacterizationTests { private func makeContext( runtime: ProviderRuntime, sourceMode: ProviderSourceMode, env: [String: String] = [:], settings: ProviderSettingsSnapshot? = nil, includeCredits: Bool = false) -> ProviderFetchContext { let browserDetection = BrowserDetection(cacheTTL: 0) return ProviderFetchContext( runtime: runtime, sourceMode: sourceMode, includeCredits: includeCredits, webTimeout: 1, webDebugDumpHTML: false, verbose: false, env: env, settings: settings, fetcher: UsageFetcher(environment: env, initializeTimeoutSeconds: 20.0, requestTimeoutSeconds: 3.0), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection) } private func strategyIDs( runtime: ProviderRuntime, sourceMode: ProviderSourceMode, env: [String: String] = [:], settings: ProviderSettingsSnapshot? = nil) async -> [String] { let descriptor = ProviderDescriptorRegistry.descriptor(for: .codex) let context = self.makeContext(runtime: runtime, sourceMode: sourceMode, env: env, settings: settings) let strategies = await descriptor.fetchPlan.pipeline.resolveStrategies(context) return strategies.map(\.id) } private func fetchOutcome( runtime: ProviderRuntime, sourceMode: ProviderSourceMode, env: [String: String] = [:], settings: ProviderSettingsSnapshot? = nil, includeCredits: Bool = false) async -> ProviderFetchOutcome { let descriptor = ProviderDescriptorRegistry.descriptor(for: .codex) let context = self.makeContext( runtime: runtime, sourceMode: sourceMode, env: env, settings: settings, includeCredits: includeCredits) return await descriptor.fetchPlan.fetchOutcome(context: context, provider: .codex) } private func makeStubCodexCLI() throws -> String { let script = """ #!/usr/bin/python3 -S import json import os import sys counter = os.environ.get("CODEXBAR_STUB_COUNTER") if counter: with open(counter, "a") as f: f.write("start\\n") credits_only = os.environ.get("CODEXBAR_STUB_CREDITS_ONLY") == "1" for line in sys.stdin: if not line.strip(): continue message = json.loads(line) method = message.get("method") if method == "initialized": continue identifier = message.get("id") if method == "initialize": payload = {"id": identifier, "result": {}} elif method == "account/rateLimits/read": rate_limits = { "credits": { "hasCredits": True, "unlimited": False, "balance": "7" } } if not credits_only: rate_limits["primary"] = { "usedPercent": 12, "windowDurationMins": 300, "resetsAt": 1766948068 } rate_limits["secondary"] = { "usedPercent": 43, "windowDurationMins": 10080, "resetsAt": 1767407914 } payload = { "id": identifier, "result": { "rateLimits": rate_limits } } elif method == "account/read": payload = { "id": identifier, "result": { "account": { "type": "chatgpt", "email": "stub@example.com", "planType": "pro" }, "requiresOpenaiAuth": False } } else: payload = {"id": identifier, "result": {}} print(json.dumps(payload), flush=True) """ let url = FileManager.default.temporaryDirectory .appendingPathComponent("codex-stub-\(UUID().uuidString)", isDirectory: false) try Data(script.utf8).write(to: url) try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path) return url.path } private func makeEmptyCodexHome() throws -> URL { let homeURL = FileManager.default.temporaryDirectory .appendingPathComponent("codex-empty-home-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true) return homeURL } private func makeUnavailableOAuthHome() throws -> URL { let homeURL = FileManager.default.temporaryDirectory .appendingPathComponent("codex-oauth-home-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true) let credentials = CodexOAuthCredentials( accessToken: "access-token", refreshToken: "refresh-token", idToken: nil, accountId: "account-id", lastRefresh: Date()) try CodexOAuthCredentialsStore.save(credentials, env: ["CODEX_HOME": homeURL.path]) let configURL = homeURL.appendingPathComponent("config.toml") try "chatgpt_base_url = \"http://127.0.0.1:9\"".write(to: configURL, atomically: true, encoding: .utf8) return homeURL } @Test func `app auto pipeline order is OAuth then CLI without web`() async { let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: .auto) #expect(strategyIDs == ["codex.oauth", "codex.cli"]) } @Test func `CLI auto pipeline order is web then OAuth then CLI`() async { let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: .auto) #expect(strategyIDs == ["codex.web.dashboard", "codex.oauth", "codex.cli"]) } @Test func `explicit fetch plan modes keep single Codex strategy selection`() async { let appCases: [(ProviderSourceMode, [String])] = [ (.oauth, ["codex.oauth"]), (.cli, ["codex.cli"]), (.web, ["codex.web.dashboard"]), ] for (sourceMode, expected) in appCases { let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: sourceMode) #expect(strategyIDs == expected) } for (sourceMode, expected) in appCases { let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: sourceMode) #expect(strategyIDs == expected) } } @Test func `app auto records unavailable OAuth before successful CLI fallback`() async throws { let stubCLIPath = try self.makeStubCodexCLI() let codexHome = try self.makeEmptyCodexHome() defer { try? FileManager.default.removeItem(at: codexHome) } let env = [ "CODEX_CLI_PATH": stubCLIPath, "CODEX_HOME": codexHome.path, ] let outcome = await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env) #expect(outcome.attempts.map(\.strategyID) == ["codex.oauth", "codex.cli"]) #expect(outcome.attempts.map(\.wasAvailable) == [false, true]) switch outcome.result { case let .success(result): #expect(result.sourceLabel == "codex-cli") #expect(result.usage.accountEmail(for: .codex) == "stub@example.com") #expect(result.usage.loginMethod(for: .codex) == "pro") case let .failure(error): Issue.record("Unexpected failure: \(error)") } } @Test func `app auto does not fall back from non auth failing OAuth`() async throws { let stubCLIPath = try self.makeStubCodexCLI() let oauthHome = try self.makeUnavailableOAuthHome() defer { try? FileManager.default.removeItem(at: oauthHome) } let env = [ "CODEX_CLI_PATH": stubCLIPath, "CODEX_HOME": oauthHome.path, ] let outcome = await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env) #expect(outcome.attempts.map(\.strategyID) == ["codex.oauth"]) #expect(outcome.attempts.map(\.wasAvailable) == [true]) #expect(outcome.attempts[0].errorDescription?.isEmpty == false) switch outcome.result { case .success: Issue.record("Expected non-auth OAuth failure to stop before CLI fallback") case let .failure(error as CodexOAuthFetchError): switch error { case .networkError: break default: Issue.record("Expected network error, got \(error)") } case let .failure(error): Issue.record("Unexpected failure: \(error)") } } @Test func `Codex CLI strategy fetches usage and credits with one app-server process`() async throws { let stubCLIPath = try self.makeStubCodexCLI() defer { try? FileManager.default.removeItem(atPath: stubCLIPath) } let counterURL = FileManager.default.temporaryDirectory .appendingPathComponent("codex-stub-counter-\(UUID().uuidString)", isDirectory: false) defer { try? FileManager.default.removeItem(at: counterURL) } let env = [ "CODEX_CLI_PATH": stubCLIPath, "CODEXBAR_STUB_COUNTER": counterURL.path, ] let outcome = await self.fetchOutcome( runtime: .app, sourceMode: .cli, env: env, includeCredits: true) switch outcome.result { case let .success(result): #expect(result.sourceLabel == "codex-cli") #expect(result.usage.primary?.usedPercent == 12) #expect(result.credits?.remaining == 7) case let .failure(error): Issue.record("Unexpected failure: \(error)") } let count = (try? String(contentsOf: counterURL, encoding: .utf8))? .split(whereSeparator: \.isNewline) .count ?? 0 #expect(count == 1) } @Test func `Codex CLI strategy keeps credits when rate limit windows are absent`() async throws { let stubCLIPath = try self.makeStubCodexCLI() defer { try? FileManager.default.removeItem(atPath: stubCLIPath) } let outcome = await self.fetchOutcome( runtime: .app, sourceMode: .cli, env: [ "CODEX_CLI_PATH": stubCLIPath, "CODEXBAR_STUB_CREDITS_ONLY": "1", ], includeCredits: true) switch outcome.result { case let .success(result): #expect(result.sourceLabel == "codex-cli") #expect(result.usage.primary == nil) #expect(result.usage.secondary == nil) #expect(result.credits?.remaining == 7) case let .failure(error): Issue.record("Unexpected failure: \(error)") } } @Test func `CLI auto records unavailable web and OAuth before successful CLI`() async throws { let stubCLIPath = try self.makeStubCodexCLI() let codexHome = try self.makeEmptyCodexHome() defer { try? FileManager.default.removeItem(at: codexHome) } let settings = ProviderSettingsSnapshot.make( codex: .init( usageDataSource: .auto, cookieSource: .auto, manualCookieHeader: nil, managedAccountStoreUnreadable: true)) let outcome = await self.fetchOutcome( runtime: .cli, sourceMode: .auto, env: [ "CODEX_CLI_PATH": stubCLIPath, "CODEX_HOME": codexHome.path, ], settings: settings) #expect(outcome.attempts.map(\.strategyID) == ["codex.web.dashboard", "codex.oauth", "codex.cli"]) #expect(outcome.attempts.map(\.wasAvailable) == [false, false, true]) switch outcome.result { case let .success(result): #expect(result.sourceLabel == "codex-cli") #expect(result.usage.accountEmail(for: .codex) == "stub@example.com") case let .failure(error): Issue.record("Unexpected failure: \(error)") } } @Test func `CLI auto tries OAuth before missing CLI fallback`() async throws { let oauthHome = try self.makeUnavailableOAuthHome() defer { try? FileManager.default.removeItem(at: oauthHome) } let settings = ProviderSettingsSnapshot.make( codex: .init( usageDataSource: .auto, cookieSource: .auto, manualCookieHeader: nil, managedAccountStoreUnreadable: true)) let outcome = await self.fetchOutcome( runtime: .cli, sourceMode: .auto, env: [ "CODEX_CLI_PATH": "/missing/codex", "CODEX_HOME": oauthHome.path, ], settings: settings) #expect(outcome.attempts.map(\.strategyID) == ["codex.web.dashboard", "codex.oauth"]) #expect(outcome.attempts.map(\.wasAvailable) == [false, true]) switch outcome.result { case .success: Issue.record("Expected unavailable OAuth endpoint to fail before CLI fallback") case let .failure(error as CodexOAuthFetchError): if case .networkError = error { break } Issue.record("Expected network error, got \(error)") case let .failure(error): Issue.record("Unexpected failure: \(error)") } }}
import Foundation
import Testing
@testable import CodexBarCore
@Suite(.serialized)
struct CodexBaselineCharacterizationTests {
private func makeContext(
runtime: ProviderRuntime,
sourceMode: ProviderSourceMode,
env: [String: String] = [:],
settings: ProviderSettingsSnapshot? = nil,
includeCredits: Bool = false) -> ProviderFetchContext
{
let browserDetection = BrowserDetection(cacheTTL: 0)
return ProviderFetchContext(
runtime: runtime,
sourceMode: sourceMode,
includeCredits: includeCredits,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: env,
settings: settings,
fetcher: UsageFetcher(environment: env, initializeTimeoutSeconds: 20.0, requestTimeoutSeconds: 3.0),
claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),
browserDetection: browserDetection)
}
private func strategyIDs(
runtime: ProviderRuntime,
sourceMode: ProviderSourceMode,
env: [String: String] = [:],
settings: ProviderSettingsSnapshot? = nil) async -> [String]
{
let descriptor = ProviderDescriptorRegistry.descriptor(for: .codex)
let context = self.makeContext(runtime: runtime, sourceMode: sourceMode, env: env, settings: settings)
let strategies = await descriptor.fetchPlan.pipeline.resolveStrategies(context)
return strategies.map(\.id)
}
private func fetchOutcome(
runtime: ProviderRuntime,
sourceMode: ProviderSourceMode,
env: [String: String] = [:],
settings: ProviderSettingsSnapshot? = nil,
includeCredits: Bool = false) async -> ProviderFetchOutcome
{
let descriptor = ProviderDescriptorRegistry.descriptor(for: .codex)
let context = self.makeContext(
runtime: runtime,
sourceMode: sourceMode,
env: env,
settings: settings,
includeCredits: includeCredits)
return await descriptor.fetchPlan.fetchOutcome(context: context, provider: .codex)
}
private func makeStubCodexCLI() throws -> String {
let script = """
#!/usr/bin/python3 -S
import json
import os
import sys
counter = os.environ.get("CODEXBAR_STUB_COUNTER")
if counter:
with open(counter, "a") as f:
f.write("start\\n")
credits_only = os.environ.get("CODEXBAR_STUB_CREDITS_ONLY") == "1"
for line in sys.stdin:
if not line.strip():
continue
message = json.loads(line)
method = message.get("method")
if method == "initialized":
continue
identifier = message.get("id")
if method == "initialize":
payload = {"id": identifier, "result": {}}
elif method == "account/rateLimits/read":
rate_limits = {
"credits": {
"hasCredits": True,
"unlimited": False,
"balance": "7"
}
}
if not credits_only:
rate_limits["primary"] = {
"usedPercent": 12,
"windowDurationMins": 300,
"resetsAt": 1766948068
}
rate_limits["secondary"] = {
"usedPercent": 43,
"windowDurationMins": 10080,
"resetsAt": 1767407914
}
payload = {
"id": identifier,
"result": {
"rateLimits": rate_limits
}
}
elif method == "account/read":
payload = {
"id": identifier,
"result": {
"account": {
"type": "chatgpt",
"email": "stub@example.com",
"planType": "pro"
},
"requiresOpenaiAuth": False
}
}
else:
payload = {"id": identifier, "result": {}}
print(json.dumps(payload), flush=True)
"""
let url = FileManager.default.temporaryDirectory
.appendingPathComponent("codex-stub-\(UUID().uuidString)", isDirectory: false)
try Data(script.utf8).write(to: url)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path)
return url.path
}
private func makeEmptyCodexHome() throws -> URL {
let homeURL = FileManager.default.temporaryDirectory
.appendingPathComponent("codex-empty-home-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
return homeURL
}
private func makeUnavailableOAuthHome() throws -> URL {
let homeURL = FileManager.default.temporaryDirectory
.appendingPathComponent("codex-oauth-home-\(UUID().uuidString)", isDirectory: true)
try FileManager.default.createDirectory(at: homeURL, withIntermediateDirectories: true)
let credentials = CodexOAuthCredentials(
accessToken: "access-token",
refreshToken: "refresh-token",
idToken: nil,
accountId: "account-id",
lastRefresh: Date())
try CodexOAuthCredentialsStore.save(credentials, env: ["CODEX_HOME": homeURL.path])
let configURL = homeURL.appendingPathComponent("config.toml")
try "chatgpt_base_url = \"http://127.0.0.1:9\"".write(to: configURL, atomically: true, encoding: .utf8)
return homeURL
}
@Test
func `app auto pipeline order is OAuth then CLI without web`() async {
let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: .auto)
#expect(strategyIDs == ["codex.oauth", "codex.cli"])
}
@Test
func `CLI auto pipeline order is web then OAuth then CLI`() async {
let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: .auto)
#expect(strategyIDs == ["codex.web.dashboard", "codex.oauth", "codex.cli"])
}
@Test
func `explicit fetch plan modes keep single Codex strategy selection`() async {
let appCases: [(ProviderSourceMode, [String])] = [
(.oauth, ["codex.oauth"]),
(.cli, ["codex.cli"]),
(.web, ["codex.web.dashboard"]),
]
for (sourceMode, expected) in appCases {
let strategyIDs = await self.strategyIDs(runtime: .app, sourceMode: sourceMode)
#expect(strategyIDs == expected)
}
for (sourceMode, expected) in appCases {
let strategyIDs = await self.strategyIDs(runtime: .cli, sourceMode: sourceMode)
#expect(strategyIDs == expected)
}
}
@Test
func `app auto records unavailable OAuth before successful CLI fallback`() async throws {
let stubCLIPath = try self.makeStubCodexCLI()
let codexHome = try self.makeEmptyCodexHome()
defer { try? FileManager.default.removeItem(at: codexHome) }
let env = [
"CODEX_CLI_PATH": stubCLIPath,
"CODEX_HOME": codexHome.path,
]
let outcome = await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env)
#expect(outcome.attempts.map(\.strategyID) == ["codex.oauth", "codex.cli"])
#expect(outcome.attempts.map(\.wasAvailable) == [false, true])
switch outcome.result {
case let .success(result):
#expect(result.sourceLabel == "codex-cli")
#expect(result.usage.accountEmail(for: .codex) == "stub@example.com")
#expect(result.usage.loginMethod(for: .codex) == "pro")
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
}
@Test
func `app auto does not fall back from non auth failing OAuth`() async throws {
let stubCLIPath = try self.makeStubCodexCLI()
let oauthHome = try self.makeUnavailableOAuthHome()
defer { try? FileManager.default.removeItem(at: oauthHome) }
let env = [
"CODEX_CLI_PATH": stubCLIPath,
"CODEX_HOME": oauthHome.path,
]
let outcome = await self.fetchOutcome(runtime: .app, sourceMode: .auto, env: env)
#expect(outcome.attempts.map(\.strategyID) == ["codex.oauth"])
#expect(outcome.attempts.map(\.wasAvailable) == [true])
#expect(outcome.attempts[0].errorDescription?.isEmpty == false)
switch outcome.result {
case .success:
Issue.record("Expected non-auth OAuth failure to stop before CLI fallback")
case let .failure(error as CodexOAuthFetchError):
switch error {
case .networkError:
break
default:
Issue.record("Expected network error, got \(error)")
}
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
}
@Test
func `Codex CLI strategy fetches usage and credits with one app-server process`() async throws {
let stubCLIPath = try self.makeStubCodexCLI()
defer { try? FileManager.default.removeItem(atPath: stubCLIPath) }
let counterURL = FileManager.default.temporaryDirectory
.appendingPathComponent("codex-stub-counter-\(UUID().uuidString)", isDirectory: false)
defer { try? FileManager.default.removeItem(at: counterURL) }
let env = [
"CODEX_CLI_PATH": stubCLIPath,
"CODEXBAR_STUB_COUNTER": counterURL.path,
]
let outcome = await self.fetchOutcome(
runtime: .app,
sourceMode: .cli,
env: env,
includeCredits: true)
switch outcome.result {
case let .success(result):
#expect(result.sourceLabel == "codex-cli")
#expect(result.usage.primary?.usedPercent == 12)
#expect(result.credits?.remaining == 7)
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
let count = (try? String(contentsOf: counterURL, encoding: .utf8))?
.split(whereSeparator: \.isNewline)
.count ?? 0
#expect(count == 1)
}
@Test
func `Codex CLI strategy keeps credits when rate limit windows are absent`() async throws {
let stubCLIPath = try self.makeStubCodexCLI()
defer { try? FileManager.default.removeItem(atPath: stubCLIPath) }
let outcome = await self.fetchOutcome(
runtime: .app,
sourceMode: .cli,
env: [
"CODEX_CLI_PATH": stubCLIPath,
"CODEXBAR_STUB_CREDITS_ONLY": "1",
],
includeCredits: true)
switch outcome.result {
case let .success(result):
#expect(result.sourceLabel == "codex-cli")
#expect(result.usage.primary == nil)
#expect(result.usage.secondary == nil)
#expect(result.credits?.remaining == 7)
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
}
@Test
func `CLI auto records unavailable web and OAuth before successful CLI`() async throws {
let stubCLIPath = try self.makeStubCodexCLI()
let codexHome = try self.makeEmptyCodexHome()
defer { try? FileManager.default.removeItem(at: codexHome) }
let settings = ProviderSettingsSnapshot.make(
codex: .init(
usageDataSource: .auto,
cookieSource: .auto,
manualCookieHeader: nil,
managedAccountStoreUnreadable: true))
let outcome = await self.fetchOutcome(
runtime: .cli,
sourceMode: .auto,
env: [
"CODEX_CLI_PATH": stubCLIPath,
"CODEX_HOME": codexHome.path,
],
settings: settings)
#expect(outcome.attempts.map(\.strategyID) == ["codex.web.dashboard", "codex.oauth", "codex.cli"])
#expect(outcome.attempts.map(\.wasAvailable) == [false, false, true])
switch outcome.result {
case let .success(result):
#expect(result.sourceLabel == "codex-cli")
#expect(result.usage.accountEmail(for: .codex) == "stub@example.com")
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
}
@Test
func `CLI auto tries OAuth before missing CLI fallback`() async throws {
let oauthHome = try self.makeUnavailableOAuthHome()
defer { try? FileManager.default.removeItem(at: oauthHome) }
let settings = ProviderSettingsSnapshot.make(
codex: .init(
usageDataSource: .auto,
cookieSource: .auto,
manualCookieHeader: nil,
managedAccountStoreUnreadable: true))
let outcome = await self.fetchOutcome(
runtime: .cli,
sourceMode: .auto,
env: [
"CODEX_CLI_PATH": "/missing/codex",
"CODEX_HOME": oauthHome.path,
],
settings: settings)
#expect(outcome.attempts.map(\.strategyID) == ["codex.web.dashboard", "codex.oauth"])
#expect(outcome.attempts.map(\.wasAvailable) == [false, true])
switch outcome.result {
case .success:
Issue.record("Expected unavailable OAuth endpoint to fail before CLI fallback")
case let .failure(error as CodexOAuthFetchError):
if case .networkError = error {
break
}
Issue.record("Expected network error, got \(error)")
case let .failure(error):
Issue.record("Unexpected failure: \(error)")
}
}
}