import Foundationimport Testing@testable import CodexBarCLI@testable import CodexBarCore @Suite(.serialized)struct PlatformGatingTests { @Test func `shell probe requests a detached Linux session`() { #if os(Linux) #expect(ShellCommandLocator.test_shellSpawnFlags == 0x80) #else #expect(Bool(true)) #endif } @Test func ampAutoSource_doesNotRequireWebSupport() { #expect(!CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .amp)) } @Test func claudeAutoSource_allowsPlannerToFallBackToCLI() { #expect(!CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .claude)) #expect(CodexBarCLI.sourceModeRequiresWebSupport(.web, provider: .claude)) } @Test func claudeAutoPipeline_skipsUnsupportedWebAndUsesCLI() async throws { #if os(Linux) let binaryURL = try Self.makeClaudeCLI(loggedIn: true) defer { try? FileManager.default.removeItem(at: binaryURL) } let context = self.makeClaudeAutoContext(env: ["CLAUDE_CLI_PATH": binaryURL.path]) let cliFetchOverride: ClaudeStatusProbe.FetchOverride = { _, _, _ in Self.makeClaudeStatus() } let outcome = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(binaryURL.path) { await ClaudeCLIAuthStatusProbe.withResultOverrideForTesting(true) { await ClaudeStatusProbe.withFetchOverrideForTesting(cliFetchOverride) { await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome( context: context, provider: .claude) } } } let result = try outcome.result.get() #expect(result.strategyID == "claude.cli") #expect(outcome.attempts.map(\.strategyID) == ["claude.web", "claude.cli"]) #expect(outcome.attempts.map(\.wasAvailable) == [false, true]) #else #expect(Bool(true)) #endif } @Test func claudeAutoPipeline_withoutCLIReportsNoAvailableStrategy() async { #if os(Linux) let context = self.makeClaudeAutoContext() let outcome = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting( "/definitely/missing/claude") { await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome( context: context, provider: .claude) } switch outcome.result { case .success: Issue.record("Expected Claude auto without a CLI to report no available strategy") case let .failure(error): guard let fetchError = error as? ProviderFetchError else { Issue.record("Expected ProviderFetchError, got \(error)") return } switch fetchError { case let .noAvailableStrategy(provider): #expect(provider == .claude) } } #expect(outcome.attempts.map(\.strategyID) == ["claude.web", "claude.cli"]) #expect(outcome.attempts.map(\.wasAvailable) == [false, false]) #else #expect(Bool(true)) #endif } @Test(arguments: [ProviderSourceMode.auto, .cli]) func `Claude CLI runtime skips logged out interactive fallback`(sourceMode: ProviderSourceMode) async throws { #if os(Linux) let invocationLog = FileManager.default.temporaryDirectory .appendingPathComponent("claude-cli-runtime-invocations-\(UUID().uuidString).log") let binaryURL = try Self.makeClaudeCLI(loggedIn: false, invocationLog: invocationLog) defer { try? FileManager.default.removeItem(at: binaryURL) try? FileManager.default.removeItem(at: invocationLog) } let context = self.makeClaudeContext( sourceMode: sourceMode, env: ["CLAUDE_CLI_PATH": binaryURL.path]) let cliFetchOverride: ClaudeStatusProbe.FetchOverride = { _, _, _ in Issue.record("Logged-out Claude CLI reached the interactive usage probe") return Self.makeClaudeStatus() } let outcome = await ClaudeCLIAuthStatusProbe.withTimeoutOverrideForTesting(20) { await ClaudeStatusProbe.withFetchOverrideForTesting(cliFetchOverride) { await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome( context: context, provider: .claude) } } switch outcome.result { case .success: Issue.record("Expected logged-out Claude CLI to report no available strategy") case let .failure(error): guard let fetchError = error as? ProviderFetchError else { Issue.record("Expected ProviderFetchError, got \(error)") return } switch fetchError { case let .noAvailableStrategy(provider): #expect(provider == .claude) } } let expectedStrategyIDs = sourceMode == .auto ? ["claude.web", "claude.cli"] : ["claude.cli"] #expect(outcome.attempts.map(\.strategyID) == expectedStrategyIDs) #expect(outcome.attempts.allSatisfy { !$0.wasAvailable }) let invocations = try String(contentsOf: invocationLog, encoding: .utf8) #expect(invocations == "auth status --json\n") #else #expect(Bool(true)) #endif } @Test func claudeOAuthUsageDoesNotDetectCLIVersion() { #expect(!CodexBarCLI.shouldDetectVersion( provider: .claude, result: self.makeResult(kind: .oauth))) #expect(CodexBarCLI.shouldDetectVersion( provider: .claude, result: self.makeResult(kind: .cli))) #expect(CodexBarCLI.shouldDetectVersion( provider: .codex, result: self.makeResult(kind: .oauth))) } @Test func claudeWebFetcher_isNotSupportedOnLinux() async { #if os(Linux) let error = await #expect(throws: ClaudeWebAPIFetcher.FetchError.self) { _ = try await ClaudeWebAPIFetcher.fetchUsage() } let isExpectedError = error.map { thrown in if case .notSupportedOnThisPlatform = thrown { return true } return false } ?? false #expect(isExpectedError) #else #expect(Bool(true)) #endif } @Test func claudeWebFetcher_hasSessionKey_isFalseOnLinux() { #if os(Linux) #expect(ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: nil) == false) #else #expect(Bool(true)) #endif } @Test func claudeWebFetcher_sessionKeyInfo_throwsOnLinux() { #if os(Linux) let error = #expect(throws: ClaudeWebAPIFetcher.FetchError.self) { _ = try ClaudeWebAPIFetcher.sessionKeyInfo() } let isExpectedError = error.map { thrown in if case .notSupportedOnThisPlatform = thrown { return true } return false } ?? false #expect(isExpectedError) #else #expect(Bool(true)) #endif } private func makeClaudeAutoContext(env: [String: String] = [:]) -> ProviderFetchContext { self.makeClaudeContext(sourceMode: .auto, env: env) } private func makeClaudeContext( sourceMode: ProviderSourceMode, env: [String: String] = [:]) -> ProviderFetchContext { let browserDetection = BrowserDetection(cacheTTL: 0) let usageDataSource: ClaudeUsageDataSource = sourceMode == .cli ? .cli : .auto return ProviderFetchContext( runtime: .cli, sourceMode: sourceMode, includeCredits: false, webTimeout: 1, webDebugDumpHTML: false, verbose: false, env: env, settings: ProviderSettingsSnapshot.make(claude: .init( usageDataSource: usageDataSource, webExtrasEnabled: false, cookieSource: .auto, manualCookieHeader: nil)), fetcher: UsageFetcher(environment: env), claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection), browserDetection: browserDetection) } private static func makeClaudeCLI(loggedIn: Bool, invocationLog: URL? = nil) throws -> URL { if let invocationLog { try Data().write(to: invocationLog) } let binaryURL = FileManager.default.temporaryDirectory .appendingPathComponent("claude-cli-runtime-\(UUID().uuidString)") let recordInvocation = invocationLog.map { "printf '%s\\n' \"$*\" >> '\($0.path)'" } ?? "" let loggedInJSON = loggedIn ? "true" : "false" let script = """ #!/bin/sh \(recordInvocation) if [ "$1" = "auth" ] && [ "$2" = "status" ]; then printf '%s\\n' '{"loggedIn":\(loggedInJSON)}' fi """ try Data(script.utf8).write(to: binaryURL) try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: binaryURL.path) return binaryURL } private static func makeClaudeStatus() -> ClaudeStatusSnapshot { ClaudeStatusSnapshot( sessionPercentLeft: 80, weeklyPercentLeft: nil, opusPercentLeft: nil, accountEmail: nil, accountOrganization: nil, loginMethod: nil, primaryResetDescription: nil, secondaryResetDescription: nil, opusResetDescription: nil, rawText: "stub") } private func makeResult(kind: ProviderFetchKind) -> ProviderFetchResult { ProviderFetchResult( usage: UsageSnapshot( primary: nil, secondary: nil, updatedAt: Date(timeIntervalSince1970: 0)), credits: nil, dashboard: nil, sourceLabel: "test", strategyID: "test", strategyKind: kind) }}
import Foundation
import Testing
@testable import CodexBarCLI
@testable import CodexBarCore
@Suite(.serialized)
struct PlatformGatingTests {
@Test
func `shell probe requests a detached Linux session`() {
#if os(Linux)
#expect(ShellCommandLocator.test_shellSpawnFlags == 0x80)
#else
#expect(Bool(true))
#endif
}
@Test
func ampAutoSource_doesNotRequireWebSupport() {
#expect(!CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .amp))
}
@Test
func claudeAutoSource_allowsPlannerToFallBackToCLI() {
#expect(!CodexBarCLI.sourceModeRequiresWebSupport(.auto, provider: .claude))
#expect(CodexBarCLI.sourceModeRequiresWebSupport(.web, provider: .claude))
}
@Test
func claudeAutoPipeline_skipsUnsupportedWebAndUsesCLI() async throws {
#if os(Linux)
let binaryURL = try Self.makeClaudeCLI(loggedIn: true)
defer { try? FileManager.default.removeItem(at: binaryURL) }
let context = self.makeClaudeAutoContext(env: ["CLAUDE_CLI_PATH": binaryURL.path])
let cliFetchOverride: ClaudeStatusProbe.FetchOverride = { _, _, _ in
Self.makeClaudeStatus()
}
let outcome = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(binaryURL.path) {
await ClaudeCLIAuthStatusProbe.withResultOverrideForTesting(true) {
await ClaudeStatusProbe.withFetchOverrideForTesting(cliFetchOverride) {
await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome(
context: context,
provider: .claude)
}
}
}
let result = try outcome.result.get()
#expect(result.strategyID == "claude.cli")
#expect(outcome.attempts.map(\.strategyID) == ["claude.web", "claude.cli"])
#expect(outcome.attempts.map(\.wasAvailable) == [false, true])
#else
#expect(Bool(true))
#endif
}
@Test
func claudeAutoPipeline_withoutCLIReportsNoAvailableStrategy() async {
#if os(Linux)
let context = self.makeClaudeAutoContext()
let outcome = await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(
"/definitely/missing/claude")
{
await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome(
context: context,
provider: .claude)
}
switch outcome.result {
case .success:
Issue.record("Expected Claude auto without a CLI to report no available strategy")
case let .failure(error):
guard let fetchError = error as? ProviderFetchError else {
Issue.record("Expected ProviderFetchError, got \(error)")
return
}
switch fetchError {
case let .noAvailableStrategy(provider):
#expect(provider == .claude)
}
}
#expect(outcome.attempts.map(\.strategyID) == ["claude.web", "claude.cli"])
#expect(outcome.attempts.map(\.wasAvailable) == [false, false])
#else
#expect(Bool(true))
#endif
}
@Test(arguments: [ProviderSourceMode.auto, .cli])
func `Claude CLI runtime skips logged out interactive fallback`(sourceMode: ProviderSourceMode) async throws {
#if os(Linux)
let invocationLog = FileManager.default.temporaryDirectory
.appendingPathComponent("claude-cli-runtime-invocations-\(UUID().uuidString).log")
let binaryURL = try Self.makeClaudeCLI(loggedIn: false, invocationLog: invocationLog)
defer {
try? FileManager.default.removeItem(at: binaryURL)
try? FileManager.default.removeItem(at: invocationLog)
}
let context = self.makeClaudeContext(
sourceMode: sourceMode,
env: ["CLAUDE_CLI_PATH": binaryURL.path])
let cliFetchOverride: ClaudeStatusProbe.FetchOverride = { _, _, _ in
Issue.record("Logged-out Claude CLI reached the interactive usage probe")
return Self.makeClaudeStatus()
}
let outcome = await ClaudeCLIAuthStatusProbe.withTimeoutOverrideForTesting(20) {
await ClaudeStatusProbe.withFetchOverrideForTesting(cliFetchOverride) {
await ClaudeProviderDescriptor.makeDescriptor().fetchPlan.fetchOutcome(
context: context,
provider: .claude)
}
}
switch outcome.result {
case .success:
Issue.record("Expected logged-out Claude CLI to report no available strategy")
case let .failure(error):
guard let fetchError = error as? ProviderFetchError else {
Issue.record("Expected ProviderFetchError, got \(error)")
return
}
switch fetchError {
case let .noAvailableStrategy(provider):
#expect(provider == .claude)
}
}
let expectedStrategyIDs = sourceMode == .auto ? ["claude.web", "claude.cli"] : ["claude.cli"]
#expect(outcome.attempts.map(\.strategyID) == expectedStrategyIDs)
#expect(outcome.attempts.allSatisfy { !$0.wasAvailable })
let invocations = try String(contentsOf: invocationLog, encoding: .utf8)
#expect(invocations == "auth status --json\n")
#else
#expect(Bool(true))
#endif
}
@Test
func claudeOAuthUsageDoesNotDetectCLIVersion() {
#expect(!CodexBarCLI.shouldDetectVersion(
provider: .claude,
result: self.makeResult(kind: .oauth)))
#expect(CodexBarCLI.shouldDetectVersion(
provider: .claude,
result: self.makeResult(kind: .cli)))
#expect(CodexBarCLI.shouldDetectVersion(
provider: .codex,
result: self.makeResult(kind: .oauth)))
}
@Test
func claudeWebFetcher_isNotSupportedOnLinux() async {
#if os(Linux)
let error = await #expect(throws: ClaudeWebAPIFetcher.FetchError.self) {
_ = try await ClaudeWebAPIFetcher.fetchUsage()
}
let isExpectedError = error.map { thrown in
if case .notSupportedOnThisPlatform = thrown { return true }
return false
} ?? false
#expect(isExpectedError)
#else
#expect(Bool(true))
#endif
}
@Test
func claudeWebFetcher_hasSessionKey_isFalseOnLinux() {
#if os(Linux)
#expect(ClaudeWebAPIFetcher.hasSessionKey(cookieHeader: nil) == false)
#else
#expect(Bool(true))
#endif
}
@Test
func claudeWebFetcher_sessionKeyInfo_throwsOnLinux() {
#if os(Linux)
let error = #expect(throws: ClaudeWebAPIFetcher.FetchError.self) {
_ = try ClaudeWebAPIFetcher.sessionKeyInfo()
}
let isExpectedError = error.map { thrown in
if case .notSupportedOnThisPlatform = thrown { return true }
return false
} ?? false
#expect(isExpectedError)
#else
#expect(Bool(true))
#endif
}
private func makeClaudeAutoContext(env: [String: String] = [:]) -> ProviderFetchContext {
self.makeClaudeContext(sourceMode: .auto, env: env)
}
private func makeClaudeContext(
sourceMode: ProviderSourceMode,
env: [String: String] = [:]) -> ProviderFetchContext
{
let browserDetection = BrowserDetection(cacheTTL: 0)
let usageDataSource: ClaudeUsageDataSource = sourceMode == .cli ? .cli : .auto
return ProviderFetchContext(
runtime: .cli,
sourceMode: sourceMode,
includeCredits: false,
webTimeout: 1,
webDebugDumpHTML: false,
verbose: false,
env: env,
settings: ProviderSettingsSnapshot.make(claude: .init(
usageDataSource: usageDataSource,
webExtrasEnabled: false,
cookieSource: .auto,
manualCookieHeader: nil)),
fetcher: UsageFetcher(environment: env),
claudeFetcher: ClaudeUsageFetcher(browserDetection: browserDetection),
browserDetection: browserDetection)
}
private static func makeClaudeCLI(loggedIn: Bool, invocationLog: URL? = nil) throws -> URL {
if let invocationLog {
try Data().write(to: invocationLog)
}
let binaryURL = FileManager.default.temporaryDirectory
.appendingPathComponent("claude-cli-runtime-\(UUID().uuidString)")
let recordInvocation = invocationLog.map { "printf '%s\\n' \"$*\" >> '\($0.path)'" } ?? ""
let loggedInJSON = loggedIn ? "true" : "false"
let script = """
#!/bin/sh
\(recordInvocation)
if [ "$1" = "auth" ] && [ "$2" = "status" ]; then
printf '%s\\n' '{"loggedIn":\(loggedInJSON)}'
fi
"""
try Data(script.utf8).write(to: binaryURL)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: binaryURL.path)
return binaryURL
}
private static func makeClaudeStatus() -> ClaudeStatusSnapshot {
ClaudeStatusSnapshot(
sessionPercentLeft: 80,
weeklyPercentLeft: nil,
opusPercentLeft: nil,
accountEmail: nil,
accountOrganization: nil,
loginMethod: nil,
primaryResetDescription: nil,
secondaryResetDescription: nil,
opusResetDescription: nil,
rawText: "stub")
}
private func makeResult(kind: ProviderFetchKind) -> ProviderFetchResult {
ProviderFetchResult(
usage: UsageSnapshot(
primary: nil,
secondary: nil,
updatedAt: Date(timeIntervalSince1970: 0)),
credits: nil,
dashboard: nil,
sourceLabel: "test",
strategyID: "test",
strategyKind: kind)
}
}