namespace Zcbot.WindowsNode.Tests; public sealed class AnsysAcceptanceViewModelTests : IDisposable { private readonly string root = Path.Combine( Path.GetTempPath(), "zcbot-ansys-view-model-tests", Guid.NewGuid().ToString("N")); [Fact] public async Task PassedAcceptanceEnablesTheMachineGateAfterConfirmation() { Directory.CreateDirectory(root); var reportPath = Path.Combine(root, "acceptance-report.json"); var service = new FakeAnsysAcceptanceService( new AdapterAcceptanceResult(true, reportPath, "passed")); using var viewModel = new AnsysAcceptanceViewModel(service); viewModel.ChooseReportDirectoryRequested += () => root; viewModel.ConfirmRunRequested += () => true; viewModel.ConfirmEnableGateRequested += () => true; viewModel.RunCommand.Execute(null); await WaitUntilAsync(() => !viewModel.IsRunning && service.RunCount == 1); Assert.True(viewModel.CanEnableGate); Assert.Equal("success", viewModel.Tone); Assert.Contains(reportPath, viewModel.Message); viewModel.EnableGateCommand.Execute(null); Assert.True(service.IsGateEnabled); Assert.False(viewModel.CanEnableGate); Assert.Contains("重新启动", viewModel.Message); } [Fact] public async Task CancellationKeepsTheExecutionGateClosed() { Directory.CreateDirectory(root); var service = new FakeAnsysAcceptanceService(null) { BlockRun = true }; using var viewModel = new AnsysAcceptanceViewModel(service); viewModel.ChooseReportDirectoryRequested += () => root; viewModel.ConfirmRunRequested += () => true; viewModel.RunCommand.Execute(null); await service.RunStarted.Task.WaitAsync(TimeSpan.FromSeconds(2)); viewModel.CancelCommand.Execute(null); await WaitUntilAsync(() => !viewModel.IsRunning); Assert.False(viewModel.CanEnableGate); Assert.Equal("验收已停止,执行门保持关闭。", viewModel.Message); } [Fact] public void ReportValidationRequiresAnExplicitPassedValue() { Directory.CreateDirectory(root); var passed = Path.Combine(root, "passed.json"); var failed = Path.Combine(root, "failed.json"); File.WriteAllText(passed, "{\"passed\":true}"); File.WriteAllText(failed, "{\"passed\":false}"); Assert.True(AnsysAcceptanceService.ReportPassed(passed)); Assert.False(AnsysAcceptanceService.ReportPassed(failed)); Assert.False(AnsysAcceptanceService.ReportPassed(Path.Combine(root, "missing.json"))); } public void Dispose() { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } private static async Task WaitUntilAsync(Func condition) { var deadline = DateTime.UtcNow.AddSeconds(2); while (!condition() && DateTime.UtcNow < deadline) { await Task.Delay(10); } Assert.True(condition()); } private sealed class FakeAnsysAcceptanceService(AdapterAcceptanceResult? result) : IAnsysAcceptanceService { internal bool BlockRun { get; init; } internal int RunCount { get; private set; } internal TaskCompletionSource RunStarted { get; } = new( TaskCreationOptions.RunContinuationsAsynchronously); public bool IsAvailable => true; public bool IsGateEnabled { get; private set; } public async Task RunAsync( string workRoot, IProgress progress, CancellationToken cancellationToken) { RunCount++; RunStarted.TrySetResult(); progress.Report("running"); if (BlockRun) { await Task.Delay(Timeout.InfiniteTimeSpan, cancellationToken); } return result ?? throw new InvalidOperationException("missing result"); } public void EnableGate(string reportPath) => IsGateEnabled = true; } }