// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using System.Reflection; using GFramework.Core.Abstractions.Architectures; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Utility; using GFramework.Core.Architectures; using GFramework.Core.Logging; using GFramework.Cqrs; using GFramework.Cqrs.Abstractions.Cqrs; using GFramework.Cqrs.Notification; using Microsoft.Extensions.DependencyInjection; namespace GFramework.Core.Tests.Architectures; /// /// 验证 Architecture 通过 ArchitectureModules 暴露出的模块安装与 CQRS 行为注册能力。 /// 这些测试覆盖模块安装回调和请求管道行为接入,确保模块管理器仍然保持可观察行为不变。 /// [NonParallelizable] [TestFixture] public class ArchitectureModulesBehaviorTests { /// /// 初始化日志工厂和全局上下文状态。 /// [SetUp] public void SetUp() { LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider(); GameContext.Clear(); AdditionalAssemblyNotificationHandlerState.Reset(); TrackingPipelineBehavior.InvocationCount = 0; TrackingStreamPipelineBehavior.InvocationCount = 0; } /// /// 清理测试过程中写入的全局上下文状态。 /// [TearDown] public void TearDown() { AdditionalAssemblyNotificationHandlerState.Reset(); GameContext.Clear(); TrackingPipelineBehavior.InvocationCount = 0; TrackingStreamPipelineBehavior.InvocationCount = 0; LegacyBridgePipelineTracker.Reset(); } /// /// 验证安装模块时会把当前架构实例传给模块,并允许模块在安装阶段注册组件。 /// [Test] public async Task InstallModule_Should_Invoke_Module_Install_With_Current_Architecture() { var module = new TrackingArchitectureModule(); var architecture = new ModuleTestArchitecture(target => target.InstallModule(module)); await architecture.InitializeAsync(); try { Assert.Multiple(() => { Assert.That(module.InstalledArchitecture, Is.SameAs(architecture)); Assert.That(module.InstallCallCount, Is.EqualTo(1)); Assert.That(architecture.Context.GetUtility(), Is.Not.Null); }); } finally { await architecture.DestroyAsync(); } } /// /// 验证注册的 CQRS 行为会参与请求管道执行。 /// [Test] public async Task RegisterCqrsPipelineBehavior_Should_Apply_Pipeline_Behavior_To_Request() { var architecture = new ModuleTestArchitecture(target => target.RegisterCqrsPipelineBehavior>()); await architecture.InitializeAsync(); try { var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest()); Assert.Multiple(() => { Assert.That(response, Is.EqualTo("handled")); Assert.That(TrackingPipelineBehavior.InvocationCount, Is.EqualTo(1)); }); } finally { await architecture.DestroyAsync(); } } /// /// 验证注册的 CQRS stream 行为会参与建流处理流程。 /// [Test] public async Task RegisterCqrsStreamPipelineBehavior_Should_Apply_Pipeline_Behavior_To_Stream_Request() { var architecture = new ModuleTestArchitecture(target => target.RegisterCqrsStreamPipelineBehavior>()); await architecture.InitializeAsync(); try { var response = await DrainAsync(architecture.Context.CreateStream(new ModuleStreamBehaviorRequest())); Assert.Multiple(() => { Assert.That(response, Is.EqualTo([7])); Assert.That( TrackingStreamPipelineBehavior.InvocationCount, Is.EqualTo(1)); }); } finally { await architecture.DestroyAsync(); } } /// /// 验证默认架构初始化路径会自动扫描 Core 程序集里的 legacy bridge handler, /// 使旧 SendCommand / SendQuery 入口也能进入统一 CQRS pipeline。 /// [Test] public async Task InitializeAsync_Should_AutoRegister_LegacyBridgeHandlers_For_Default_Core_Assemblies() { LegacyBridgePipelineTracker.Reset(); var architecture = new LegacyBridgeArchitecture(); await architecture.InitializeAsync(); try { var query = new LegacyArchitectureBridgeQuery(); var command = new LegacyArchitectureBridgeCommand(); var queryResult = architecture.Context.SendQuery(query); architecture.Context.SendCommand(command); Assert.Multiple(() => { Assert.That(queryResult, Is.EqualTo(24)); Assert.That(query.ObservedContext, Is.SameAs(architecture.Context)); Assert.That(command.Executed, Is.True); Assert.That(command.ObservedContext, Is.SameAs(architecture.Context)); Assert.That(LegacyBridgePipelineTracker.InvocationCount, Is.EqualTo(2)); }); } finally { await architecture.DestroyAsync(); } } /// /// 验证标准架构启动路径会复用通过 声明的自定义 notification publisher, /// 而不是在 创建 runtime 时提前固化默认顺序策略。 /// [Test] public async Task InitializeAsync_Should_Reuse_Custom_NotificationPublisher_From_Configurator() { var generatedAssembly = CreateGeneratedHandlerAssembly(); var architecture = new ConfiguredNotificationPublisherArchitecture(generatedAssembly.Object); await architecture.InitializeAsync(); try { var probe = architecture.Context.GetService(); await architecture.Context.PublishAsync(new AdditionalAssemblyNotification()); Assert.Multiple(() => { Assert.That(probe.WasCalled, Is.True); Assert.That(AdditionalAssemblyNotificationHandlerState.InvocationCount, Is.EqualTo(1)); }); } finally { await architecture.DestroyAsync(); } } /// /// 用于测试模块行为的最小架构实现。 /// private sealed class ModuleTestArchitecture(Action registrationAction) : Architecture { /// /// 在初始化阶段执行测试注入的模块注册逻辑。 /// protected override void OnInitialize() { registrationAction(this); } } /// /// 通过公开初始化入口注册测试 pipeline behavior 的最小架构, /// 用于验证默认 Core 程序集扫描是否会自动接入 legacy bridge handler。 /// private sealed class LegacyBridgeArchitecture : Architecture { /// /// 在容器钩子阶段注册 open-generic pipeline behavior, /// 以便 bridge request 走真实的架构初始化与 handler 自动扫描链路。 /// public override Action? Configurator => services => services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(LegacyBridgeTrackingPipelineBehavior<,>)); /// /// 保持空初始化,让测试只聚焦默认 CQRS 接线与 legacy bridge handler 自动发现。 /// protected override void OnInitialize() { } } /// /// 通过标准架构启动路径声明自定义 notification publisher 的最小架构。 /// private sealed class ConfiguredNotificationPublisherArchitecture(Assembly generatedAssembly) : Architecture { /// /// 在服务钩子阶段注册 probe 与自定义 publisher, /// 以模拟真实项目在组合根里通过 覆盖默认策略的路径。 /// public override Action? Configurator => services => { services.AddSingleton(); services.AddSingleton(); }; /// /// 在用户初始化阶段显式接入额外程序集里的 notification handler, /// 让测试聚焦“publisher 是否被复用”,而不是依赖当前测试文件自己的 handler 扫描形状。 /// protected override void OnInitialize() { RegisterCqrsHandlersFromAssembly(generatedAssembly); } } /// /// 记录模块安装调用情况的测试模块。 /// private sealed class TrackingArchitectureModule : IArchitectureModule { /// /// 获取模块安装调用次数。 /// public int InstallCallCount { get; private set; } /// /// 获取最近一次接收到的架构实例。 /// public IArchitecture? InstalledArchitecture { get; private set; } /// /// 记录安装调用,并在安装阶段注册一个工具验证调用链可用。 /// /// 目标架构实例。 public void Install(IArchitecture architecture) { InstallCallCount++; InstalledArchitecture = architecture; architecture.RegisterUtility(new InstalledByModuleUtility()); } } /// /// 由测试模块安装时注册的简单工具。 /// private sealed class InstalledByModuleUtility : IUtility { } /// /// 创建一个仅暴露程序集级 CQRS registry 元数据的 mocked Assembly。 /// 该测试替身模拟扩展程序集已经提供 notification handler registry,而架构只需在初始化时显式接入该程序集。 /// /// 包含程序集级 notification 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; } /// /// 记录自定义 notification publisher 是否真正参与了标准架构启动路径下的 publish 调用。 /// private sealed class ArchitectureNotificationPublisherProbe { /// /// 获取 probe 是否已被 publisher 标记为执行过。 /// public bool WasCalled { get; private set; } /// /// 记录当前 publish 调用已经命中了自定义 publisher。 /// public void MarkCalled() { WasCalled = true; } } /// /// 依赖容器内 probe 的自定义 notification publisher。 /// 该类型通过显式标记 + 正常转发处理器执行,验证标准架构启动路径不会把自定义策略短路成默认顺序发布器。 /// private sealed class ArchitectureTrackingNotificationPublisher( ArchitectureNotificationPublisherProbe probe) : INotificationPublisher { /// /// 记录自定义 publisher 已参与当前发布调用,并继续按处理器解析顺序转发执行。 /// public async ValueTask PublishAsync( NotificationPublishContext context, CancellationToken cancellationToken = default) where TNotification : INotification { ArgumentNullException.ThrowIfNull(context); cancellationToken.ThrowIfCancellationRequested(); probe.MarkCalled(); foreach (var handler in context.Handlers) { await context.InvokeHandlerAsync(handler, cancellationToken).ConfigureAwait(false); } } } /// /// 物化异步流为只读列表,便于断言 stream pipeline 行为的最终可观察结果。 /// /// 流元素类型。 /// 要物化的异步流。 /// 按枚举顺序收集的元素列表。 private static async Task> DrainAsync(IAsyncEnumerable stream) { var results = new List(); await foreach (var item in stream.ConfigureAwait(false)) { results.Add(item); } return results; } }