Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions GFramework.Core.Tests/Architectures/ArchitectureContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Architectures;
using GFramework.Core.Command;
using GFramework.Core.Cqrs;
using GFramework.Core.Environment;
using GFramework.Core.Events;
using GFramework.Core.Ioc;
using GFramework.Core.Logging;
using GFramework.Core.Query;
using GFramework.Core.Services.Modules;
using GFramework.Cqrs.Abstractions.Cqrs;

namespace GFramework.Core.Tests.Architectures;
Expand All @@ -41,9 +43,13 @@ namespace GFramework.Core.Tests.Architectures;
/// - GetUtility方法 - 获取未注册工具时抛出异常
/// - GetEnvironment方法 - 获取环境对象
/// </summary>
[NonParallelizable]
[TestFixture]
public class ArchitectureContextTests
{
/// <summary>
/// 初始化测试所需的容器与默认服务实例。
/// </summary>
[SetUp]
public void SetUp()
{
Expand Down Expand Up @@ -71,10 +77,22 @@ public void SetUp()
_container.RegisterPlurality(_queryBus);
_container.RegisterPlurality(_asyncQueryBus);
_container.RegisterPlurality(_environment);
new CqrsRuntimeModule().Register(_container);
RegisterLegacyBridgeHandlers(_container);

_context = new ArchitectureContext(_container);
}

/// <summary>
/// 释放当前测试创建的容器,并清理 legacy bridge 共享计数状态。
/// </summary>
[TearDown]
public void TearDown()
{
LegacyBridgePipelineTracker.Reset();
_container?.Dispose();
}

private AsyncQueryExecutor? _asyncQueryBus;
private CommandExecutor? _commandBus;
private MicrosoftDiContainer? _container;
Expand Down Expand Up @@ -124,6 +142,31 @@ public void SendQuery_Should_ReturnResult_When_Query_IsValid()
Assert.That(result, Is.EqualTo(42));
}

/// <summary>
/// 测试 legacy 查询通过 <see cref="ArchitectureContext" /> 发送时会进入统一 CQRS pipeline,
/// 并把当前架构上下文注入到查询对象。
/// </summary>
[Test]
public void SendQuery_Should_Bridge_Through_CqrsRuntime_And_Preserve_Context()
{
LegacyBridgePipelineTracker.Reset();
var testQuery = new LegacyArchitectureBridgeQuery();
var bridgeContext = CreateFrozenBridgeContext(out var bridgeContainer);

try
{
var result = bridgeContext.SendQuery(testQuery);

Assert.That(result, Is.EqualTo(24));
Assert.That(testQuery.ObservedContext, Is.SameAs(bridgeContext));
Assert.That(LegacyBridgePipelineTracker.InvocationCount, Is.EqualTo(1));
}
finally
{
bridgeContainer.Dispose();
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
/// 测试SendQuery方法在查询为null时应抛出ArgumentNullException
/// </summary>
Expand All @@ -146,6 +189,31 @@ public void SendCommand_Should_ExecuteCommand_When_Command_IsValid()
Assert.That(testCommand.Executed, Is.True);
}

/// <summary>
/// 测试 legacy 命令通过 <see cref="ArchitectureContext" /> 发送时会进入统一 CQRS pipeline,
/// 并把当前架构上下文注入到命令对象。
/// </summary>
[Test]
public void SendCommand_Should_Bridge_Through_CqrsRuntime_And_Preserve_Context()
{
LegacyBridgePipelineTracker.Reset();
var testCommand = new LegacyArchitectureBridgeCommand();
var bridgeContext = CreateFrozenBridgeContext(out var bridgeContainer);

try
{
bridgeContext.SendCommand(testCommand);

Assert.That(testCommand.Executed, Is.True);
Assert.That(testCommand.ObservedContext, Is.SameAs(bridgeContext));
Assert.That(LegacyBridgePipelineTracker.InvocationCount, Is.EqualTo(1));
}
finally
{
bridgeContainer.Dispose();
}
}

/// <summary>
/// 测试SendCommand方法在命令为null时应抛出ArgumentNullException
/// </summary>
Expand All @@ -168,6 +236,87 @@ public void SendCommand_WithResult_Should_ReturnResult_When_Command_IsValid()
Assert.That(result, Is.EqualTo(123));
}

