Files
2026-07-13 12:22:33 +08:00

258 lines
9.5 KiB
Swift

import Foundation
import Testing
@testable import CodexBarCLI
@testable import CodexBarCore
@Suite
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 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 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 })
#expect(try String(contentsOf: invocationLog, encoding: .utf8) == "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)
}
}