import Foundation import Testing @testable import CodexBar @testable import CodexBarCore struct PathBuilderTests { @Test func `merges login shell path when available`() { let seeded = PathBuilder.effectivePATH( purposes: [.rpc], env: ["PATH": "/custom/bin:/usr/bin"], loginPATH: ["/login/bin", "/login/alt"]) #expect(seeded == "/login/bin:/login/alt:/custom/bin:/usr/bin") } @Test func `falls back to existing path when no login path`() { let seeded = PathBuilder.effectivePATH( purposes: [.tty], env: ["PATH": "/custom/bin:/usr/bin"], loginPATH: nil) #expect(seeded == "/custom/bin:/usr/bin") } @Test func `uses fallback when no path available`() { let seeded = PathBuilder.effectivePATH( purposes: [.tty], env: [:], loginPATH: nil) #expect(seeded == "/usr/bin:/bin:/usr/sbin:/sbin") } @Test func `debug snapshot async matches sync`() async { let env = [ "CODEX_CLI_PATH": "/usr/bin/true", "CLAUDE_CLI_PATH": "/usr/bin/true", "GEMINI_CLI_PATH": "/usr/bin/true", "PATH": "/usr/bin:/bin", ] let sync = PathBuilder.debugSnapshot(purposes: [.rpc], env: env, home: "/tmp") let async = await PathBuilder.debugSnapshotAsync(purposes: [.rpc], env: env, home: "/tmp") #expect(async == sync) } @Test func `login shell cache retries after timed out nil capture`() async { let capture = LoginShellPathCaptureStub([ nil, ["/login/bin", "/usr/bin"], ]) let cache = LoginShellPathCache { _, _ in capture.next() } let firstResult: [String]? = await withCheckedContinuation { continuation in cache.captureOnce(shell: "/unused", timeout: 0.01) { result in continuation.resume(returning: result) } } #expect(firstResult == nil) #expect(cache.current == nil) let recovered = cache.currentOrCapture(shell: "/unused", timeout: 2.0) #expect(recovered == ["/login/bin", "/usr/bin"]) #expect(cache.current == ["/login/bin", "/usr/bin"]) #expect(capture.callCount == 2) } @Test func `shell runner drains noisy stdout and stderr`() throws { let script = """ i=0 while [ "$i" -lt 4000 ]; do printf 'out-%04d\\n' "$i" printf 'err-%04d\\n' "$i" >&2 i=$((i + 1)) done printf '__CODEXBAR_DONE__\\n' """ let data = try #require(ShellCommandLocator.test_runShellCommand( shell: "/bin/sh", arguments: ["-c", script], timeout: 4.0)) let output = try #require(String(data: data, encoding: .utf8)) #expect(output.contains("out-3999")) #expect(output.contains("__CODEXBAR_DONE__")) } @Test func `shell runner terminates background children after normal exit`() throws { let marker = FileManager.default.temporaryDirectory .appendingPathComponent("codexbar-shell-runner-\(UUID().uuidString)") .path let escapedMarker = Self.shellSingleQuoted(marker) let script = """ ( trap '' TERM touch \(escapedMarker) while :; do sleep 1; done ) & printf '%s\\n' "$!" """ let data = try #require(ShellCommandLocator.test_runShellCommand( shell: "/bin/sh", arguments: ["-c", script], timeout: 2.0)) let pidText = try #require(String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines)) let pid = try #require(pid_t(pidText)) defer { kill(pid, SIGKILL) try? FileManager.default.removeItem(atPath: marker) } let deadline = Date().addingTimeInterval(2.0) while kill(pid, 0) == 0, Date() < deadline { usleep(50000 as useconds_t) } #expect(kill(pid, 0) != 0) } @Test func `resolves codex from env override`() { let overridePath = "/custom/bin/codex" let fm = MockFileManager(executables: [overridePath]) let resolved = BinaryLocator.resolveCodexBinary( env: ["CODEX_CLI_PATH": overridePath], loginPATH: nil, fileManager: fm, home: "/home/test") #expect(resolved == overridePath) } @Test func `resolves codex from login path`() { let fm = MockFileManager(executables: ["/login/bin/codex"]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/env/bin"], loginPATH: ["/login/bin"], fileManager: fm, home: "/home/test") #expect(resolved == "/login/bin/codex") } @Test func `resolves codex from env path`() { let fm = MockFileManager(executables: ["/env/bin/codex"]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/env/bin:/usr/bin"], loginPATH: nil, fileManager: fm, home: "/home/test") #expect(resolved == "/env/bin/codex") } @Test func `resolves codex from bundled ChatGPT app`() { let appPath = "/Applications/ChatGPT.app/Contents/Resources/codex" let fm = MockFileManager(executables: [appPath]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/missing/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { _, _ in true }, fileManager: fm, home: "/Users/test") #expect(resolved == appPath) } @Test func `resolves codex from user bundled ChatGPT app`() { let appPath = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let fm = MockFileManager(executables: [appPath]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/missing/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { _, _ in true }, fileManager: fm, home: "/Users/test") #expect(resolved == appPath) } @Test func `prefers bundled ChatGPT app over legacy Codex app within one scope`() { let chatGPTPath = "/Applications/ChatGPT.app/Contents/Resources/codex" let codexPath = "/Applications/Codex.app/Contents/Resources/codex" let fm = MockFileManager(executables: [chatGPTPath, codexPath]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/missing/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { _, _ in true }, fileManager: fm, home: "/Users/test") #expect(resolved == chatGPTPath) } @Test func `preserves user app precedence over system ChatGPT app`() { let userCodexPath = "/Users/test/Applications/Codex.app/Contents/Resources/codex" let systemChatGPTPath = "/Applications/ChatGPT.app/Contents/Resources/codex" let fm = MockFileManager(executables: [userCodexPath, systemChatGPTPath]) let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/missing/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { _, _ in true }, fileManager: fm, home: "/Users/test") #expect(resolved == userCodexPath) } @Test func `skips blocked ChatGPT app and falls back to legacy Codex app`() { let chatGPTPath = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let codexPath = "/Users/test/Applications/Codex.app/Contents/Resources/codex" let fm = MockFileManager(executables: [chatGPTPath, codexPath]) var checked: [String] = [] let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/missing/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { path, _ in checked.append(path) return path != chatGPTPath }, fileManager: fm, home: "/Users/test") #expect(resolved == codexPath) #expect(checked == [chatGPTPath, codexPath]) } @Test func `skips blocked codex path and falls back to signed app binary`() { let blockedPath = "/usr/local/bin/codex" let appPath = "/Applications/Codex.app/Contents/Resources/codex" let fm = MockFileManager(executables: [blockedPath, appPath]) var checked: [String] = [] let resolved = BinaryLocator.resolveCodexBinary( env: ["PATH": "/usr/local/bin"], loginPATH: nil, commandV: { _, _, _, _ in nil }, aliasResolver: { _, _, _, _, _ in nil }, launchCandidateFilter: { path, _ in checked.append(path) return path != blockedPath }, fileManager: fm, home: "/Users/test") #expect(resolved == appPath) #expect(checked == [blockedPath, appPath]) } @Test func `explicit codex override bypasses launch candidate fallback`() { let overridePath = "/custom/bin/codex" let appPath = "/Applications/Codex.app/Contents/Resources/codex" let fm = MockFileManager(executables: [overridePath, appPath]) var checked: [String] = [] let resolved = BinaryLocator.resolveCodexBinary( env: ["CODEX_CLI_PATH": overridePath], loginPATH: nil, launchCandidateFilter: { path, _ in checked.append(path) return false }, fileManager: fm, home: "/Users/test") #expect(resolved == overridePath) #expect(checked.isEmpty) } @Test func `Codex CLI strategy availability uses filtered binary resolution`() { let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let unavailable = CodexCLIUsageStrategy.resolvedBinary( env: ["PATH": "/missing/bin", "SHELL": "/bin/sh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: MockFileManager(executables: []), home: "/home/test") #expect(unavailable == nil) let available = CodexCLIUsageStrategy.resolvedBinary( env: ["PATH": "/tools/bin", "SHELL": "/bin/sh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: MockFileManager(executables: ["/tools/bin/codex"]), home: "/home/test") #expect(available == "/tools/bin/codex") } #if os(macOS) @Test func `Codex launch preflight allows quarantined notarized native binary`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/Applications/Codex.app/Contents/Resources/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { _ in .init(output: "accepted\nsource=Notarized Developer ID", exitStatus: 0) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(allowed) } @Test func `Codex launch preflight blocks malware attribute before assessment`() { var assessed = false let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/Applications/Codex.app/Contents/Resources/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.malware" }, spctlAssessment: { _ in assessed = true return .init(output: "accepted\nsource=Notarized Developer ID", exitStatus: 0) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) #expect(!assessed) } @Test func `Codex launch preflight validates containing app bundle`() { let executable = "/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Applications/ChatGPT.app" var assessedPaths: [String] = [] let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { path, name in path == bundle && name == "com.apple.quarantine" }, spctlAssessment: { path in assessedPaths.append(path) return .init(output: "\(path): accepted\nsource=Notarized Developer ID", exitStatus: 0) }, appSignatureIsTrusted: { path in path == bundle }, isMachOExecutable: { path in path == executable }) #expect(allowed) #expect(assessedPaths == [bundle]) } @Test func `Codex launch preflight blocks unexpected app signing identity`() { let executable = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Users/test/Applications/ChatGPT.app" var assessed = false let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { _ in assessed = true return .init(output: "accepted", exitStatus: 0) }, appSignatureIsTrusted: { path in #expect(path == bundle) return false }, isMachOExecutable: { path in path == executable }) #expect(!allowed) #expect(!assessed) } @Test func `Codex launch preflight blocks rejected containing app bundle`() { let executable = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Users/test/Applications/ChatGPT.app" let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { path in #expect(path == bundle) return .init(output: "\(path): rejected\nsource=no usable signature", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { path in path == executable }) #expect(!allowed) } @Test func `Codex launch preflight requires successful app bundle assessment`() { let executable = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Users/test/Applications/ChatGPT.app" let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { path in #expect(path == bundle) return .init(output: "\(path): accepted", exitStatus: 1) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { path in path == executable }) #expect(!allowed) } @Test func `Codex launch preflight rejects indeterminate app assessment`() { let executable = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Users/test/Applications/ChatGPT.app" let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { path in #expect(path == bundle) return .init(output: "internal code signing error", exitStatus: 0) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { path in path == executable }) #expect(!allowed) } @Test func `Codex launch preflight fails closed when app bundle cannot be assessed`() { let executable = "/Users/test/Applications/ChatGPT.app/Contents/Resources/codex" let bundle = "/Users/test/Applications/ChatGPT.app" let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable, fileManager: MockFileManager(executables: []), hasExtendedAttribute: { path, name in path == bundle && name == "com.apple.quarantine" }, spctlAssessment: { path in #expect(path == bundle) return nil }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in false }) #expect(!allowed) } @Test func `Codex launch preflight blocks app bundled executable symlink escaping the bundle`() throws { let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) let bundle = root.appendingPathComponent("ChatGPT.app") let resources = bundle.appendingPathComponent("Contents/Resources") let executable = resources.appendingPathComponent("codex") let escapedTarget = root.appendingPathComponent("outside-codex") try FileManager.default.createDirectory(at: resources, withIntermediateDirectories: true) try Data().write(to: escapedTarget) try FileManager.default.createSymbolicLink(at: executable, withDestinationURL: escapedTarget) defer { try? FileManager.default.removeItem(at: root) } var assessed = false let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: executable.path, fileManager: FileManager.default, hasExtendedAttribute: { _, _ in false }, spctlAssessment: { _ in assessed = true return .init(output: "accepted", exitStatus: 0) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) #expect(!assessed) } @Test func `Codex launch preflight blocks quarantined script without native assessment`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/opt/homebrew/bin/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { _ in nil }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in false }) #expect(!allowed) } @Test func `Codex launch preflight blocks revoked assessment`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/Applications/Codex.app/Contents/Resources/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { _ in .init(output: "rejected\nCSSMERR_TP_CERT_REVOKED", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) } @Test func `Codex launch preflight blocks generic Gatekeeper rejection`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/opt/homebrew/bin/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, _ in false }, spctlAssessment: { _ in .init(output: "rejected\nsource=no usable signature", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) } @Test func `Codex launch preflight allows valid signed command line binary assessment`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/opt/homebrew/bin/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { path in .init( output: "\(path): rejected (the code is valid but does not seem to be an app)", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(allowed) } @Test func `Codex launch preflight blocks revoked assessment even with non app rejection text`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/opt/homebrew/bin/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { _ in .init( output: """ rejected (the code is valid but does not seem to be an app) CSSMERR_TP_CERT_REVOKED """, exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) } @Test func `Codex launch preflight ignores benign text in path when verdict is generic rejection`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/tmp/code is valid but does not seem to be an app/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { path in .init(output: "\(path): rejected\nsource=no usable signature", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) } @Test func `Codex launch preflight ignores benign text before verdict separator`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/tmp/x: code is valid but does not seem to be an app/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { path in .init(output: "\(path): rejected\nsource=no usable signature", exitStatus: 3) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(!allowed) } @Test func `Codex launch preflight ignores blocked words in accepted path and source fields`() { let allowed = CodexLaunchPreflight.isLaunchCandidateAllowed( path: "/tmp/rejected/quarantine/codex", fileManager: MockFileManager(executables: []), hasExtendedAttribute: { _, name in name == "com.apple.quarantine" }, spctlAssessment: { path in .init( output: """ \(path): accepted source=revoked quarantine marker origin=malware test fixture """, exitStatus: 0) }, appSignatureIsTrusted: { _ in true }, isMachOExecutable: { _ in true }) #expect(allowed) } #endif @Test func `resolves codex from interactive shell`() { let fm = MockFileManager(executables: ["/shell/bin/codex"]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in #expect(tool == "codex") #expect(shell == "/bin/zsh") #expect(timeout == 2.0) _ = fileManager return "/shell/bin/codex" } let resolved = BinaryLocator.resolveCodexBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, fileManager: fm, home: "/home/test") #expect(resolved == "/shell/bin/codex") } @Test func `resolves claude from interactive shell`() { let fm = MockFileManager(executables: ["/shell/bin/claude"]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in #expect(tool == "claude") #expect(shell == "/bin/zsh") #expect(timeout == 2.0) _ = fileManager return "/shell/bin/claude" } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, fileManager: fm, home: "/home/test") #expect(resolved == "/shell/bin/claude") } @Test func `resolves gemini from interactive shell`() { let fm = MockFileManager(executables: ["/shell/bin/gemini"]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { tool, shell, timeout, fileManager in #expect(tool == "gemini") #expect(shell == "/bin/zsh") #expect(timeout == 2.0) _ = fileManager return "/shell/bin/gemini" } let resolved = BinaryLocator.resolveGeminiBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, fileManager: fm, home: "/home/test") #expect(resolved == "/shell/bin/gemini") } @Test func `resolves claude from login path`() { let fm = MockFileManager(executables: ["/login/bin/claude"]) let resolved = BinaryLocator.resolveClaudeBinary( env: ["PATH": "/env/bin"], loginPATH: ["/login/bin"], fileManager: fm, home: "/home/test") #expect(resolved == "/login/bin/claude") } @Test func `resolves claude from alias when other lookups fail`() { let aliasPath = "/home/test/.claude/local/bin/claude" let fm = MockFileManager(executables: [aliasPath]) var aliasCalled = false let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { tool, shell, timeout, _, home in aliasCalled = true #expect(tool == "claude") #expect(shell == "/bin/zsh") #expect(timeout == 2.0) #expect(home == "/home/test") return aliasPath } let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/home/test") #expect(aliasCalled) #expect(resolved == aliasPath) } @Test func `resolves codex from alias when other lookups fail`() { let aliasPath = "/home/test/.codex/bin/codex" let fm = MockFileManager(executables: [aliasPath]) var aliasCalled = false let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { tool, shell, timeout, _, home in aliasCalled = true #expect(tool == "codex") #expect(shell == "/bin/zsh") #expect(timeout == 2.0) #expect(home == "/home/test") return aliasPath } let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let resolved = BinaryLocator.resolveCodexBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/home/test") #expect(aliasCalled) #expect(resolved == aliasPath) } @Test func `resolves claude from well-known cmux path when shell lookups fail`() { let cmuxPath = "/Applications/cmux.app/Contents/Resources/bin/claude" let fm = MockFileManager(executables: [cmuxPath]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == cmuxPath) } @Test func `resolves claude from well-known home dir path`() { let homePath = "/Users/test/.claude/bin/claude" let fm = MockFileManager(executables: [homePath]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == homePath) } @Test func `resolves claude from native installer path`() { let nativePath = "/Users/test/.local/bin/claude" let fm = MockFileManager(executables: [nativePath]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == nativePath) } @Test func `prefers migrated local claude path over legacy home dir path`() { let migratedPath = "/Users/test/.claude/local/claude" let legacyPath = "/Users/test/.claude/bin/claude" let fm = MockFileManager(executables: [migratedPath, legacyPath]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == migratedPath) } @Test func `prefers user managed well-known path over cmux path`() { let homePath = "/Users/test/.claude/bin/claude" let cmuxPath = "/Applications/cmux.app/Contents/Resources/bin/claude" let fm = MockFileManager(executables: [homePath, cmuxPath]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == homePath) } @Test func `prefers homebrew arm path over usr local fallback`() { let fm = MockFileManager(executables: [ "/opt/homebrew/bin/claude", "/usr/local/bin/claude", ]) let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in nil } let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in nil } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/Users/test") #expect(resolved == "/opt/homebrew/bin/claude") } @Test func `prefers well-known paths over interactive shell lookup`() { let shellPath = "/custom/bin/claude" let cmuxPath = "/Applications/cmux.app/Contents/Resources/bin/claude" let fm = MockFileManager(executables: [shellPath, cmuxPath]) var shellLookupCalled = false let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in shellLookupCalled = true return shellPath } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, fileManager: fm, home: "/Users/test") #expect(!shellLookupCalled) #expect(resolved == cmuxPath) } @Test func `skips alias when command V resolves`() { let path = "/shell/bin/claude" let fm = MockFileManager(executables: [path]) var aliasCalled = false let aliasResolver: (String, String?, TimeInterval, FileManager, String) -> String? = { _, _, _, _, _ in aliasCalled = true return "/alias/claude" } let commandV: (String, String?, TimeInterval, FileManager) -> String? = { _, _, _, _ in path } let resolved = BinaryLocator.resolveClaudeBinary( env: ["SHELL": "/bin/zsh"], loginPATH: nil, commandV: commandV, aliasResolver: aliasResolver, fileManager: fm, home: "/home/test") #expect(!aliasCalled) #expect(resolved == path) } private static func shellSingleQuoted(_ value: String) -> String { "'\(value.replacingOccurrences(of: "'", with: "'\\''"))'" } } private final class LoginShellPathCaptureStub: @unchecked Sendable { private let lock = NSLock() private var results: [[String]?] private var callCountStorage = 0 var callCount: Int { self.lock.lock() defer { self.lock.unlock() } return self.callCountStorage } init(_ results: [[String]?]) { self.results = results } func next() -> [String]? { self.lock.lock() defer { self.lock.unlock() } self.callCountStorage += 1 return self.results.isEmpty ? nil : self.results.removeFirst() } } private final class MockFileManager: FileManager { private let executables: Set init(executables: Set) { self.executables = executables } override func isExecutableFile(atPath path: String) -> Bool { self.executables.contains(path) } }