From 172c08176cdd046da04e4bbc7959a14a9b35f0c6 Mon Sep 17 00:00:00 2001 From: gewuyou <95328647+GeWuYou@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:34:08 +0800 Subject: [PATCH] =?UTF-8?q?test(cqrs):=20=E8=A1=A5=E5=85=85=20hidden=20imp?= =?UTF-8?q?lementation=20provider=20=E5=85=83=E6=95=B0=E6=8D=AE=E6=96=AD?= =?UTF-8?q?=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 hidden implementation 但 visible handler interface 的 request provider 生成断言 - 新增 hidden implementation 但 visible handler interface 的 stream provider 生成断言 --- .../Cqrs/CqrsHandlerRegistryGeneratorTests.cs | 269 ++++++++++++++++++ 1 file changed, 269 insertions(+) diff --git a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs index c22b08e6..9b6fac2e 100644 --- a/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs +++ b/GFramework.SourceGenerators.Tests/Cqrs/CqrsHandlerRegistryGeneratorTests.cs @@ -1896,6 +1896,215 @@ public class CqrsHandlerRegistryGeneratorTests } """; + private const string HiddenImplementationRequestInvokerProviderSource = """ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + + namespace Microsoft.Extensions.DependencyInjection + { + public interface IServiceCollection { } + + public static class ServiceCollectionServiceExtensions + { + public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { } + } + } + + namespace GFramework.Core.Abstractions.Logging + { + public interface ILogger + { + void Debug(string msg); + } + } + + namespace GFramework.Cqrs.Abstractions.Cqrs + { + public interface IRequest { } + public interface INotification { } + public interface IStreamRequest { } + + public interface IRequestHandler where TRequest : IRequest + { + ValueTask Handle(TRequest request, CancellationToken cancellationToken); + } + + public interface INotificationHandler where TNotification : INotification { } + public interface IStreamRequestHandler where TRequest : IStreamRequest { } + } + + namespace GFramework.Cqrs + { + public interface ICqrsHandlerRegistry + { + void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); + } + + public interface ICqrsRequestInvokerProvider + { + bool TryGetDescriptor(Type requestType, Type responseType, out CqrsRequestInvokerDescriptor? descriptor); + } + + public interface IEnumeratesCqrsRequestInvokerDescriptors + { + IReadOnlyList GetDescriptors(); + } + + public sealed class CqrsRequestInvokerDescriptor + { + public CqrsRequestInvokerDescriptor(Type handlerType, MethodInfo invokerMethod) { } + } + + public sealed class CqrsRequestInvokerDescriptorEntry + { + public CqrsRequestInvokerDescriptorEntry(Type requestType, Type responseType, CqrsRequestInvokerDescriptor descriptor) + { + RequestType = requestType; + ResponseType = responseType; + Descriptor = descriptor; + } + + public Type RequestType { get; } + + public Type ResponseType { get; } + + public CqrsRequestInvokerDescriptor Descriptor { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class CqrsHandlerRegistryAttribute : Attribute + { + public CqrsHandlerRegistryAttribute(Type registryType) { } + } + } + + namespace TestApp + { + using GFramework.Cqrs.Abstractions.Cqrs; + + public sealed record VisibleRequest() : IRequest; + + public sealed class Container + { + private sealed class HiddenHandler : IRequestHandler + { + public ValueTask Handle(VisibleRequest request, CancellationToken cancellationToken) + { + return ValueTask.FromResult(string.Empty); + } + } + } + } + """; + + private const string HiddenImplementationStreamInvokerProviderSource = """ + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + + namespace Microsoft.Extensions.DependencyInjection + { + public interface IServiceCollection { } + + public static class ServiceCollectionServiceExtensions + { + public static void AddTransient(IServiceCollection services, Type serviceType, Type implementationType) { } + } + } + + namespace GFramework.Core.Abstractions.Logging + { + public interface ILogger + { + void Debug(string msg); + } + } + + namespace GFramework.Cqrs.Abstractions.Cqrs + { + public interface IRequest { } + public interface INotification { } + public interface IStreamRequest { } + + public interface IRequestHandler where TRequest : IRequest { } + public interface INotificationHandler where TNotification : INotification { } + + public interface IStreamRequestHandler where TRequest : IStreamRequest + { + IAsyncEnumerable Handle(TRequest request, CancellationToken cancellationToken); + } + } + + namespace GFramework.Cqrs + { + public interface ICqrsHandlerRegistry + { + void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); + } + + public interface ICqrsStreamInvokerProvider + { + bool TryGetDescriptor(Type requestType, Type responseType, out CqrsStreamInvokerDescriptor? descriptor); + } + + public interface IEnumeratesCqrsStreamInvokerDescriptors + { + IReadOnlyList GetDescriptors(); + } + + public sealed class CqrsStreamInvokerDescriptor + { + public CqrsStreamInvokerDescriptor(Type handlerType, MethodInfo invokerMethod) { } + } + + public sealed class CqrsStreamInvokerDescriptorEntry + { + public CqrsStreamInvokerDescriptorEntry(Type requestType, Type responseType, CqrsStreamInvokerDescriptor descriptor) + { + RequestType = requestType; + ResponseType = responseType; + Descriptor = descriptor; + } + + public Type RequestType { get; } + + public Type ResponseType { get; } + + public CqrsStreamInvokerDescriptor Descriptor { get; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class CqrsHandlerRegistryAttribute : Attribute + { + public CqrsHandlerRegistryAttribute(Type registryType) { } + } + } + + namespace TestApp + { + using GFramework.Cqrs.Abstractions.Cqrs; + + public sealed record VisibleStream() : IStreamRequest; + + public sealed class Container + { + private sealed class HiddenHandler : IStreamRequestHandler + { + public async IAsyncEnumerable Handle(VisibleStream request, CancellationToken cancellationToken) + { + yield return 1; + await Task.CompletedTask; + } + } + } + } + """; + /// /// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。 /// @@ -2493,6 +2702,36 @@ public class CqrsHandlerRegistryGeneratorTests }); } + /// + /// 验证当 handler 实现类型隐藏、但 request handler interface 仍可见时, + /// 生成器仍会发射 request invoker provider 元数据,而不是因为实现类型不可直接引用而整体退回反射路径。 + /// + [Test] + public void Emits_Request_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface() + { + var generatedSource = RunGenerator(HiddenImplementationRequestInvokerProviderSource); + + Assert.Multiple(() => + { + Assert.That( + generatedSource, + Does.Contain( + "internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry, global::GFramework.Cqrs.ICqrsRequestInvokerProvider, global::GFramework.Cqrs.IEnumeratesCqrsRequestInvokerDescriptors")); + Assert.That( + generatedSource, + Does.Contain( + "new global::GFramework.Cqrs.CqrsRequestInvokerDescriptorEntry(typeof(global::TestApp.VisibleRequest), typeof(string),")); + Assert.That( + generatedSource, + Does.Contain( + "new global::GFramework.Cqrs.CqrsRequestInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeRequestHandler0), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!)")); + Assert.That( + generatedSource, + Does.Contain( + "var typedHandler = (global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler)handler;")); + }); + } + /// /// 验证当 runtime 暴露 stream invoker provider 契约时,生成器会让 generated registry 同时发射 /// stream invoker 描述符与对应的开放静态 invoker 方法。 @@ -2552,6 +2791,36 @@ public class CqrsHandlerRegistryGeneratorTests }); } + /// + /// 验证当 handler 实现类型隐藏、但 stream handler interface 仍可见时, + /// 生成器仍会发射 stream invoker provider 元数据,而不是放弃生成稳定的 generated invoker 桥接。 + /// + [Test] + public void Emits_Stream_Invoker_Provider_Metadata_For_Hidden_Implementation_With_Visible_Handler_Interface() + { + var generatedSource = RunGenerator(HiddenImplementationStreamInvokerProviderSource); + + Assert.Multiple(() => + { + Assert.That( + generatedSource, + Does.Contain( + "internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry, global::GFramework.Cqrs.ICqrsStreamInvokerProvider, global::GFramework.Cqrs.IEnumeratesCqrsStreamInvokerDescriptors")); + Assert.That( + generatedSource, + Does.Contain( + "new global::GFramework.Cqrs.CqrsStreamInvokerDescriptorEntry(typeof(global::TestApp.VisibleStream), typeof(int),")); + Assert.That( + generatedSource, + Does.Contain( + "new global::GFramework.Cqrs.CqrsStreamInvokerDescriptor(typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler), typeof(__GFrameworkGeneratedCqrsHandlerRegistry).GetMethod(nameof(InvokeStreamHandler0), global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Static)!)")); + Assert.That( + generatedSource, + Does.Contain( + "var typedHandler = (global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler)handler;")); + }); + } + /// /// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。 ///