/// <summary>
/// 测试 legacy 带返回值命令通过 <see cref="ArchitectureContext" /> 发送时会进入统一 CQRS pipeline,
/// 并保持原始返回值语义。
/// </summary>
[Test]
public void SendCommand_WithResult_Should_Bridge_Through_CqrsRuntime()
{
LegacyBridgePipelineTracker.Reset();
var testCommand = new LegacyArchitectureBridgeCommandWithResult();
var bridgeContext = CreateFrozenBridgeContext(out var bridgeContainer);

try
{
var result = bridgeContext.SendCommand(testCommand);

Assert.That(result, Is.EqualTo(42));
Assert.That(testCommand.ObservedContext, Is.SameAs(bridgeContext));
Assert.That(LegacyBridgePipelineTracker.InvocationCount, Is.EqualTo(1));
}
finally
{
bridgeContainer.Dispose();
}
}

/// <summary>
/// 测试 legacy 异步查询通过 <see cref="ArchitectureContext" /> 发送时也会进入统一 CQRS pipeline。
/// </summary>
[Test]
public async Task SendQueryAsync_Should_Bridge_Through_CqrsRuntime_And_Preserve_Context()
{
LegacyBridgePipelineTracker.Reset();
var testQuery = new LegacyArchitectureBridgeAsyncQuery();
var bridgeContext = CreateFrozenBridgeContext(out var bridgeContainer);

try
{
var result = await bridgeContext.SendQueryAsync(testQuery).ConfigureAwait(false);

Assert.That(result, Is.EqualTo(64));
Assert.That(testQuery.ObservedContext, Is.SameAs(bridgeContext));
Assert.That(LegacyBridgePipelineTracker.InvocationCount, Is.EqualTo(1));
}
finally
{
bridgeContainer.Dispose();
}
}

/// <summary>
/// 为需要验证统一 CQRS pipeline 的用例创建一个已冻结的最小 bridge 上下文。
/// </summary>
/// <param name="container">返回承载当前 bridge 上下文的冻结容器,供测试在 finally 中显式释放。</param>
/// <returns>能够执行 legacy bridge request 且会 materialize open-generic pipeline behavior 的上下文。</returns>
private static ArchitectureContext CreateFrozenBridgeContext(out MicrosoftDiContainer container)
{
container = new MicrosoftDiContainer();
RegisterLegacyBridgeHandlers(container);
new CqrsRuntimeModule().Register(container);
container.ExecuteServicesHook(services =>
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(LegacyBridgeTrackingPipelineBehavior<,>)));
container.Freeze();
return new ArchitectureContext(container);
}

/// <summary>
/// 把 GFramework.Core 内部的 legacy bridge handler 实例预先注册成可见的实例绑定。
/// </summary>
/// <param name="container">目标测试容器。</param>
private static void RegisterLegacyBridgeHandlers(MicrosoftDiContainer container)
{
ArgumentNullException.ThrowIfNull(container);

container.RegisterPlurality(new LegacyCommandDispatchRequestHandler());
container.RegisterPlurality(new LegacyCommandResultDispatchRequestHandler());
container.RegisterPlurality(new LegacyAsyncCommandDispatchRequestHandler());
container.RegisterPlurality(new LegacyAsyncCommandResultDispatchRequestHandler());
container.RegisterPlurality(new LegacyQueryDispatchRequestHandler());
container.RegisterPlurality(new LegacyAsyncQueryDispatchRequestHandler());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
/// 测试SendCommand方法(带返回值)在命令为null时应抛出ArgumentNullException
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
using GFramework.Core.Abstractions.Utility;
using GFramework.Core.Architectures;
using GFramework.Core.Logging;
using GFramework.Cqrs.Abstractions.Cqrs;
using Microsoft.Extensions.DependencyInjection;

namespace GFramework.Core.Tests.Architectures;

/// <summary>
/// 验证 Architecture 通过 <c>ArchitectureModules</c> 暴露出的模块安装与 CQRS 行为注册能力。
/// 这些测试覆盖模块安装回调和请求管道行为接入,确保模块管理器仍然保持可观察行为不变。
/// </summary>
[NonParallelizable]
[TestFixture]
public class ArchitectureModulesBehaviorTests
{
Expand All @@ -35,6 +38,7 @@ public void TearDown()
{
GameContext.Clear();
TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount = 0;
LegacyBridgePipelineTracker.Reset();
}

/// <summary>
Expand All @@ -47,15 +51,19 @@ public async Task InstallModule_Should_Invoke_Module_Install_With_Current_Archit
var architecture = new ModuleTestArchitecture(target => target.InstallModule(module));

await architecture.InitializeAsync();

Assert.Multiple(() =>
try
{
Assert.That(module.InstalledArchitecture, Is.SameAs(architecture));
Assert.That(module.InstallCallCount, Is.EqualTo(1));
Assert.That(architecture.Context.GetUtility<InstalledByModuleUtility>(), Is.Not.Null);
});

await architecture.DestroyAsync();
Assert.Multiple(() =>
{
Assert.That(module.InstalledArchitecture, Is.SameAs(architecture));
Assert.That(module.InstallCallCount, Is.EqualTo(1));
Assert.That(architecture.Context.GetUtility<InstalledByModuleUtility>(), Is.Not.Null);
});
}
finally
{
await architecture.DestroyAsync();
}
}

