using ClaudeDo.Data.Environment; namespace ClaudeDo.Data.Tests; public sealed class ExecutableResolverTests : IDisposable { private readonly string _root; public ExecutableResolverTests() { _root = Path.Combine(Path.GetTempPath(), $"claudedo_exeresolve_{Guid.NewGuid():N}"); Directory.CreateDirectory(_root); } public void Dispose() { try { Directory.Delete(_root, recursive: true); } catch { } } private string MakeDir(string name) { var dir = Path.Combine(_root, name); Directory.CreateDirectory(dir); return dir; } private static void Touch(string path) => File.WriteAllText(path, ""); [Fact] public void Command_with_directory_separator_is_treated_as_path() { var dir = MakeDir("bin1"); var exePath = Path.Combine(dir, "myclaude.exe"); Touch(exePath); var result = ExecutableResolver.Resolve(exePath, pathOverride: "C:\\does\\not\\matter"); Assert.NotNull(result); Assert.Equal(exePath, result!.Path); Assert.False(result.IsShim); } [Fact] public void Path_like_command_without_extension_resolves_via_pathext() { var dir = MakeDir("bin2"); var cmdPath = Path.Combine(dir, "myclaude.cmd"); Touch(cmdPath); var commandWithoutExt = Path.Combine(dir, "myclaude"); var result = ExecutableResolver.Resolve(commandWithoutExt, pathExtOverride: ".com;.exe;.bat;.cmd"); Assert.NotNull(result); Assert.Equal(cmdPath, result!.Path); Assert.True(result.IsShim); } [Fact] public void Cmd_file_in_path_is_found_when_no_exe_exists() { var dir = MakeDir("bin3"); Touch(Path.Combine(dir, "claude.cmd")); var result = ExecutableResolver.Resolve("claude", pathOverride: dir, pathExtOverride: ".com;.exe;.bat;.cmd"); Assert.NotNull(result); Assert.Equal(Path.Combine(dir, "claude.cmd"), result!.Path); Assert.True(result.IsShim); } [Fact] public void Exe_wins_against_cmd_in_same_directory() { var dir = MakeDir("bin4"); Touch(Path.Combine(dir, "claude.cmd")); Touch(Path.Combine(dir, "claude.exe")); var result = ExecutableResolver.Resolve("claude", pathOverride: dir, pathExtOverride: ".com;.exe;.bat;.cmd"); Assert.NotNull(result); Assert.Equal(Path.Combine(dir, "claude.exe"), result!.Path); Assert.False(result.IsShim); } [Fact] public void Directory_order_in_path_is_respected() { var dir1 = MakeDir("bin5a"); var dir2 = MakeDir("bin5b"); Touch(Path.Combine(dir1, "claude.cmd")); Touch(Path.Combine(dir2, "claude.exe")); var pathOverride = $"{dir1}{Path.PathSeparator}{dir2}"; var result = ExecutableResolver.Resolve("claude", pathOverride: pathOverride, pathExtOverride: ".com;.exe;.bat;.cmd"); Assert.NotNull(result); Assert.Equal(Path.Combine(dir1, "claude.cmd"), result!.Path); Assert.True(result.IsShim); } [Fact] public void Unresolvable_command_returns_null() { var dir = MakeDir("bin6"); var result = ExecutableResolver.Resolve( "claudedo_totally_missing_cmd_9f3a1", pathOverride: dir, pathExtOverride: ".com;.exe;.bat;.cmd"); Assert.Null(result); } [Fact] public void BuildShimStartInfo_uses_cmd_exe_and_quotes_paths_with_spaces() { const string shimPath = @"C:\Program Files\claude\claude.cmd"; var startInfo = ExecutableResolver.BuildShimStartInfo(shimPath, new[] { "--version", "arg2" }); Assert.Equal("cmd.exe", startInfo.FileName); Assert.Contains("/c", startInfo.Arguments); Assert.Contains($"\"{shimPath}\"", startInfo.Arguments); Assert.Contains("--version", startInfo.Arguments); Assert.Contains("arg2", startInfo.Arguments); } }