using System.Reflection; using GFramework.Core.Abstractions.Cqrs; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Architectures; using GFramework.Core.Logging; using GFramework.Cqrs.Abstractions.Cqrs; namespace GFramework.Core.Tests.Architectures; /// /// 验证架构初始化阶段可以显式接入默认程序集之外的 CQRS handlers。 /// [TestFixture] public sealed class ArchitectureAdditionalCqrsHandlersTests { /// /// 初始化日志工厂和共享测试状态。 /// [SetUp] public void SetUp() { _previousLoggerFactoryProvider = LoggerFactoryResolver.Provider; LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); GameContext.Clear(); AdditionalAssemblyNotificationHandlerState.Reset(); } /// /// 清理测试过程中写入的共享状态。 /// [TearDown] public void TearDown() { AdditionalAssemblyNotificationHandlerState.Reset(); GameContext.Clear(); LoggerFactoryResolver.Provider = _previousLoggerFactoryProvider ?? throw new InvalidOperationException( "LoggerFactoryResolver.Provider should be captured during setup."); } private ILoggerFactoryProvider? _previousLoggerFactoryProvider; /// /// 验证显式声明的额外程序集会在初始化阶段接入当前架构容器。 /// /// The asynchronous test task. [Test] public async Task RegisterCqrsHandlersFromAssembly_Should_Register_Handlers_From_Explicit_Assembly() { var generatedAssembly = CreateGeneratedHandlerAssembly(); var architecture = CreateArchitecture(target => target.RegisterCqrsHandlersFromAssembly(generatedAssembly.Object)); await architecture.InitializeAsync(); try { await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1)); } finally { await architecture.DestroyAsync(); } } /// /// 验证不同 实例只要解析到相同程序集键,就不会向容器重复写入相同 handler 映射。 /// /// The asynchronous test task. [Test] public async Task RegisterCqrsHandlersFromAssembly_Should_Deduplicate_Repeated_Assembly_Registration() { var generatedAssemblyA = CreateGeneratedHandlerAssembly(); var generatedAssemblyB = CreateGeneratedHandlerAssembly(); var architecture = CreateArchitecture(target => { target.RegisterCqrsHandlersFromAssembly(generatedAssemblyA.Object); target.RegisterCqrsHandlersFromAssemblies([generatedAssemblyB.Object]); }); await architecture.InitializeAsync(); try { await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1)); } finally { await architecture.DestroyAsync(); } } /// /// 创建一个仅暴露程序集级 CQRS registry 元数据的 mocked Assembly。 /// 该测试替身模拟“扩展程序集已经挂接 source-generator,运行时只需显式接入该程序集”的真实路径。 /// /// 包含程序集级 handler registry 元数据的 mocked Assembly。 private static Mock CreateGeneratedHandlerAssembly() { var generatedAssembly = new Mock(); generatedAssembly .SetupGet(static assembly => assembly.FullName) .Returns("GFramework.Core.Tests.Architectures.ExplicitAdditionalHandlers, Version=1.0.0.0"); generatedAssembly .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false)) .Returns([new CqrsHandlerRegistryAttribute(typeof(AdditionalAssemblyNotificationHandlerRegistry))]); return generatedAssembly; } /// /// 创建复用现有测试架构基建的测试架构,并在注册阶段后执行额外程序集接入逻辑。 /// /// 初始化阶段执行的额外 CQRS 程序集接入逻辑。 /// 带有注册后钩子的测试架构实例。 private static SyncTestArchitecture CreateArchitecture(Action configure) { var architecture = new SyncTestArchitecture(); architecture.AddPostRegistrationHook(configure); return architecture; } } /// /// 用于验证额外程序集接入是否成功的测试通知。 /// public sealed record AdditionalAssemblyNotification : INotification; /// /// 记录模拟扩展程序集通知处理器的执行次数。 /// public static class AdditionalAssemblyNotificationHandlerState { private static int _invocationCount; /// /// 获取当前测试进程中该处理器的执行次数。 /// /// /// 该计数器通过原子读写维护,以支持 NUnit 并行执行环境中的并发访问。 /// public static int InvocationCount => Volatile.Read(ref _invocationCount); /// /// 记录一次通知处理,供测试断言显式程序集接入后的运行时行为。 /// public static void RecordInvocation() { Interlocked.Increment(ref _invocationCount); } /// /// 清理共享计数器,避免测试间相互污染。 /// public static void Reset() { Interlocked.Exchange(ref _invocationCount, 0); } } /// /// 模拟由 source-generator 为扩展程序集生成的 CQRS handler registry。 /// internal sealed class AdditionalAssemblyNotificationHandlerRegistry : ICqrsHandlerRegistry { /// /// 将扩展程序集中的通知处理器映射写入服务集合。 /// /// 目标服务集合。 /// 日志记录器。 /// /// 当 时抛出。 /// public void Register(IServiceCollection services, ILogger logger) { ArgumentNullException.ThrowIfNull(services); ArgumentNullException.ThrowIfNull(logger); services.AddTransient>(_ => CreateHandler()); logger.Debug( $"Registered CQRS handler proxy for {typeof(INotificationHandler).FullName}."); } /// /// 创建一个仅供显式程序集注册路径使用的动态通知处理器。 /// /// 用于记录通知触发次数的测试替身处理器。 private static INotificationHandler CreateHandler() { var handler = new Mock>(); handler .Setup(target => target.Handle(It.IsAny(), It.IsAny())) .Returns(() => { AdditionalAssemblyNotificationHandlerState.RecordInvocation(); return ValueTask.CompletedTask; }); return handler.Object; } }