Der Button, der aus „Problem erkannt" ein „Problem gelöst" macht: startet eine interaktive Claude-Session, die dem Nutzer beim Einrichten hilft. ## Warum externes Terminal Der ConPTY-Stack (`PtyTerminalSession`, `ConPtyPaneView`) liegt in `ClaudeDo.Ui` und ist Avalonia — der Installer ist WPF und referenziert nur Data/Releases/Localization. Beim Fresh Install sind `app\`/`worker\` außerdem noch n ClaudeDo-Task: 4e196058-38a3-404f-9862-4cb0e90195da
236 lines
7.2 KiB
C#
236 lines
7.2 KiB
C#
using ClaudeDo.Installer.Checks;
|
|
using ClaudeDo.Installer.Core;
|
|
using ClaudeDo.Installer.Tests.Core;
|
|
using Vm = ClaudeDo.Installer.Pages.SystemCheckPage.SystemCheckPageViewModel;
|
|
|
|
namespace ClaudeDo.Installer.Tests.Pages.SystemCheckPage;
|
|
|
|
public sealed class SystemCheckPageViewModelTests
|
|
{
|
|
private static Vm CreateViewModel(Func<EnvironmentCheckService> factory, ClaudeHelpLauncher? claudeHelpLauncher = null, InstallContext? context = null) =>
|
|
new(context ?? new InstallContext(), factory, claudeHelpLauncher ?? TestClaudeHelpLauncher.Create());
|
|
|
|
private static Func<EnvironmentCheckService> FactoryFor(params IEnvironmentCheck[] checks) =>
|
|
() => new EnvironmentCheckService(checks);
|
|
|
|
[Fact]
|
|
public async Task Entering_the_page_runs_checks_exactly_once()
|
|
{
|
|
var check = FakeEnvironmentCheck.Ok("a");
|
|
var vm = CreateViewModel(FactoryFor(check));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.Equal(1, check.CallCount);
|
|
Assert.True(vm.HasRun);
|
|
Assert.Single(vm.Rows);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Entering_the_page_a_second_time_does_not_run_checks_again()
|
|
{
|
|
var check = FakeEnvironmentCheck.Ok("a");
|
|
var vm = CreateViewModel(FactoryFor(check));
|
|
|
|
await vm.LoadAsync();
|
|
await vm.LoadAsync();
|
|
|
|
Assert.Equal(1, check.CallCount);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Recheck_while_a_run_is_in_progress_does_not_start_a_second_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 = CreateViewModel(() =>
|
|
{
|
|
factoryCalls++;
|
|
return new EnvironmentCheckService(new IEnvironmentCheck[] { slow });
|
|
});
|
|
|
|
var firstRun = vm.RunChecksCommand.ExecuteAsync(null);
|
|
Assert.True(vm.IsRunning);
|
|
|
|
await vm.RunChecksCommand.ExecuteAsync(null); // recheck while the first run is still in flight
|
|
|
|
Assert.Equal(1, factoryCalls);
|
|
Assert.Equal(1, slow.CallCount);
|
|
|
|
gate.SetResult();
|
|
await firstRun;
|
|
|
|
Assert.False(vm.IsRunning);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Blocking_error_blocks_navigation_and_clears_after_a_clean_recheck()
|
|
{
|
|
var callNumber = 0;
|
|
var vm = CreateViewModel(() =>
|
|
{
|
|
callNumber++;
|
|
IEnvironmentCheck check = callNumber == 1
|
|
? FakeEnvironmentCheck.Fail("git", CheckSeverity.Error)
|
|
: FakeEnvironmentCheck.Ok("git");
|
|
return new EnvironmentCheckService(new[] { check });
|
|
});
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.True(vm.HasBlockingError);
|
|
Assert.True(vm.BlocksNavigation);
|
|
|
|
await vm.RunChecksCommand.ExecuteAsync(null);
|
|
|
|
Assert.False(vm.HasBlockingError);
|
|
Assert.False(vm.BlocksNavigation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Only_warnings_does_not_block_navigation()
|
|
{
|
|
var warning = FakeEnvironmentCheck.Fail("gitIdentity", CheckSeverity.Warning);
|
|
var vm = CreateViewModel(FactoryFor(warning));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.False(vm.HasBlockingError);
|
|
Assert.False(vm.BlocksNavigation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Only_unknown_results_do_not_block_navigation()
|
|
{
|
|
var unknown = FakeEnvironmentCheck.Unknown("claudeAuth", CheckSeverity.Error);
|
|
var vm = CreateViewModel(FactoryFor(unknown));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.False(vm.HasBlockingError);
|
|
Assert.False(vm.BlocksNavigation);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_reports_all_ok()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(FakeEnvironmentCheck.Ok("git")));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.Equal("Everything looks good.", vm.Summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_reports_warning_count()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Fail("gitIdentity", CheckSeverity.Warning),
|
|
FakeEnvironmentCheck.Fail("ports", CheckSeverity.Warning)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.Equal("2 warning(s) found.", vm.Summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_reports_blocking_errors_by_name()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Fail("git", CheckSeverity.Error),
|
|
FakeEnvironmentCheck.Fail("writeAccess", CheckSeverity.Error)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.Equal("2 problem(s) must be fixed: checks.git.title, checks.writeAccess.title", vm.Summary);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Summary_reports_running_state_while_a_check_is_in_flight()
|
|
{
|
|
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 vm = CreateViewModel(FactoryFor(slow));
|
|
|
|
var run = vm.RunChecksCommand.ExecuteAsync(null);
|
|
|
|
Assert.True(vm.IsRunning);
|
|
Assert.Equal("Checking your system…", vm.Summary);
|
|
|
|
gate.SetResult();
|
|
await run;
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClaudeHelp_disabled_when_claude_cli_check_failed()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Fail(ClaudeCliCheck.CheckId, CheckSeverity.Error),
|
|
FakeEnvironmentCheck.Ok(ClaudeAuthCheck.CheckId)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.False(vm.CanStartClaudeHelp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClaudeHelp_disabled_when_claude_auth_check_failed()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Ok(ClaudeCliCheck.CheckId),
|
|
FakeEnvironmentCheck.Fail(ClaudeAuthCheck.CheckId, CheckSeverity.Error)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.False(vm.CanStartClaudeHelp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClaudeHelp_enabled_when_both_checks_ok()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Ok(ClaudeCliCheck.CheckId),
|
|
FakeEnvironmentCheck.Ok(ClaudeAuthCheck.CheckId)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.True(vm.CanStartClaudeHelp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClaudeHelp_stays_enabled_when_auth_check_is_unknown()
|
|
{
|
|
var vm = CreateViewModel(FactoryFor(
|
|
FakeEnvironmentCheck.Ok(ClaudeCliCheck.CheckId),
|
|
FakeEnvironmentCheck.Unknown(ClaudeAuthCheck.CheckId)));
|
|
|
|
await vm.LoadAsync();
|
|
|
|
Assert.True(vm.CanStartClaudeHelp);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClaudeHelp_start_failure_sets_error_message_without_throwing()
|
|
{
|
|
var ctx = new InstallContext { ClaudeBin = "claudedo_totally_missing_cmd_9f3a1" };
|
|
var vm = CreateViewModel(
|
|
FactoryFor(FakeEnvironmentCheck.Ok(ClaudeCliCheck.CheckId), FakeEnvironmentCheck.Ok(ClaudeAuthCheck.CheckId)),
|
|
context: ctx);
|
|
await vm.LoadAsync();
|
|
|
|
await vm.StartClaudeHelpCommand.ExecuteAsync(null);
|
|
|
|
Assert.NotNull(vm.ClaudeHelpError);
|
|
}
|
|
}
|