using System.Net; using System.Net.Sockets; using ClaudeDo.Installer.Checks; using ClaudeDo.Installer.Checks.Interfaces; using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Tests.Pages.SystemCheckPage; using Vm = ClaudeDo.Installer.Pages.DiagnosePage.DiagnosePageViewModel; namespace ClaudeDo.Installer.Tests.Pages.DiagnosePage; public sealed class DiagnosePageViewModelTests { private sealed class FakeOwnerResolver : IPortOwnerResolver { private readonly string? _owner; public FakeOwnerResolver(string? owner) => _owner = owner; public Task FindOwningProcessNameAsync(int port, CancellationToken ct) => Task.FromResult(_owner); } private static int GetFreePort() { var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; listener.Stop(); return port; } [Fact] public async Task Loading_the_page_does_not_run_checks() { var check = FakeEnvironmentCheck.Ok("a"); var vm = new Vm( new InstallContext(), () => new EnvironmentCheckService(new IEnvironmentCheck[] { check }), () => new InstallerWorkerConfig()); await vm.LoadAsync(); Assert.Equal(0, check.CallCount); Assert.False(vm.Checks.HasRun); Assert.Empty(vm.Checks.Rows); } [Fact] public async Task Clicking_check_runs_once_and_a_second_click_during_the_run_starts_no_new_run() { var gate = new TaskCompletionSource(); var slow = new FakeEnvironmentCheck("slow", CheckSeverity.Error, async (_, _) => { await gate.Task; return CheckResult.Ok("slow", CheckSeverity.Error, "checks.slow.title", "ok"); }); var factoryCalls = 0; var vm = new Vm( new InstallContext(), () => { factoryCalls++; return new EnvironmentCheckService(new IEnvironmentCheck[] { slow }); }, () => new InstallerWorkerConfig()); await vm.LoadAsync(); var firstRun = vm.Checks.RunChecksCommand.ExecuteAsync(null); Assert.True(vm.Checks.IsRunning); await vm.Checks.RunChecksCommand.ExecuteAsync(null); // second click while the first run is still in flight Assert.Equal(1, factoryCalls); Assert.Equal(1, slow.CallCount); gate.SetResult(); await firstRun; Assert.False(vm.Checks.IsRunning); } [Fact] public async Task Checks_run_against_the_installed_configuration_not_context_defaults() { InstallContext? capturedCtx = null; var probe = new FakeEnvironmentCheck("probe", CheckSeverity.Warning, (ctx, _) => { capturedCtx = ctx; return Task.FromResult(CheckResult.Ok("probe", CheckSeverity.Warning, "checks.probe.title", "ok")); }); var sharedContext = new InstallContext { InstallDirectory = @"C:\Program Files\ClaudeDo" }; var vm = new Vm( sharedContext, () => new EnvironmentCheckService(new IEnvironmentCheck[] { probe }), () => new InstallerWorkerConfig { ClaudeBin = @"C:\installed\claude.exe", SignalRPort = 55555 }); await vm.LoadAsync(); await vm.Checks.RunChecksCommand.ExecuteAsync(null); Assert.NotNull(capturedCtx); Assert.Equal(@"C:\installed\claude.exe", capturedCtx!.ClaudeBin); Assert.Equal(55555, capturedCtx.SignalRPort); Assert.Equal(@"C:\Program Files\ClaudeDo", capturedCtx.InstallDirectory); } [Fact] public async Task Worker_already_running_on_the_installed_signalr_port_reports_ok() { var port = GetFreePort(); using var occupying = new TcpListener(IPAddress.Loopback, port); occupying.Start(); var sharedContext = new InstallContext { ExternalMcpPort = GetFreePort() }; var vm = new Vm( sharedContext, () => new EnvironmentCheckService(new IEnvironmentCheck[] { new PortCheck(new FakeOwnerResolver("ClaudeDo.Worker")) }), () => new InstallerWorkerConfig { SignalRPort = port }); await vm.LoadAsync(); await vm.Checks.RunChecksCommand.ExecuteAsync(null); var row = Assert.Single(vm.Checks.Rows); Assert.Equal(CheckStatus.Ok, row.Status); } }