GFramework/GFramework.Cqrs.Tests/Cqrs/ArchitectureContextComprehensiveTests.cs
gewuyou ff553977e3 chore(license): 补齐 Apache-2.0 文件头治理
- 新增许可证文件头检查与修复脚本

- 补充维护者手动修复 PR 工作流和 CI 校验

- 更新贡献指南中的文件头说明

- 补齐仓库维护源码和配置文件的许可证声明
2026-05-03 19:39:49 +08:00

726 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Events;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Query;
using GFramework.Cqrs.Abstractions.Cqrs;
using ICommand = GFramework.Core.Abstractions.Command.ICommand;
namespace GFramework.Cqrs.Tests.Cqrs;
/// <summary>
/// 覆盖 <see cref="ArchitectureContext" /> 在 CQRS 请求、通知、流式处理和传统总线共存场景下的综合行为。
/// </summary>
[TestFixture]
public sealed class ArchitectureContextComprehensiveTests
{
/// <summary>
/// 测试初始化方法,在每个测试方法执行前运行。
/// 负责初始化日志工厂、依赖注入容器、自有 CQRS 处理器以及各种总线服务。
/// </summary>
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(ArchitectureContextComprehensiveTests)));
// 注册传统 CQRS 基础服务,验证其与 ArchitectureContext 请求管线可以共存。
_eventBus = new EventBus();
_commandBus = new CommandExecutor();
_queryBus = new QueryExecutor();
_asyncQueryBus = new AsyncQueryExecutor();
_environment = new DefaultEnvironment();
_container.RegisterPlurality(_eventBus);
_container.RegisterPlurality(_commandBus);
_container.RegisterPlurality(_queryBus);
_container.RegisterPlurality(_asyncQueryBus);
_container.RegisterPlurality(_environment);
CqrsTestRuntime.RegisterHandlers(
_container,
typeof(ArchitectureContextComprehensiveTests).Assembly,
typeof(ArchitectureContext).Assembly);
_container.Freeze();
_context = new ArchitectureContext(_container);
}
/// <summary>
/// 测试清理方法,在每个测试方法执行后运行。
/// 负责释放容器和上下文资源。
/// </summary>
[TearDown]
public void TearDown()
{
_context = null;
_container = null;
_eventBus = null;
_commandBus = null;
_queryBus = null;
_asyncQueryBus = null;
_environment = null;
}
private AsyncQueryExecutor? _asyncQueryBus;
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
private DefaultEnvironment? _environment;
private EventBus? _eventBus;
private QueryExecutor? _queryBus;
/// <summary>
/// 测试SendRequestAsync方法在请求有效时返回结果
/// </summary>
[Test]
public async Task SendRequestAsync_Should_ReturnResult_When_Request_IsValid()
{
var testRequest = new TestRequest { Value = 42 };
var result = await _context!.SendRequestAsync(testRequest).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(42));
}
/// <summary>
/// 测试SendRequestAsync方法在请求为null时抛出ArgumentNullException
/// </summary>
[Test]
public void SendRequestAsync_Should_ThrowArgumentNullException_When_Request_IsNull()
{
Assert.ThrowsAsync<ArgumentNullException>(async () =>
await _context!.SendRequestAsync<int>(null!).ConfigureAwait(false));
}
/// <summary>
/// 测试SendRequest方法在请求有效时返回结果
/// </summary>
[Test]
public void SendRequest_Should_ReturnResult_When_Request_IsValid()
{
var testRequest = new TestRequest { Value = 123 };
var result = _context!.SendRequest(testRequest);
Assert.That(result, Is.EqualTo(123));
}
/// <summary>
/// 测试PublishAsync方法在通知有效时发布通知
/// </summary>
[Test]
public async Task PublishAsync_Should_PublishNotification_When_Notification_IsValid()
{
TestNotificationHandler.LastReceivedMessage = null;
var notification = new TestNotification { Message = "test" };
await _context!.PublishAsync(notification).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
Assert.That(TestNotificationHandler.LastReceivedMessage, Is.EqualTo("test"));
}
/// <summary>
/// 测试CreateStream方法在流请求有效时返回流
/// </summary>
[Test]
public async Task CreateStream_Should_ReturnStream_When_StreamRequest_IsValid()
{
var testStreamRequest = new TestStreamRequest { Values = [1, 2, 3, 4, 5] };
var stream = _context!.CreateStream(testStreamRequest);
var results = new List<int>();
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
Assert.That(results, Is.EqualTo(new[] { 1, 2, 3, 4, 5 }));
}
/// <summary>
/// 测试SendAsync方法无返回值命令在命令有效时执行
/// </summary>
[Test]
public async Task SendAsync_CommandWithoutResult_Should_Execute_When_Command_IsValid()
{
var testCommand = new TestCommand { ShouldExecute = true };
await _context!.SendAsync(testCommand).ConfigureAwait(false);
Assert.That(testCommand.Executed, Is.True);
}
/// <summary>
/// 测试SendAsync方法带返回值命令在命令有效时返回结果
/// </summary>
[Test]
public async Task SendAsync_CommandWithResult_Should_ReturnResult_When_Command_IsValid()
{
var testCommand = new TestCommandWithResult { ResultValue = 42 };
var result = await _context!.SendAsync(testCommand).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(42));
}
/// <summary>
/// 测试GetService方法使用缓存
/// </summary>
[Test]
public void GetService_Should_Use_Cache()
{
var firstResult = _context!.GetService<IEventBus>();
Assert.That(firstResult, Is.Not.Null);
Assert.That(firstResult, Is.SameAs(_eventBus));
var secondResult = _context.GetService<IEventBus>();
Assert.That(secondResult, Is.SameAs(firstResult));
}
/// <summary>
/// 测试未注册的 CQRS handler 时抛出 InvalidOperationException
/// </summary>
[Test]
public void Unregistered_Cqrs_Handler_Should_Throw_InvalidOperationException()
{
var containerWithoutHandlers = new MicrosoftDiContainer();
containerWithoutHandlers.Freeze();
var contextWithoutHandlers = new ArchitectureContext(containerWithoutHandlers);
var testRequest = new TestRequest { Value = 42 };
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await contextWithoutHandlers.SendRequestAsync(testRequest).ConfigureAwait(false));
}
/// <summary>
/// 测试多个通知处理器都被调用
/// </summary>
[Test]
public async Task Multiple_Notification_Handlers_Should_All_Be_Invoked()
{
// 重置静态字段
TestNotificationHandler.LastReceivedMessage = null;
TestNotificationHandler2.LastReceivedMessage = null;
TestNotificationHandler3.LastReceivedMessage = null;
var notification = new TestNotification { Message = "multi-handler test" };
await _context!.PublishAsync(notification).ConfigureAwait(false);
await Task.Delay(100).ConfigureAwait(false);
// 验证所有处理器都被调用
Assert.That(TestNotificationHandler.LastReceivedMessage, Is.EqualTo("multi-handler test"));
Assert.That(TestNotificationHandler2.LastReceivedMessage, Is.EqualTo("multi-handler test"));
Assert.That(TestNotificationHandler3.LastReceivedMessage, Is.EqualTo("multi-handler test"));
}
/// <summary>
/// 测试CancellationToken取消长时间运行的请求
/// </summary>
[Test]
public async Task CancellationToken_Should_Cancel_Long_Running_Request()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(50));
var longRequest = new TestLongRunningRequest { DelayMs = 1000 };
// 应该在50ms后被取消
Assert.ThrowsAsync<TaskCanceledException>(async () =>
await _context!.SendRequestAsync(longRequest, cts.Token).ConfigureAwait(false));
}
/// <summary>
/// 测试CancellationToken取消流请求
/// </summary>
[Test]
public async Task CancellationToken_Should_Cancel_Stream_Request()
{
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(100));
var longStreamRequest = new TestLongStreamRequest { ItemCount = 1000 };
var stream = _context!.CreateStream(longStreamRequest, cts.Token);
var results = new List<int>();
// 流应该在100ms后被取消TaskCanceledException 继承自 OperationCanceledException
Assert.CatchAsync<OperationCanceledException>(async () =>
{
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
});
// 验证只处理了部分数据
Assert.That(results.Count, Is.LessThan(1000));
}
/// <summary>
/// 测试并发 CQRS 请求不会相互干扰
/// </summary>
[Test]
public async Task Concurrent_Cqrs_Requests_Should_Not_Interfere()
{
const int requestCount = 10;
var tasks = new List<Task<int>>();
// 并发发送多个请求
for (int i = 0; i < requestCount; i++)
{
var request = new TestRequest { Value = i };
tasks.Add(_context!.SendRequestAsync(request).AsTask());
}
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
// 验证所有结果都正确返回
Assert.That(results.Length, Is.EqualTo(requestCount));
Assert.That(results.OrderBy(x => x), Is.EqualTo(Enumerable.Range(0, requestCount)));
}
/// <summary>
/// 测试处理器异常被正确传播
/// </summary>
[Test]
public async Task Handler_Exception_Should_Be_Propagated()
{
var faultyRequest = new TestFaultyRequest();
Assert.ThrowsAsync<InvalidOperationException>(async () =>
await _context!.SendRequestAsync(faultyRequest).ConfigureAwait(false));
}
/// <summary>
/// 测试多个命令处理器可以修改同一对象
/// </summary>
[Test]
public async Task Multiple_Command_Handlers_Can_Modify_Same_Object()
{
var sharedData = new SharedData();
var command1 = new TestModifyDataCommand { Data = sharedData, Value = 10 };
var command2 = new TestModifyDataCommand { Data = sharedData, Value = 20 };
await _context!.SendAsync(command1).ConfigureAwait(false);
await _context.SendAsync(command2).ConfigureAwait(false);
// 验证数据被正确修改
Assert.That(sharedData.Value, Is.EqualTo(30)); // 10 + 20
}
/// <summary>
/// 测试通知顺序被保留
/// </summary>
[Test]
public async Task Notification_Ordering_Should_Be_Preserved()
{
var receivedOrder = new List<string>();
TestOrderedNotificationHandler.ReceivedMessages = receivedOrder;
var notifications = new[]
{
new TestOrderedNotification { Order = 1, Message = "First" },
new TestOrderedNotification { Order = 2, Message = "Second" },
new TestOrderedNotification { Order = 3, Message = "Third" }
};
foreach (var notification in notifications)
{
await _context!.PublishAsync(notification).ConfigureAwait(false);
}
await Task.Delay(200).ConfigureAwait(false); // 等待所有处理完成
// 验证接收顺序与发送顺序一致
Assert.That(receivedOrder.Count, Is.EqualTo(3));
Assert.That(receivedOrder[0], Is.EqualTo("First"));
Assert.That(receivedOrder[1], Is.EqualTo("Second"));
Assert.That(receivedOrder[2], Is.EqualTo("Third"));
}
/// <summary>
/// 测试流请求带过滤功能
/// </summary>
[Test]
public async Task Stream_Request_With_Filtering()
{
var filterRequest = new TestFilterStreamRequest
{
Values = Enumerable.Range(1, 10).ToArray(),
FilterEven = true
};
var stream = _context!.CreateStream(filterRequest);
var results = new List<int>();
await foreach (var item in stream.ConfigureAwait(false))
{
results.Add(item);
}
// 验证只返回偶数
Assert.That(results.All(x => x % 2 == 0), Is.True);
Assert.That(results, Is.EqualTo(new[] { 2, 4, 6, 8, 10 }));
}
/// <summary>
/// 测试请求验证使用Behaviors
/// </summary>
[Test]
public async Task Request_Validation_With_Behaviors()
{
var invalidCommand = new TestValidatedCommand { Name = "" }; // 无效:空字符串
Assert.ThrowsAsync<ArgumentException>(async () =>
await _context!.SendAsync(invalidCommand).ConfigureAwait(false));
}
/// <summary>
/// 测试 CQRS 性能基准
/// </summary>
[Test]
public async Task Performance_Benchmark_For_Cqrs()
{
const int iterations = 1000;
var stopwatch = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
{
var request = new TestRequest { Value = i };
var result = await _context!.SendRequestAsync(request).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(i));
}
stopwatch.Stop();
var avgTime = stopwatch.ElapsedMilliseconds / (double)iterations;
// 验证性能在合理范围内平均每个请求不超过10ms
Assert.That(avgTime, Is.LessThan(10.0));
Console.WriteLine($"Average time per request: {avgTime:F2}ms");
}
/// <summary>
/// 测试自有 CQRS 和传统 CQRS 可以共存
/// </summary>
[Test]
public async Task Cqrs_And_Legacy_CQRS_Can_Coexist()
{
// 使用传统方式
var legacyCommand = new TestLegacyCommand();
_context!.SendCommand(legacyCommand);
Assert.That(legacyCommand.Executed, Is.True);
// 使用自有 CQRS 方式
var cqrsCommand = new TestCommandWithResult { ResultValue = 999 };
var result = await _context.SendAsync(cqrsCommand).ConfigureAwait(false);
Assert.That(result, Is.EqualTo(999));
// 验证两者可以同时工作
Assert.That(legacyCommand.Executed, Is.True);
Assert.That(result, Is.EqualTo(999));
}
#region ArchitectureContext CQRS Test Helpers
private sealed record TestLongRunningRequest : IRequest<string>
{
public int DelayMs { get; init; }
}
private sealed class TestLongRunningRequestHandler : IRequestHandler<TestLongRunningRequest, string>
{
public async ValueTask<string> Handle(TestLongRunningRequest request, CancellationToken cancellationToken)
{
await Task.Delay(request.DelayMs, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return "Completed";
}
}
private sealed record TestLongStreamRequest : IStreamRequest<int>
{
public int ItemCount { get; init; }
}
private sealed class TestLongStreamRequestHandler : IStreamRequestHandler<TestLongStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestLongStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
for (int i = 0; i < request.ItemCount; i++)
{
cancellationToken.ThrowIfCancellationRequested();
yield return i;
await Task.Delay(10, cancellationToken).ConfigureAwait(false); // 模拟处理延迟
}
}
}
private sealed record TestFaultyRequest : IRequest<string>;
private sealed class TestFaultyRequestHandler : IRequestHandler<TestFaultyRequest, string>
{
public ValueTask<string> Handle(TestFaultyRequest request, CancellationToken cancellationToken)
{
throw new InvalidOperationException("Handler failed intentionally");
}
}
private sealed class SharedData
{
public int Value { get; set; }
}
private sealed record TestModifyDataCommand : IRequest<Unit>
{
public SharedData Data { get; init; } = null!;
public int Value { get; init; }
}
private sealed class TestModifyDataCommandHandler : IRequestHandler<TestModifyDataCommand, Unit>
{
public ValueTask<Unit> Handle(TestModifyDataCommand request, CancellationToken cancellationToken)
{
request.Data.Value += request.Value;
return ValueTask.FromResult(Unit.Value);
}
}
private sealed record TestCachingQuery : IRequest<string>
{
public string Key { get; init; } = string.Empty;
public IDictionary<string, string> Cache { get; init; } = new Dictionary<string, string>(StringComparer.Ordinal);
}
private sealed class TestCachingQueryHandler : IRequestHandler<TestCachingQuery, string>
{
public ValueTask<string> Handle(TestCachingQuery request, CancellationToken cancellationToken)
{
if (request.Cache.TryGetValue(request.Key, out var cachedValue))
{
return new ValueTask<string>(cachedValue);
}
var newValue = $"Value_for_{request.Key}";
request.Cache[request.Key] = newValue;
return new ValueTask<string>(newValue);
}
}
private sealed record TestOrderedNotification : INotification
{
public int Order { get; init; }
public string Message { get; init; } = string.Empty;
}
private sealed class TestOrderedNotificationHandler : INotificationHandler<TestOrderedNotification>
{
public static ICollection<string> ReceivedMessages { get; set; } = new List<string>();
public ValueTask Handle(TestOrderedNotification notification, CancellationToken cancellationToken)
{
ReceivedMessages.Add(notification.Message);
return ValueTask.CompletedTask;
}
}
// 额外的通知处理器用于验证多处理器通知分发场景。
private sealed class TestNotificationHandler2 : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
private sealed class TestNotificationHandler3 : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
private sealed record TestFilterStreamRequest : IStreamRequest<int>
{
public int[] Values { get; init; } = [];
public bool FilterEven { get; init; }
}
private sealed class TestFilterStreamRequestHandler : IStreamRequestHandler<TestFilterStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestFilterStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (var value in request.Values)
{
cancellationToken.ThrowIfCancellationRequested();
if (request.FilterEven && value % 2 != 0)
continue;
yield return value;
await Task.Yield();
}
}
}
private sealed record TestValidatedCommand : IRequest<Unit>
{
public string Name { get; init; } = string.Empty;
}
private sealed class TestValidatedCommandHandler : IRequestHandler<TestValidatedCommand, Unit>
{
public ValueTask<Unit> Handle(TestValidatedCommand request, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ArgumentException("Name cannot be empty.", nameof(request));
}
return ValueTask.FromResult(Unit.Value);
}
}
// 传统命令用于验证旧命令总线与 ArchitectureContext 的 CQRS API 可以同时工作。
private sealed class TestLegacyCommand : ICommand
{
public bool Executed { get; private set; }
public void Execute()
{
Executed = true;
}
public void SetContext(IArchitectureContext context)
{
// 该测试只验证传统命令是否被执行,不依赖上下文回填。
}
public IArchitectureContext GetContext()
{
return null!;
}
}
#endregion
#region ArchitectureContext CQRS Runtime Types
private sealed record TestRequest : IRequest<int>
{
public int Value { get; init; }
}
private sealed record TestCommand : IRequest<Unit>
{
public bool ShouldExecute { get; init; }
public bool Executed { get; set; }
}
private sealed record TestCommandWithResult : IRequest<int>
{
public int ResultValue { get; init; }
}
private sealed record TestQuery : IRequest<string>
{
public string QueryResult { get; init; } = string.Empty;
}
private sealed record TestNotification : INotification
{
public string Message { get; init; } = string.Empty;
}
private sealed record TestStreamRequest : IStreamRequest<int>
{
public int[] Values { get; init; } = [];
}
private sealed class TestRequestHandler : IRequestHandler<TestRequest, int>
{
public ValueTask<int> Handle(TestRequest request, CancellationToken cancellationToken)
{
return new ValueTask<int>(request.Value);
}
}
private sealed class TestCommandHandler : IRequestHandler<TestCommand, Unit>
{
public ValueTask<Unit> Handle(TestCommand request, CancellationToken cancellationToken)
{
if (request.ShouldExecute)
{
request.Executed = true;
}
return ValueTask.FromResult(Unit.Value);
}
}
private sealed class TestCommandWithResultHandler : IRequestHandler<TestCommandWithResult, int>
{
public ValueTask<int> Handle(TestCommandWithResult request, CancellationToken cancellationToken)
{
return new ValueTask<int>(request.ResultValue);
}
}
private sealed class TestQueryHandler : IRequestHandler<TestQuery, string>
{
public ValueTask<string> Handle(TestQuery request, CancellationToken cancellationToken)
{
return new ValueTask<string>(request.QueryResult);
}
}
private sealed class TestNotificationHandler : INotificationHandler<TestNotification>
{
public static string? LastReceivedMessage { get; set; }
public ValueTask Handle(TestNotification notification, CancellationToken cancellationToken)
{
LastReceivedMessage = notification.Message;
return ValueTask.CompletedTask;
}
}
private sealed class TestStreamRequestHandler : IStreamRequestHandler<TestStreamRequest, int>
{
public async IAsyncEnumerable<int> Handle(
TestStreamRequest request,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (var value in request.Values)
{
cancellationToken.ThrowIfCancellationRequested();
yield return value;
await Task.Yield();
}
}
}
#endregion
}