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); +}