using ClaudeDo.Installer.Checks; using ClaudeDo.Installer.Core; namespace ClaudeDo.Installer.Tests.Checks; public sealed class ClaudeVersionCheckTests : IDisposable { private readonly string _dir; public ClaudeVersionCheckTests() { _dir = Path.Combine(Path.GetTempPath(), $"cdclaudeversion_{Guid.NewGuid():N}"); Directory.CreateDirectory(_dir); File.WriteAllText(Path.Combine(_dir, "claude.exe"), ""); } public void Dispose() { try { Directory.Delete(_dir, recursive: true); } catch { } } private ClaudeCliLookup MakeLookup(FakeProcessRunner runner) => new(runner, pathOverride: _dir, pathExtOverride: ".exe"); [Theory] [InlineData("2.1.220 (Claude Code)", CheckStatus.Ok)] // exactly at the floor [InlineData("2.1.219 (Claude Code)", CheckStatus.Failed)] // one patch below the floor [InlineData("2.1.191", CheckStatus.Failed)] [InlineData("v2.1.191", CheckStatus.Failed)] [InlineData("2.10.0", CheckStatus.Ok)] // minor 10 > minor 1, not a lexical trap [InlineData("garbage output, no version here", CheckStatus.Unknown)] public async Task Version_parsing_and_gate(string versionOutput, CheckStatus expected) { var runner = new FakeProcessRunner((0, versionOutput)); var check = new ClaudeVersionCheck(MakeLookup(runner)); var result = await check.RunAsync(new InstallContext(), CancellationToken.None); Assert.Equal(expected, result.Status); } [Fact] public void Minor_version_compares_numerically_not_lexically() { Assert.True(new Version(2, 10, 0) > new Version(2, 9, 0)); } [Fact] public async Task Cli_not_found_reports_unknown_not_failed() { var emptyDir = Path.Combine(Path.GetTempPath(), $"cdclaudeversion_empty_{Guid.NewGuid():N}"); Directory.CreateDirectory(emptyDir); try { var runner = new FakeProcessRunner(); var check = new ClaudeVersionCheck(new ClaudeCliLookup(runner, pathOverride: emptyDir, pathExtOverride: ".exe")); var ctx = new InstallContext { ClaudeBin = "claudedo_totally_missing_cmd_9f3a1" }; var result = await check.RunAsync(ctx, CancellationToken.None); Assert.Empty(runner.Calls); Assert.Equal(CheckStatus.Unknown, result.Status); } finally { Directory.Delete(emptyDir, recursive: true); } } }