import Foundation import Testing @testable import CodexBarCore struct ClaudeDirectUsageFallbackTests { private final class InvocationLog: @unchecked Sendable { private let url: URL private let lock = NSLock() init(url: URL) { self.url = url } func contents() -> String { self.lock.withLock { (try? String(contentsOf: self.url, encoding: .utf8)) ?? "" } } } @Test func `passive claude probes always disable the cli auto updater`() { let environment = ClaudeCLISession.launchEnvironment(baseEnv: [ "DISABLE_AUTOUPDATER": "0", ClaudeOAuthCredentialsStore.environmentTokenKey: "oauth-token", "ANTHROPIC_API_KEY": "api-token", ]) #expect(environment["DISABLE_AUTOUPDATER"] == "1") #expect(environment[ClaudeOAuthCredentialsStore.environmentTokenKey] == nil) #expect(environment["ANTHROPIC_API_KEY"] == nil) } @Test func `cli source falls back to direct usage when pty usage fails to load`() async throws { let cliLogURL = FileManager.default.temporaryDirectory .appendingPathComponent("claude-direct-fallback-log-\(UUID().uuidString).txt") let log = InvocationLog(url: cliLogURL) let fakeCLI = try Self.makeDirectFallbackClaudeCLI(logURL: cliLogURL) let fetcher = ClaudeUsageFetcher( browserDetection: BrowserDetection(cacheTTL: 0), environment: [ "CLAUDE_CLI_PATH": fakeCLI.path, ClaudeOAuthCredentialsStore.environmentTokenKey: "oauth-token", ClaudeOAuthCredentialsStore.environmentScopesKey: "user:profile", "ANTHROPIC_ADMIN_KEY": "admin-token", ], dataSource: .cli) try await ClaudeCLISession.withIsolatedSessionForTesting { try await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(fakeCLI.path) { do { _ = try await fetcher.loadLatestUsage(model: "sonnet") #expect(Bool(false), "Subscription-only usage should fail parsing") } catch let ClaudeUsageError.parseFailed(message) { #expect(message.lowercased().contains("subscription")) } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.lowercased().contains("subscription")) } } } let invocations = log.contents() #expect(invocations.contains("pty-usage")) #expect(invocations.contains("direct-usage")) #expect(invocations.contains("pty-auto-updater-disabled")) #expect(invocations.contains("direct-auto-updater-disabled")) #expect(!invocations.contains("pty-secret-env")) #expect(!invocations.contains("direct-secret-env")) } @Test func `direct usage timeout keeps original pty failure`() async throws { let cliLogURL = FileManager.default.temporaryDirectory .appendingPathComponent("claude-direct-timeout-log-\(UUID().uuidString).txt") let log = InvocationLog(url: cliLogURL) let fakeCLI = try Self.makeDirectTimeoutClaudeCLI(logURL: cliLogURL) let fetcher = ClaudeUsageFetcher( browserDetection: BrowserDetection(cacheTTL: 0), environment: ["CLAUDE_CLI_PATH": fakeCLI.path], dataSource: .cli) await ClaudeCLISession.withIsolatedSessionForTesting { await ClaudeCLIResolver.withResolvedBinaryPathOverrideForTesting(fakeCLI.path) { do { _ = try await fetcher.loadLatestUsage(model: "sonnet") #expect(Bool(false), "PTY failure should still surface") } catch let ClaudeStatusProbeError.parseFailed(message) { #expect(message.lowercased().contains("could not load usage data")) } catch { #expect(Bool(false), "Unexpected error: \(error)") } } } let invocations = log.contents() #expect(invocations.contains("pty-usage")) #expect(invocations.contains("direct-usage")) } private static func makeDirectFallbackClaudeCLI(logURL: URL) throws -> URL { try self.makeClaudeCLI(name: "claude-direct-fallback", logURL: logURL, scriptBody: """ if [ "$1" = "/usage" ]; then printf 'direct-usage\\n' >> "$LOG_FILE" if [ "$DISABLE_AUTOUPDATER" = "1" ]; then printf 'direct-auto-updater-disabled\\n' >> "$LOG_FILE" fi if [ -n "$CODEXBAR_CLAUDE_OAUTH_TOKEN" ] || [ -n "$CODEXBAR_CLAUDE_OAUTH_SCOPES" ] || [ -n "$ANTHROPIC_ADMIN_KEY" ]; then printf 'direct-secret-env\\n' >> "$LOG_FILE" fi printf '%s\\n' 'You are currently using your subscription to power your Claude Code usage' exit 0 fi while IFS= read -r line; do case "$line" in *"/usage"*) printf 'pty-usage\\n' >> "$LOG_FILE" if [ "$DISABLE_AUTOUPDATER" = "1" ]; then printf 'pty-auto-updater-disabled\\n' >> "$LOG_FILE" fi if [ -n "$CODEXBAR_CLAUDE_OAUTH_TOKEN" ] || [ -n "$CODEXBAR_CLAUDE_OAUTH_SCOPES" ] || [ -n "$ANTHROPIC_ADMIN_KEY" ]; then printf 'pty-secret-env\\n' >> "$LOG_FILE" fi printf '%s\\n' 'Failed to load usage data' ;; *"/status"*) printf 'pty-status\\n' >> "$LOG_FILE" printf '%s\\n' 'Account: subscription@example.com' ;; esac done """) } private static func makeDirectTimeoutClaudeCLI(logURL: URL) throws -> URL { try self.makeClaudeCLI(name: "claude-direct-timeout", logURL: logURL, scriptBody: """ if [ "$1" = "/usage" ]; then printf 'direct-usage\\n' >> "$LOG_FILE" sleep 30 exit 0 fi while IFS= read -r line; do case "$line" in *"/usage"*) printf 'pty-usage\\n' >> "$LOG_FILE" printf '%s\\n' 'Failed to load usage data' ;; esac done """) } private static func makeClaudeCLI(name: String, logURL: URL, scriptBody: String) throws -> URL { let directory = FileManager.default.temporaryDirectory .appendingPathComponent("\(name)-\(UUID().uuidString)", isDirectory: true) try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let scriptURL = directory.appendingPathComponent("claude") let script = """ #!/bin/sh LOG_FILE='\(logURL.path)' \(scriptBody) """ try script.write(to: scriptURL, atomically: true, encoding: .utf8) try FileManager.default.setAttributes( [.posixPermissions: NSNumber(value: Int16(0o755))], ofItemAtPath: scriptURL.path) return scriptURL } }