264 lines · 9.9 KB · Swift
RawDownload
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)    }}