zcbot/windows-node/Zcbot.WindowsNode.Tests/SoftwarePageViewModelTests.cs

146 lines
5.5 KiB
C#

namespace Zcbot.WindowsNode.Tests;
public sealed class SoftwarePageViewModelTests
{
[Fact]
public async Task Refresh_PopulatesSoftwareCardsAndExecutionState()
{
var service = new FakeSoftwareManagementService(
Snapshot("origin", "Origin", available: true),
Snapshot("ansys", "ANSYS", available: false, active: true));
using var viewModel = new SoftwarePageViewModel(service);
await viewModel.RefreshAsync();
Assert.Equal(2, viewModel.Cards.Count);
Assert.True(viewModel.Cards[0].InstallRuntimeCommand.CanExecute(null));
Assert.False(viewModel.Cards[1].InstallRuntimeCommand.CanExecute(null));
Assert.Equal("检测完成", viewModel.Message);
}
[Fact]
public async Task ChooseAndClearLocation_UseSoftwareServiceThenRefresh()
{
var service = new FakeSoftwareManagementService(
Snapshot("origin", "Origin", available: false));
using var viewModel = new SoftwarePageViewModel(service);
viewModel.ChooseLocationRequested += (_, _, _) => @"C:\Origin\Origin.exe";
await viewModel.RefreshAsync();
var card = Assert.Single(viewModel.Cards);
card.ChooseLocationCommand.Execute(null);
await WaitUntilAsync(() => service.SavedPath is not null);
await WaitUntilAsync(() => !viewModel.IsBusy);
Assert.Equal(("origin", @"C:\Origin\Origin.exe"), service.SavedPath);
Assert.Equal("应用位置已保存", viewModel.Message);
card.ClearLocationCommand.Execute(null);
await WaitUntilAsync(() => service.ClearedId is not null);
await WaitUntilAsync(() => !viewModel.IsBusy);
Assert.Equal("origin", service.ClearedId);
}
[Fact]
public async Task InstallRuntime_RequiresConfirmationAndReportsResult()
{
var service = new FakeSoftwareManagementService(
Snapshot("blender", "Blender", available: true));
using var viewModel = new SoftwarePageViewModel(service);
viewModel.ConfirmInstallRequested += _ => true;
await viewModel.RefreshAsync();
viewModel.Cards[0].InstallRuntimeCommand.Execute(null);
await WaitUntilAsync(() => service.InstallCount == 1);
await WaitUntilAsync(() => !viewModel.IsBusy);
Assert.Contains("安装完成", viewModel.Message);
Assert.Equal("blender", service.InstalledId);
}
[Fact]
public async Task CancelInstall_CancelsTheInFlightRuntimeOperation()
{
var service = new FakeSoftwareManagementService(
Snapshot("origin", "Origin", available: true))
{
BlockInstall = true,
};
using var viewModel = new SoftwarePageViewModel(service);
viewModel.ConfirmInstallRequested += _ => true;
await viewModel.RefreshAsync();
viewModel.Cards[0].InstallRuntimeCommand.Execute(null);
await service.InstallStarted.Task.WaitAsync(TimeSpan.FromSeconds(2));
Assert.True(viewModel.CancelInstallCommand.CanExecute(null));
viewModel.CancelInstallCommand.Execute(null);
await WaitUntilAsync(() => !viewModel.IsBusy);
Assert.Equal("安装已取消,原运行环境保持不变。", viewModel.Message);
}
private static SoftwareManagementSnapshot Snapshot(
string id,
string displayName,
bool available,
bool active = false) =>
new(
id,
displayName,
SoftwarePathKind.Executable,
"*.exe",
available,
available ? $@"C:\{displayName}\app.exe" : "未检测到应用位置",
available ? "运行环境已安装" : "运行环境尚未安装",
RuntimeInstalled: available,
HasActiveJobs: active);
private static async Task WaitUntilAsync(Func<bool> condition)
{
var deadline = DateTime.UtcNow.AddSeconds(2);
while (!condition() && DateTime.UtcNow < deadline)
{
await Task.Delay(10);
}
Assert.True(condition());
}
private sealed class FakeSoftwareManagementService(
params SoftwareManagementSnapshot[] snapshots) : ISoftwareManagementService
{
internal (string Id, string Path)? SavedPath { get; private set; }
internal string? ClearedId { get; private set; }
internal string? InstalledId { get; private set; }
internal int InstallCount { get; private set; }
internal bool BlockInstall { get; init; }
internal TaskCompletionSource InstallStarted { get; } = new(
TaskCreationOptions.RunContinuationsAsynchronously);
public Task<IReadOnlyList<SoftwareManagementSnapshot>> RefreshAsync(
CancellationToken cancellationToken) =>
Task.FromResult<IReadOnlyList<SoftwareManagementSnapshot>>(snapshots);
public void SaveLocation(string softwareId, string path) =>
SavedPath = (softwareId, path);
public void ClearLocation(string softwareId) => ClearedId = softwareId;
public async Task<RuntimeInstallResult> InstallRuntimeAsync(
string softwareId,
IProgress<string> progress,
CancellationToken cancellationToken)
{
InstallCount++;
InstalledId = softwareId;
InstallStarted.TrySetResult();
progress.Report("正在安装…");
if (BlockInstall)
{
await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken);
}
return new RuntimeInstallResult(@"C:\runtime\python.exe", "安装完成");
}
}
}