// Copyright (c) 2026 GeWuYou // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Coroutine; using GFramework.Core.Abstractions.Rule; using GFramework.Core.Coroutine.Extensions; using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Cqrs.Tests.Coroutine; /// /// 的单元测试类。 /// 验证新的 CQRS 协程扩展直接走框架内建 CQRS runtime, /// 并确保协程对命令调度异常的传播行为保持稳定。 /// [TestFixture] public class CqrsCoroutineExtensionsTests { /// /// 验证SendCommandCoroutine应该返回IEnumerator /// [Test] public void SendCommandCoroutine_Should_Return_IEnumerator_Of_YieldInstruction() { var command = new TestCommand("Test"); var contextAware = new TestContextAware(); contextAware.MockContext .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(ValueTask.CompletedTask); var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); Assert.That(coroutine, Is.InstanceOf>()); } /// /// 验证 SendCommandCoroutine 在底层命令调度失败时会重新抛出原始异常。 /// [Test] public void SendCommandCoroutine_Should_Rethrow_Inner_Exception_When_Command_Fails() { var command = new TestCommand("Test"); var contextAware = new TestContextAware(); var expectedException = new InvalidOperationException("Command failed."); contextAware.MockContext .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(new ValueTask(Task.FromException(expectedException))); var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); Assert.That(coroutine.MoveNext(), Is.True); var exception = Assert.Throws(() => coroutine.MoveNext()); Assert.That(exception, Is.SameAs(expectedException)); } /// /// 验证 SendCommandCoroutine 在提供错误回调时也会传递解包后的原始异常, /// 避免回调路径暴露 。 /// [Test] public void SendCommandCoroutine_Should_Forward_Inner_Exception_To_Error_Handler() { var command = new TestCommand("Test"); var contextAware = new TestContextAware(); var expectedException = new InvalidOperationException("Command failed."); Exception? capturedException = null; contextAware.MockContext .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(new ValueTask(Task.FromException(expectedException))); var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine( contextAware, command, exception => capturedException = exception); Assert.That(coroutine.MoveNext(), Is.True); Assert.That(coroutine.MoveNext(), Is.False); Assert.That(capturedException, Is.SameAs(expectedException)); } /// /// 验证 SendCommandCoroutine 在底层命令被取消且未提供错误回调时会抛出取消异常。 /// [Test] public void SendCommandCoroutine_Should_Throw_TaskCanceledException_When_Command_Is_Canceled() { var command = new TestCommand("Test"); var contextAware = new TestContextAware(); using var cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.Cancel(); contextAware.MockContext .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token))); var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine(contextAware, command); Assert.That(coroutine.MoveNext(), Is.True); Assert.Throws(() => coroutine.MoveNext()); } /// /// 验证 SendCommandCoroutine 在底层命令被取消且提供错误回调时会把取消异常转发给回调。 /// [Test] public void SendCommandCoroutine_Should_Forward_TaskCanceledException_To_Error_Handler_When_Command_Is_Canceled() { var command = new TestCommand("Test"); var contextAware = new TestContextAware(); using var cancellationTokenSource = new CancellationTokenSource(); Exception? capturedException = null; cancellationTokenSource.Cancel(); contextAware.MockContext .Setup(ctx => ctx.SendAsync(command, It.IsAny())) .Returns(new ValueTask(Task.FromCanceled(cancellationTokenSource.Token))); var coroutine = CqrsCoroutineExtensions.SendCommandCoroutine( contextAware, command, exception => capturedException = exception); Assert.That(coroutine.MoveNext(), Is.True); Assert.That(coroutine.MoveNext(), Is.False); Assert.That(capturedException, Is.TypeOf()); } /// /// 测试用的简单命令类 /// private sealed record TestCommand(string Data) : IRequest; /// /// 上下文感知基类的模拟实现 /// private sealed class TestContextAware : IContextAware { /// /// 提供可配置的架构上下文 Mock。 /// public Mock MockContext { get; } = new(); /// /// 获取当前架构上下文。 /// /// 用于 CQRS 调用的架构上下文实例。 public IArchitectureContext GetContext() { return MockContext.Object; } /// /// 设置架构上下文。 /// /// 要设置的架构上下文。 public void SetContext(IArchitectureContext context) { } } }