using System.Reflection;
using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Command;
using GFramework.Core.Abstractions.Environment;
using GFramework.Core.Abstractions.Ioc;
using GFramework.Core.Abstractions.Logging;
using GFramework.Core.Abstractions.Query;
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;
namespace GFramework.Core.Tests.Architectures;
///
/// ArchitectureContext类的单元测试
/// 测试内容包括:
/// - 构造函数参数验证(所有5个参数)
/// - 构造函数空参数异常
/// - SendQuery方法 - 正常查询发送
/// - SendQuery方法 - 空查询异常
/// - SendCommand方法 - 正常命令发送
/// - SendCommand方法 - 空命令异常
/// - SendCommand_WithResult方法 - 正常命令发送
/// - SendCommand_WithResult方法 - 空命令异常
/// - SendEvent方法 - 正常事件发送
/// - SendEvent_WithInstance方法 - 正常事件发送
/// - SendEvent_WithInstance方法 - 空事件异常
/// - GetSystem方法 - 获取已注册系统
/// - GetSystem方法 - 获取未注册系统时抛出异常
/// - GetModel方法 - 获取已注册模型
/// - GetModel方法 - 获取未注册模型时抛出异常
/// - GetUtility方法 - 获取已注册工具
/// - GetUtility方法 - 获取未注册工具时抛出异常
/// - GetEnvironment方法 - 获取环境对象
///
[TestFixture]
public class ArchitectureContextTests
{
[SetUp]
public void SetUp()
{
// 初始化 LoggerFactoryResolver 以支持 MicrosoftDiContainer
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
// 直接初始化 logger 字段
var loggerField = typeof(MicrosoftDiContainer).GetField("_logger",
BindingFlags.NonPublic | BindingFlags.Instance);
loggerField?.SetValue(_container,
LoggerFactoryResolver.Provider.CreateLogger(nameof(ArchitectureContextTests)));
// 创建服务实例
_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);
_context = new ArchitectureContext(_container);
}
private AsyncQueryExecutor? _asyncQueryBus;
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
private DefaultEnvironment? _environment;
private EventBus? _eventBus;
private QueryExecutor? _queryBus;
///
/// 测试构造函数在所有参数都有效时不应抛出异常
///
[Test]
public void Constructor_Should_NotThrow_When_AllParameters_AreValid()
{
Assert.That(() => new ArchitectureContext(_container!), Throws.Nothing);
}
///
/// 测试构造函数在 container 为 null 时应抛出 ArgumentNullException
///
[Test]
public void Constructor_Should_Throw_When_Container_IsNull()
{
Assert.That(() => new ArchitectureContext(null!), Throws.ArgumentNullException);
}
///
/// 测试构造函数在Container为null时应抛出ArgumentNullException
///
[Test]
public void Constructor_Should_ThrowArgumentNullException_When_Container_IsNull()
{
Assert.That(() => new ArchitectureContext(null!),
Throws.ArgumentNullException.With.Property("ParamName").EqualTo("container"));
}
///
/// 测试SendQuery方法在查询有效时返回正确结果
///
[Test]
public void SendQuery_Should_ReturnResult_When_Query_IsValid()
{
var testQuery = new TestQueryV2 { Result = 42 };
var result = _context!.SendQuery(testQuery);
Assert.That(result, Is.EqualTo(42));
}
///
/// 测试SendQuery方法在查询为null时应抛出ArgumentNullException
///
[Test]
public void SendQuery_Should_ThrowArgumentNullException_When_Query_IsNull()
{
// 明确指定调用旧的 IQuery 重载
Assert.That(() => _context!.SendQuery((IQuery)null!),
Throws.ArgumentNullException.With.Property("ParamName").EqualTo("query"));
}
///
/// 测试SendCommand方法在命令有效时正确执行
///
[Test]
public void SendCommand_Should_ExecuteCommand_When_Command_IsValid()
{
var testCommand = new TestCommandV2();
Assert.That(() => _context!.SendCommand(testCommand), Throws.Nothing);
Assert.That(testCommand.Executed, Is.True);
}
///
/// 测试SendCommand方法在命令为null时应抛出ArgumentNullException
///
[Test]
public void SendCommand_Should_ThrowArgumentNullException_When_Command_IsNull()
{
Assert.That(() => _context!.SendCommand(null!),
Throws.ArgumentNullException.With.Property("ParamName").EqualTo("command"));
}
///
/// 测试SendCommand方法(带返回值)在命令有效时返回正确结果
///
[Test]
public void SendCommand_WithResult_Should_ReturnResult_When_Command_IsValid()
{
var testCommand = new TestCommandWithResultV2 { Result = 123 };
var result = _context!.SendCommand(testCommand);
Assert.That(result, Is.EqualTo(123));
}
///
/// 测试SendCommand方法(带返回值)在命令为null时应抛出ArgumentNullException
///
[Test]
public void SendCommand_WithResult_Should_ThrowArgumentNullException_When_Command_IsNull()
{
Assert.That(() => _context!.SendCommand((ICommand)null!),
Throws.ArgumentNullException.With.Property("ParamName").EqualTo("command"));
}
///
/// 测试SendEvent方法在事件类型有效时正确发送事件
///
[Test]
public void SendEvent_Should_SendEvent_When_EventType_IsValid()
{
var eventReceived = false;
_context!.RegisterEvent(_ => eventReceived = true);
_context.SendEvent();
Assert.That(eventReceived, Is.True);
}
///
/// 测试SendEvent方法(带实例)在事件实例有效时正确发送事件
///
[Test]
public void SendEvent_WithInstance_Should_SendEvent_When_EventInstance_IsValid()
{
var eventReceived = false;
var testEvent = new TestEventV2();
_context!.RegisterEvent(_ => eventReceived = true);
_context.SendEvent(testEvent);
Assert.That(eventReceived, Is.True);
}
///
/// 测试SendEvent方法(带实例)在事件实例为null时应抛出ArgumentNullException
///
[Test]
public void SendEvent_WithInstance_Should_ThrowArgumentNullException_When_EventInstance_IsNull()
{
Assert.That(() => _context!.SendEvent(null!),
Throws.ArgumentNullException.With.Property("ParamName").EqualTo("e"));
}
///
/// 测试GetSystem方法在系统已注册时返回注册的系统
///
[Test]
public void GetSystem_Should_ReturnRegisteredSystem_When_SystemIsRegistered()
{
var testSystem = new TestSystemV2();
_container!.RegisterPlurality(testSystem);
var result = _context!.GetSystem();
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.SameAs(testSystem));
}
///
/// 测试GetSystem方法在系统未注册时应抛出 InvalidOperationException
///
[Test]
public void GetSystem_Should_ThrowInvalidOperationException_When_SystemIsNotRegistered()
{
Assert.That(() => _context!.GetSystem(),
Throws.InvalidOperationException);
}
///
/// 测试GetModel方法在模型已注册时返回注册的模型
///
[Test]
public void GetModel_Should_ReturnRegisteredModel_When_ModelIsRegistered()
{
var testModel = new TestModelV2();
_container!.RegisterPlurality(testModel);
var result = _context!.GetModel();
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.SameAs(testModel));
}
///
/// 测试GetModel方法在模型未注册时应抛出 InvalidOperationException
///
[Test]
public void GetModel_Should_ThrowInvalidOperationException_When_ModelIsNotRegistered()
{
Assert.That(() => _context!.GetModel(),
Throws.InvalidOperationException);
}
///
/// 测试GetUtility方法在工具已注册时返回注册的工具
///
[Test]
public void GetUtility_Should_ReturnRegisteredUtility_When_UtilityIsRegistered()
{
var testUtility = new TestUtilityV2();
_container!.RegisterPlurality(testUtility);
var result = _context!.GetUtility();
Assert.That(result, Is.Not.Null);
Assert.That(result, Is.SameAs(testUtility));
}
///
/// 测试GetUtility方法在工具未注册时应抛出 InvalidOperationException
///
[Test]
public void GetUtility_Should_ThrowInvalidOperationException_When_UtilityIsNotRegistered()
{
Assert.That(() => _context!.GetUtility(),
Throws.InvalidOperationException);
}
///
/// 测试GetEnvironment方法返回环境实例
///
[Test]
public void GetEnvironment_Should_Return_EnvironmentInstance()
{
var environment = _context!.GetEnvironment();
Assert.That(environment, Is.Not.Null);
Assert.That(environment, Is.InstanceOf());
}
///
/// 测试 CQRS runtime 在并发首次访问时只会从容器解析一次。
///
[Test]
public async Task SendRequestAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
var resolutionCallCount = 0;
var runtime = new Mock(MockBehavior.Strict);
var container = new Mock(MockBehavior.Strict);
runtime.Setup(mockRuntime => mockRuntime.SendAsync(
It.IsAny(),
It.IsAny>(),
It.IsAny()))
.Returns(new ValueTask(42));
container.Setup(mockContainer => mockContainer.Get())
.Returns(() =>
{
Interlocked.Increment(ref resolutionCallCount);
allowResolutionToComplete.Wait();
return runtime.Object;
});
var context = new ArchitectureContext(container.Object);
var requests = Enumerable.Range(0, workerCount)
.Select(_ => Task.Run(async () =>
{
workersReady.Signal();
startGate.Wait();
return await context.SendRequestAsync(new TestCqrsRequest()).ConfigureAwait(false);
}))
.ToArray();
ReleaseWorkersAfterFirstResolutionAttempt(
workersReady,
startGate,
allowResolutionToComplete,
() => Volatile.Read(ref resolutionCallCount) > 0);
var responses = await Task.WhenAll(requests);
Assert.That(responses, Has.All.EqualTo(42));
Assert.That(resolutionCallCount, Is.EqualTo(1));
container.Verify(mockContainer => mockContainer.Get(), Times.Once);
runtime.Verify(
mockRuntime => mockRuntime.SendAsync(
It.IsAny(),
It.IsAny>(),
It.IsAny()),
Times.Exactly(requests.Length));
}
///
/// 测试 CQRS runtime 在并发首次发布通知时只会从容器解析一次。
///
[Test]
public async Task PublishAsync_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
var resolutionCallCount = 0;
var runtime = new Mock(MockBehavior.Strict);
var container = new Mock(MockBehavior.Strict);
runtime.Setup(mockRuntime => mockRuntime.PublishAsync(
It.IsAny(),
It.IsAny(),
It.IsAny()))
.Returns(ValueTask.CompletedTask);
container.Setup(mockContainer => mockContainer.Get())
.Returns(() =>
{
Interlocked.Increment(ref resolutionCallCount);
allowResolutionToComplete.Wait();
return runtime.Object;
});
var context = new ArchitectureContext(container.Object);
var notifications = Enumerable.Range(0, workerCount)
.Select(_ => Task.Run(async () =>
{
workersReady.Signal();
startGate.Wait();
await context.PublishAsync(new TestCqrsNotification()).ConfigureAwait(false);
}))
.ToArray();
ReleaseWorkersAfterFirstResolutionAttempt(
workersReady,
startGate,
allowResolutionToComplete,
() => Volatile.Read(ref resolutionCallCount) > 0);
await Task.WhenAll(notifications).ConfigureAwait(false);
Assert.That(resolutionCallCount, Is.EqualTo(1));
container.Verify(mockContainer => mockContainer.Get(), Times.Once);
runtime.Verify(
mockRuntime => mockRuntime.PublishAsync(
It.IsAny(),
It.IsAny(),
It.IsAny()),
Times.Exactly(notifications.Length));
}
///
/// 测试 CQRS runtime 在并发首次创建流时只会从容器解析一次。
///
[Test]
public async Task CreateStream_Should_ResolveCqrsRuntime_OnlyOnce_When_AccessedConcurrently()
{
const int workerCount = 8;
using var startGate = new ManualResetEventSlim(false);
using var allowResolutionToComplete = new ManualResetEventSlim(false);
using var workersReady = new CountdownEvent(workerCount);
var resolutionCallCount = 0;
var runtime = new Mock(MockBehavior.Strict);
var container = new Mock(MockBehavior.Strict);
runtime.Setup(mockRuntime => mockRuntime.CreateStream(
It.IsAny(),
It.IsAny(),
It.IsAny()))
.Returns(static () => CreateTestCqrsStream());
container.Setup(mockContainer => mockContainer.Get())
.Returns(() =>
{
Interlocked.Increment(ref resolutionCallCount);
allowResolutionToComplete.Wait();
return runtime.Object;
});
var context = new ArchitectureContext(container.Object);
var streamTasks = Enumerable.Range(0, workerCount)
.Select(_ => Task.Run(async () =>
{
workersReady.Signal();
startGate.Wait();
await DrainAsync(context.CreateStream(new TestCqrsStreamRequest())).ConfigureAwait(false);
}))
.ToArray();
ReleaseWorkersAfterFirstResolutionAttempt(
workersReady,
startGate,
allowResolutionToComplete,
() => Volatile.Read(ref resolutionCallCount) > 0);
await Task.WhenAll(streamTasks).ConfigureAwait(false);
Assert.That(resolutionCallCount, Is.EqualTo(1));
container.Verify(mockContainer => mockContainer.Get(), Times.Once);
runtime.Verify(
mockRuntime => mockRuntime.CreateStream(
It.IsAny(),
It.IsAny(),
It.IsAny()),
Times.Exactly(streamTasks.Length));
}
///
/// 枚举完整个测试流,确保 `CreateStream` 路径真正执行到底。
///
/// 要消费的异步流。
/// 表示消费完成的任务。
private static async Task DrainAsync(IAsyncEnumerable stream)
{
ArgumentNullException.ThrowIfNull(stream);
await foreach (var _ in stream.ConfigureAwait(false))
{
}
}
///
/// 释放并发 worker,并确保在断言失败时也能放行首次 runtime 解析。
///
/// 用于确认 worker 已就绪的倒计时器。
/// 用于同时放行 worker 的门闩。
/// 用于解除首次 runtime 解析阻塞的门闩。
/// 用于判断当前是否已观察到首次 runtime 解析尝试。
private static void ReleaseWorkersAfterFirstResolutionAttempt(
CountdownEvent workersReady,
ManualResetEventSlim startGate,
ManualResetEventSlim allowResolutionToComplete,
Func hasObservedResolutionAttempt)
{
ArgumentNullException.ThrowIfNull(workersReady);
ArgumentNullException.ThrowIfNull(startGate);
ArgumentNullException.ThrowIfNull(allowResolutionToComplete);
ArgumentNullException.ThrowIfNull(hasObservedResolutionAttempt);
var workerStartupTimeout = TimeSpan.FromSeconds(5);
var firstResolutionTimeout = TimeSpan.FromSeconds(5);
Assert.That(
workersReady.Wait(workerStartupTimeout),
Is.True,
"Expected all workers to be ready before releasing start gate.");
startGate.Set();
try
{
Assert.That(
SpinWait.SpinUntil(hasObservedResolutionAttempt, firstResolutionTimeout),
Is.True,
"Expected at least one CQRS runtime resolution attempt.");
}
finally
{
allowResolutionToComplete.Set();
}
}
///
/// 为 `CreateStream` 并发解析测试提供最小异步流。
///
/// 只包含单个元素的异步流。
private static async IAsyncEnumerable CreateTestCqrsStream()
{
yield return 42;
await Task.CompletedTask.ConfigureAwait(false);
}
private sealed class TestCqrsRequest : IRequest
{
}
private sealed record TestCqrsNotification : INotification;
private sealed record TestCqrsStreamRequest : IStreamRequest;
}