using Moq; using Xunit; using System.Windows.Media.Imaging; using XplorePlane.Services; using XplorePlane.ViewModels; using XplorePlane.Tests.Helpers; using System.Threading.Tasks; using System; using System.Linq; using System.Collections.Generic; using System.Threading; namespace XplorePlane.Tests.Pipeline { /// /// 测试 PipelineExecutionService(任务 6.6) /// public class PipelineExecutionServiceTests { private readonly Mock _mockImageSvc; private readonly PipelineExecutionService _svc; private readonly BitmapSource _testBitmap; public PipelineExecutionServiceTests() { _mockImageSvc = TestHelpers.CreateMockImageService(); _svc = new PipelineExecutionService(_mockImageSvc.Object); _testBitmap = TestHelpers.CreateTestBitmap(); } private static PipelineNodeViewModel MakeNode(string key, int order = 0, bool enabled = true) { var node = new PipelineNodeViewModel(key, key + "算子") { Order = order, IsEnabled = enabled }; return node; } // ── 空流水线 ────────────────────────────────────────────────── [Fact] public async Task EmptyPipeline_ReturnsSourceImage() { var result = await _svc.ExecutePipelineAsync( Enumerable.Empty(), _testBitmap); Assert.Same(_testBitmap, result); } [Fact] public async Task NullNodes_ReturnsSourceImage() { var result = await _svc.ExecutePipelineAsync(null!, _testBitmap); Assert.Same(_testBitmap, result); } [Fact] public async Task AllDisabledNodes_ReturnsSourceImage() { var nodes = new[] { MakeNode("Blur", 0, enabled: false) }; var result = await _svc.ExecutePipelineAsync(nodes, _testBitmap); Assert.Same(_testBitmap, result); } [Fact] public async Task NullSource_ThrowsArgumentNullException() { await Assert.ThrowsAsync( () => _svc.ExecutePipelineAsync(Enumerable.Empty(), null!)); } // ── 取消令牌 ────────────────────────────────────────────────── [Fact] public async Task CancelledToken_ThrowsOperationCanceledException() { // 让 ProcessImageAsync 在执行时检查取消令牌 _mockImageSvc.Setup(s => s.ProcessImageAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns, IProgress, CancellationToken>( (src, _, _, _, ct) => { ct.ThrowIfCancellationRequested(); return Task.FromResult(src); }); using var cts = new CancellationTokenSource(); cts.Cancel(); var nodes = new[] { MakeNode("Blur", 0) }; await Assert.ThrowsAsync( () => _svc.ExecutePipelineAsync(nodes, _testBitmap, cancellationToken: cts.Token)); } [Fact] public async Task PreCancelledToken_ThrowsBeforeExecution() { using var cts = new CancellationTokenSource(); cts.Cancel(); var nodes = new[] { MakeNode("Blur", 0) }; await Assert.ThrowsAsync( () => _svc.ExecutePipelineAsync(nodes, _testBitmap, cancellationToken: cts.Token)); // ProcessImageAsync 不应被调用 _mockImageSvc.Verify(s => s.ProcessImageAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny()), Times.Never); } // ── 节点失败包装 ────────────────────────────────────────────── [Fact] public async Task NodeThrows_WrappedAsPipelineExecutionException() { _mockImageSvc.Setup(s => s.ProcessImageAsync( It.IsAny(), "Blur", It.IsAny>(), It.IsAny>(), It.IsAny())) .ThrowsAsync(new InvalidOperationException("处理失败")); var nodes = new[] { MakeNode("Blur", 0) }; var ex = await Assert.ThrowsAsync( () => _svc.ExecutePipelineAsync(nodes, _testBitmap)); Assert.Equal("Blur", ex.FailedOperatorKey); Assert.Equal(0, ex.FailedNodeOrder); } [Fact] public async Task NodeReturnsNull_ThrowsPipelineExecutionException() { _mockImageSvc.Setup(s => s.ProcessImageAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) .ReturnsAsync((BitmapSource?)null); var nodes = new[] { MakeNode("Blur", 0) }; await Assert.ThrowsAsync( () => _svc.ExecutePipelineAsync(nodes, _testBitmap)); } // ── 进度回调 ────────────────────────────────────────────────── [Fact] public async Task Progress_ReportedForEachNode() { var nodes = new[] { MakeNode("Blur", 0), MakeNode("Sharpen", 1) }; var reports = new List(); var progress = new Progress(p => reports.Add(p)); await _svc.ExecutePipelineAsync(nodes, _testBitmap, progress); // 等待 Progress 回调(Progress 是异步的) await Task.Delay(50); Assert.Equal(2, reports.Count); Assert.Equal(1, reports[0].CurrentStep); Assert.Equal(2, reports[1].CurrentStep); } // ── 节点按 Order 排序执行 ───────────────────────────────────── [Fact] public async Task Nodes_ExecutedInOrderAscending() { var executionOrder = new List(); _mockImageSvc.Setup(s => s.ProcessImageAsync( It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny())) .Returns, IProgress, CancellationToken>( (src, key, _, _, _) => { executionOrder.Add(key); return Task.FromResult(src); }); // 故意乱序传入 var nodes = new[] { MakeNode("Threshold", 2), MakeNode("Blur", 0), MakeNode("Sharpen", 1) }; await _svc.ExecutePipelineAsync(nodes, _testBitmap); Assert.Equal(new[] { "Blur", "Sharpen", "Threshold" }, executionOrder); } } }