using ClaudeDo.Installer.Checks; using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Tests.Checks; public sealed class ClaudeCliCheckTests : IDisposable { private readonly string _dir; public ClaudeCliCheckTests() { _dir = Path.Combine(Path.GetTempPath(), $"cdclaudecli_{Guid.NewGuid():N}"); Directory.CreateDirectory(_dir); } public void Dispose() { try { Directory.Delete(_dir, recursive: true); } catch { } } [Fact] public async Task Found_as_exe_reports_ok_with_path_and_version_and_no_shim_detail() { File.WriteAllText(Path.Combine(_dir, "claude.exe"), ""); var runner = new FakeProcessRunner((0, "2.1.230 (Claude Code)")); var check = new ClaudeCliCheck(new ClaudeCliLookup(runner, pathOverride: _dir, pathExtOverride: ".exe")); var result = await check.RunAsync(new InstallContext(), CancellationToken.None); Assert.Equal(CheckStatus.Ok, result.Status); Assert.Contains(_dir, result.Message); Assert.Contains("2.1.230", result.Message); Assert.Null(result.Detail); } [Fact] public async Task Found_as_shim_flags_it_in_detail() { File.WriteAllText(Path.Combine(_dir, "claude.cmd"), ""); var runner = new FakeProcessRunner((0, "2.1.230 (Claude Code)")); var check = new ClaudeCliCheck(new ClaudeCliLookup(runner, pathOverride: _dir, pathExtOverride: ".cmd")); var result = await check.RunAsync(new InstallContext(), CancellationToken.None); Assert.Equal(CheckStatus.Ok, result.Status); Assert.NotNull(result.Detail); } [Fact] public async Task Not_found_reports_failed_naming_searched_locations() { var runner = new FakeProcessRunner(); var check = new ClaudeCliCheck(new ClaudeCliLookup(runner, pathOverride: _dir, pathExtOverride: ".exe")); // A distinctive, definitely-missing name — a plain "claude" would still resolve via // ExecutableResolver's fixed fallback dirs (e.g. %USERPROFILE%\.local\bin) on a machine // that has the real CLI installed, regardless of pathOverride. var ctx = new InstallContext { ClaudeBin = "claudedo_totally_missing_cmd_9f3a1" }; var result = await check.RunAsync(ctx, CancellationToken.None); Assert.Equal(CheckStatus.Failed, result.Status); Assert.Equal(CheckSeverity.Error, result.Severity); Assert.Contains(_dir, result.Message); Assert.NotNull(result.HintKey); Assert.Empty(runner.Calls); } [Fact] public async Task Nonzero_exit_reports_failed() { File.WriteAllText(Path.Combine(_dir, "claude.exe"), ""); var runner = new FakeProcessRunner((1, "some error")); var check = new ClaudeCliCheck(new ClaudeCliLookup(runner, pathOverride: _dir, pathExtOverride: ".exe")); var result = await check.RunAsync(new InstallContext(), CancellationToken.None); Assert.Equal(CheckStatus.Failed, result.Status); } }