146 lines
5.5 KiB
C#
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", "安装完成");
|
|
}
|
|
}
|
|
}
|