// 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)
{
}
}
}