From 5a77e2fb3347115367e3423583010ad40c4b01e0 Mon Sep 17 00:00:00 2001
From: gewuyou <95328647+GeWuYou@users.noreply.github.com>
Date: Thu, 30 Apr 2026 14:13:09 +0800
Subject: [PATCH] =?UTF-8?q?test(cqrs-tests):=20=E8=A1=A5=E5=85=85=20hidden?=
=?UTF-8?q?=20implementation=20generated=20invoker=20=E5=9B=9E=E5=BD=92?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 新增 hidden implementation request provider runtime 集成回归,验证 registrar 与 dispatcher 会继续消费 generated metadata
- 新增 hidden implementation stream provider runtime 集成回归,覆盖可见 handler interface 下的流式 dispatch 路径
- 补充对应测试替身 registry 与隐藏 handler 容器,保持现有 generated invoker 测试风格
---
...qrsGeneratedRequestInvokerProviderTests.cs | 106 ++++++++++++++++++
...GeneratedRequestInvokerProviderRegistry.cs | 93 +++++++++++++++
...nGeneratedStreamInvokerProviderRegistry.cs | 105 +++++++++++++++++
...enImplementationRequestInvokerContainer.cs | 38 +++++++
...denImplementationStreamInvokerContainer.cs | 51 +++++++++
5 files changed, 393 insertions(+)
create mode 100644 GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedRequestInvokerProviderRegistry.cs
create mode 100644 GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
create mode 100644 GFramework.Cqrs.Tests/Cqrs/HiddenImplementationRequestInvokerContainer.cs
create mode 100644 GFramework.Cqrs.Tests/Cqrs/HiddenImplementationStreamInvokerContainer.cs
diff --git a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
index b49e9d86..2d2725b2 100644
--- a/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
+++ b/GFramework.Cqrs.Tests/Cqrs/CqrsGeneratedRequestInvokerProviderTests.cs
@@ -57,6 +57,25 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Is.EqualTo([typeof(GeneratedRequestInvokerProviderRegistry)]));
}
+ ///
+ /// 验证当实现类型隐藏、但 request handler interface 仍可直接表达时,
+ /// registrar 仍会把 generated request invoker provider 注册到容器中。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Register_Generated_Request_Invoker_Provider_For_Hidden_Implementation()
+ {
+ var generatedAssembly = CreateHiddenImplementationGeneratedRequestInvokerAssembly();
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+
+ var providers = container.GetAll();
+
+ Assert.That(
+ providers.Select(static provider => provider.GetType()),
+ Is.EqualTo([typeof(HiddenImplementationGeneratedRequestInvokerProviderRegistry)]));
+ }
+
///
/// 验证 registrar 激活 generated registry 后,会把 stream invoker provider 注册到容器中。
///
@@ -75,6 +94,25 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Is.EqualTo([typeof(GeneratedStreamInvokerProviderRegistry)]));
}
+ ///
+ /// 验证当实现类型隐藏、但 stream handler interface 仍可直接表达时,
+ /// registrar 仍会把 generated stream invoker provider 注册到容器中。
+ ///
+ [Test]
+ public void RegisterHandlers_Should_Register_Generated_Stream_Invoker_Provider_For_Hidden_Implementation()
+ {
+ var generatedAssembly = CreateHiddenImplementationGeneratedStreamInvokerAssembly();
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+
+ var providers = container.GetAll();
+
+ Assert.That(
+ providers.Select(static provider => provider.GetType()),
+ Is.EqualTo([typeof(HiddenImplementationGeneratedStreamInvokerProviderRegistry)]));
+ }
+
///
/// 验证 dispatcher 在首次创建 request binding 时,会优先消费 generated request invoker provider。
///
@@ -92,6 +130,25 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(response, Is.EqualTo("generated:payload"));
}
+ ///
+ /// 验证当实现类型隐藏、但 request handler interface 仍可直接表达时,
+ /// dispatcher 仍会消费 generated request invoker descriptor。
+ ///
+ [Test]
+ public async Task SendAsync_Should_Use_Generated_Request_Invoker_For_Hidden_Implementation_When_Provider_Is_Registered()
+ {
+ var generatedAssembly = CreateHiddenImplementationGeneratedRequestInvokerAssembly();
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var response = await context.SendRequestAsync(
+ new HiddenImplementationRequestInvokerContainer.VisibleRequest("payload"));
+ Assert.That(response, Is.EqualTo("generated-hidden:payload"));
+ }
+
///
/// 验证 dispatcher 在首次创建 stream binding 时,会优先消费 generated stream invoker provider。
///
@@ -109,6 +166,25 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
Assert.That(results, Is.EqualTo([30, 31]));
}
+ ///
+ /// 验证当实现类型隐藏、但 stream handler interface 仍可直接表达时,
+ /// dispatcher 仍会消费 generated stream invoker descriptor。
+ ///
+ [Test]
+ public async Task CreateStream_Should_Use_Generated_Stream_Invoker_For_Hidden_Implementation_When_Provider_Is_Registered()
+ {
+ var generatedAssembly = CreateHiddenImplementationGeneratedStreamInvokerAssembly();
+ var container = new MicrosoftDiContainer();
+
+ CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
+ container.Freeze();
+
+ var context = new ArchitectureContext(container);
+ var results = await DrainAsync(
+ context.CreateStream(new HiddenImplementationStreamInvokerContainer.VisibleStreamRequest(3)));
+ Assert.That(results, Is.EqualTo([300, 301]));
+ }
+
///
/// 创建带有 generated request invoker registry 元数据的程序集替身。
///
@@ -139,6 +215,36 @@ internal sealed class CqrsGeneratedRequestInvokerProviderTests
return generatedAssembly;
}
+ ///
+ /// 创建带有 hidden implementation request invoker registry 元数据的程序集替身。
+ ///
+ private static Mock CreateHiddenImplementationGeneratedRequestInvokerAssembly()
+ {
+ var generatedAssembly = new Mock();
+ generatedAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Cqrs.Tests.Cqrs.HiddenGeneratedRequestInvokerAssembly, Version=1.0.0.0");
+ generatedAssembly
+ .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
+ .Returns([new CqrsHandlerRegistryAttribute(typeof(HiddenImplementationGeneratedRequestInvokerProviderRegistry))]);
+ return generatedAssembly;
+ }
+
+ ///
+ /// 创建带有 hidden implementation stream invoker registry 元数据的程序集替身。
+ ///
+ private static Mock CreateHiddenImplementationGeneratedStreamInvokerAssembly()
+ {
+ var generatedAssembly = new Mock();
+ generatedAssembly
+ .SetupGet(static assembly => assembly.FullName)
+ .Returns("GFramework.Cqrs.Tests.Cqrs.HiddenGeneratedStreamInvokerAssembly, Version=1.0.0.0");
+ generatedAssembly
+ .Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
+ .Returns([new CqrsHandlerRegistryAttribute(typeof(HiddenImplementationGeneratedStreamInvokerProviderRegistry))]);
+ return generatedAssembly;
+ }
+
///
/// 清空 registrar 静态缓存。
///
diff --git a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedRequestInvokerProviderRegistry.cs b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedRequestInvokerProviderRegistry.cs
new file mode 100644
index 00000000..d9091157
--- /dev/null
+++ b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedRequestInvokerProviderRegistry.cs
@@ -0,0 +1,93 @@
+using System.Reflection;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Core.Ioc;
+using GFramework.Core.Logging;
+using GFramework.Cqrs.Abstractions.Cqrs;
+
+namespace GFramework.Cqrs.Tests.Cqrs;
+
+///
+/// 模拟 generated registry 在实现类型隐藏、但 request handler interface 可见时,仍提供 request invoker 元数据。
+///
+internal sealed class HiddenImplementationGeneratedRequestInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsRequestInvokerProvider,
+ IEnumeratesCqrsRequestInvokerDescriptors
+{
+ private static readonly Type HandlerContractType =
+ typeof(IRequestHandler);
+
+ private static readonly CqrsRequestInvokerDescriptor Descriptor = new(
+ HandlerContractType,
+ typeof(HiddenImplementationGeneratedRequestInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsRequestInvokerDescriptorEntry DescriptorEntry = new(
+ typeof(HiddenImplementationRequestInvokerContainer.VisibleRequest),
+ typeof(string),
+ Descriptor);
+
+ ///
+ /// 通过可见 handler interface 把隐藏实现类型注册进目标服务集合,模拟 generator 的 reflected-implementation 路径。
+ ///
+ /// 承载处理器映射的服务集合。
+ /// 用于记录注册诊断的日志器。
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ var implementationType = HiddenImplementationRequestInvokerContainer.HiddenHandlerType;
+ services.AddTransient(HandlerContractType, implementationType);
+ logger.Debug(
+ $"Registered CQRS handler {implementationType.FullName} as {HandlerContractType.FullName}.");
+ }
+
+ ///
+ /// 尝试返回指定 request/response 类型对对应的 generated invoker 描述符。
+ ///
+ /// 请求运行时类型。
+ /// 响应运行时类型。
+ /// 命中时返回的描述符。
+ /// 若类型对匹配当前测试请求则返回 。
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsRequestInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(HiddenImplementationRequestInvokerContainer.VisibleRequest)
+ && responseType == typeof(string))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 返回当前 registry 暴露的全部 generated request invoker 描述符。
+ ///
+ /// 单条 hidden implementation request invoker 描述符条目。
+ public IReadOnlyList GetDescriptors()
+ {
+ return [DescriptorEntry];
+ }
+
+ ///
+ /// 模拟 generated request invoker 在隐藏实现类型场景下直接执行后的返回值。
+ ///
+ /// 当前请求处理器实例。
+ /// 当前测试请求。
+ /// 取消令牌。
+ /// 带有 hidden generated 前缀的结果,便于断言 dispatcher 命中了 generated provider 路径。
+ private static ValueTask InvokeGenerated(object handler, object request, CancellationToken cancellationToken)
+ {
+ _ = handler as IRequestHandler
+ ?? throw new InvalidOperationException("Generated invoker received an incompatible hidden handler instance.");
+ var typedRequest = (HiddenImplementationRequestInvokerContainer.VisibleRequest)request;
+ return ValueTask.FromResult($"generated-hidden:{typedRequest.Value}");
+ }
+}
diff --git a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
new file mode 100644
index 00000000..8d14cca1
--- /dev/null
+++ b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationGeneratedStreamInvokerProviderRegistry.cs
@@ -0,0 +1,105 @@
+using System.Reflection;
+using GFramework.Core.Abstractions.Logging;
+using GFramework.Core.Ioc;
+using GFramework.Cqrs.Abstractions.Cqrs;
+
+namespace GFramework.Cqrs.Tests.Cqrs;
+
+///
+/// 模拟 generated registry 在实现类型隐藏、但 stream handler interface 可见时,仍提供 stream invoker 元数据。
+///
+internal sealed class HiddenImplementationGeneratedStreamInvokerProviderRegistry :
+ ICqrsHandlerRegistry,
+ ICqrsStreamInvokerProvider,
+ IEnumeratesCqrsStreamInvokerDescriptors
+{
+ private static readonly Type HandlerContractType =
+ typeof(IStreamRequestHandler);
+
+ private static readonly CqrsStreamInvokerDescriptor Descriptor = new(
+ HandlerContractType,
+ typeof(HiddenImplementationGeneratedStreamInvokerProviderRegistry).GetMethod(
+ nameof(InvokeGenerated),
+ BindingFlags.NonPublic | BindingFlags.Static)!);
+
+ private static readonly CqrsStreamInvokerDescriptorEntry DescriptorEntry = new(
+ typeof(HiddenImplementationStreamInvokerContainer.VisibleStreamRequest),
+ typeof(int),
+ Descriptor);
+
+ ///
+ /// 通过可见 stream handler interface 把隐藏实现类型注册进目标服务集合,模拟 generator 的 reflected-implementation 路径。
+ ///
+ /// 承载处理器映射的服务集合。
+ /// 用于记录注册诊断的日志器。
+ public void Register(IServiceCollection services, ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(services);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ var implementationType = HiddenImplementationStreamInvokerContainer.HiddenHandlerType;
+ services.AddTransient(HandlerContractType, implementationType);
+ logger.Debug(
+ $"Registered CQRS handler {implementationType.FullName} as {HandlerContractType.FullName}.");
+ }
+
+ ///
+ /// 尝试返回指定 stream request/response 类型对对应的 generated invoker 描述符。
+ ///
+ /// 流式请求运行时类型。
+ /// 流式响应元素类型。
+ /// 命中时返回的描述符。
+ /// 若类型对匹配当前测试流式请求则返回 。
+ public bool TryGetDescriptor(
+ Type requestType,
+ Type responseType,
+ out CqrsStreamInvokerDescriptor? descriptor)
+ {
+ if (requestType == typeof(HiddenImplementationStreamInvokerContainer.VisibleStreamRequest)
+ && responseType == typeof(int))
+ {
+ descriptor = Descriptor;
+ return true;
+ }
+
+ descriptor = null;
+ return false;
+ }
+
+ ///
+ /// 返回当前 registry 暴露的全部 generated stream invoker 描述符。
+ ///
+ /// 单条 hidden implementation stream invoker 描述符条目。
+ public IReadOnlyList GetDescriptors()
+ {
+ return [DescriptorEntry];
+ }
+
+ ///
+ /// 模拟 generated stream invoker 在隐藏实现类型场景下直接执行后的返回值。
+ ///
+ /// 当前流式请求处理器实例。
+ /// 当前测试流式请求。
+ /// 取消令牌。
+ /// 带有 hidden generated 语义的异步流,便于断言 dispatcher 命中了 generated provider 路径。
+ private static object InvokeGenerated(object handler, object request, CancellationToken cancellationToken)
+ {
+ _ = handler as IStreamRequestHandler
+ ?? throw new InvalidOperationException("Generated stream invoker received an incompatible hidden handler instance.");
+ var typedRequest = (HiddenImplementationStreamInvokerContainer.VisibleStreamRequest)request;
+ return StreamResultsAsync(typedRequest.Start, cancellationToken);
+ }
+
+ ///
+ /// 构造供测试断言使用的固定异步流结果。
+ ///
+ private static async IAsyncEnumerable StreamResultsAsync(
+ int start,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ yield return start * 100;
+ await Task.Yield();
+ cancellationToken.ThrowIfCancellationRequested();
+ yield return start * 100 + 1;
+ }
+}
diff --git a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationRequestInvokerContainer.cs b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationRequestInvokerContainer.cs
new file mode 100644
index 00000000..0dce0e9a
--- /dev/null
+++ b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationRequestInvokerContainer.cs
@@ -0,0 +1,38 @@
+using GFramework.Cqrs.Abstractions.Cqrs;
+
+namespace GFramework.Cqrs.Tests.Cqrs;
+
+///
+/// 为 hidden implementation request invoker 回归提供“可见请求 + 隐藏实现类型”的测试替身容器。
+///
+internal static class HiddenImplementationRequestInvokerContainer
+{
+ ///
+ /// 用于验证 generated request invoker metadata 在隐藏实现类型场景下仍可被 dispatcher 消费的请求。
+ ///
+ /// 用于断言 generated 返回值的请求负载。
+ internal sealed record VisibleRequest(string Value) : IRequest;
+
+ ///
+ /// 供 registrar 通过可见 handler interface 注册、但自身保持隐藏的 request handler 实现。
+ ///
+ private sealed class HiddenHandler : IRequestHandler
+ {
+ ///
+ /// 返回 runtime 路径专用结果,便于与 generated invoker 路径区分。
+ ///
+ /// 当前测试请求。
+ /// 取消令牌。
+ /// runtime handler 生成的响应字符串。
+ public ValueTask Handle(VisibleRequest request, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ return ValueTask.FromResult($"runtime-hidden:{request.Value}");
+ }
+ }
+
+ ///
+ /// 返回当前隐藏 request handler 实现类型,供 generated registry 以反射注册语义模拟 hidden implementation 场景。
+ ///
+ internal static Type HiddenHandlerType => typeof(HiddenHandler);
+}
diff --git a/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationStreamInvokerContainer.cs b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationStreamInvokerContainer.cs
new file mode 100644
index 00000000..088eb722
--- /dev/null
+++ b/GFramework.Cqrs.Tests/Cqrs/HiddenImplementationStreamInvokerContainer.cs
@@ -0,0 +1,51 @@
+using GFramework.Cqrs.Abstractions.Cqrs;
+
+namespace GFramework.Cqrs.Tests.Cqrs;
+
+///
+/// 为 hidden implementation stream invoker 回归提供“可见请求 + 隐藏实现类型”的测试替身容器。
+///
+internal static class HiddenImplementationStreamInvokerContainer
+{
+ ///
+ /// 用于验证 generated stream invoker metadata 在隐藏实现类型场景下仍可被 dispatcher 消费的流式请求。
+ ///
+ /// 用于构造 generated stream 输出的起始值。
+ internal sealed record VisibleStreamRequest(int Start) : IStreamRequest;
+
+ ///
+ /// 供 registrar 通过可见 stream handler interface 注册、但自身保持隐藏的流式 handler 实现。
+ ///
+ private sealed class HiddenHandler : IStreamRequestHandler
+ {
+ ///
+ /// 返回 runtime 路径专用异步流,便于与 generated invoker 路径区分。
+ ///
+ /// 当前测试流式请求。
+ /// 取消令牌。
+ /// runtime handler 生成的异步流结果。
+ public IAsyncEnumerable Handle(VisibleStreamRequest request, CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(request);
+ return StreamResultsAsync(request.Start, cancellationToken);
+ }
+
+ ///
+ /// 生成用于区分 runtime 路径的固定异步流结果。
+ ///
+ private static async IAsyncEnumerable StreamResultsAsync(
+ int start,
+ [EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ yield return start;
+ await Task.Yield();
+ cancellationToken.ThrowIfCancellationRequested();
+ yield return start + 1;
+ }
+ }
+
+ ///
+ /// 返回当前隐藏 stream handler 实现类型,供 generated registry 以反射注册语义模拟 hidden implementation 场景。
+ ///
+ internal static Type HiddenHandlerType => typeof(HiddenHandler);
+}