/// <summary>
Expand All @@ -68,16 +76,54 @@ public async Task RegisterCqrsPipelineBehavior_Should_Apply_Pipeline_Behavior_To
target.RegisterCqrsPipelineBehavior<TrackingPipelineBehavior<ModuleBehaviorRequest, string>>());

await architecture.InitializeAsync();
try
{
var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest());

var response = await architecture.Context.SendRequestAsync(new ModuleBehaviorRequest());

Assert.Multiple(() =>
Assert.Multiple(() =>
{
Assert.That(response, Is.EqualTo("handled"));
Assert.That(TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount, Is.EqualTo(1));
});
}
finally
{
Assert.That(response, Is.EqualTo("handled"));
Assert.That(TrackingPipelineBehavior<ModuleBehaviorRequest, string>.InvocationCount, Is.EqualTo(1));
});
await architecture.DestroyAsync();
}
}

/// <summary>
/// 验证默认架构初始化路径会自动扫描 Core 程序集里的 legacy bridge handler,
/// 使旧 <c>SendCommand</c> / <c>SendQuery</c> 入口也能进入统一 CQRS pipeline。
/// </summary>
[Test]
public async Task InitializeAsync_Should_AutoRegister_LegacyBridgeHandlers_For_Default_Core_Assemblies()
{
LegacyBridgePipelineTracker.Reset();
var architecture = new LegacyBridgeArchitecture();

await architecture.DestroyAsync();
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();
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/// <summary>
Expand All @@ -94,6 +140,27 @@ protected override void OnInitialize()
}
}

/// <summary>
/// 通过公开初始化入口注册测试 pipeline behavior 的最小架构,
/// 用于验证默认 Core 程序集扫描是否会自动接入 legacy bridge handler。
/// </summary>
private sealed class LegacyBridgeArchitecture : Architecture
{
/// <summary>
/// 在容器钩子阶段注册 open-generic pipeline behavior,
/// 以便 bridge request 走真实的架构初始化与 handler 自动扫描链路。
/// </summary>
public override Action<IServiceCollection>? Configurator => services =>
services.AddSingleton(typeof(IPipelineBehavior<,>), typeof(LegacyBridgeTrackingPipelineBehavior<,>));

/// <summary>
/// 保持空初始化,让测试只聚焦默认 CQRS 接线与 legacy bridge handler 自动发现。
/// </summary>
protected override void OnInitialize()
{
}
}

/// <summary>
/// 记录模块安装调用情况的测试模块。
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0

using GFramework.Core.Abstractions.Architectures;
using GFramework.Core.Abstractions.Query;
using GFramework.Core.Rule;

namespace GFramework.Core.Tests.Architectures;

/// <summary>
/// 用于验证 legacy 异步查询桥接时也会显式注入当前架构上下文。
/// </summary>
public sealed class LegacyArchitectureBridgeAsyncQuery : ContextAwareBase, IAsyncQuery<int>
{
/// <summary>
/// 获取执行期间观察到的上下文实例。
/// </summary>
public IArchitectureContext? ObservedContext { get; private set; }

/// <summary>
/// 执行异步查询并返回测试结果。
/// </summary>
public Task<int> DoAsync()
{
ObservedContext = ((GFramework.Core.Abstractions.Rule.IContextAware)this).GetContext();
return Task.FromResult(64);
}
}
Loading
Loading