using System.Reflection; using GFramework.SourceGenerators.Cqrs; using GFramework.SourceGenerators.Tests.Core; namespace GFramework.SourceGenerators.Tests.Cqrs; /// /// 验证 CQRS 处理器注册生成器的输出与回退边界。 /// [TestFixture] public class CqrsHandlerRegistryGeneratorTests { /// /// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。 /// [Test] public async Task Generates_Assembly_Level_Cqrs_Handler_Registry() { const string source = """ using System; using System.Collections.Generic; 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 { } } namespace GFramework.Cqrs { public interface ICqrsHandlerRegistry { void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class CqrsHandlerRegistryAttribute : Attribute { public CqrsHandlerRegistryAttribute(Type registryType) { } } } namespace TestApp { using GFramework.Cqrs.Abstractions.Cqrs; public sealed record PingQuery() : IRequest; public sealed record DomainEvent() : INotification; public sealed record NumberStream() : IStreamRequest; public sealed class ZetaNotificationHandler : INotificationHandler { } public sealed class AlphaQueryHandler : IRequestHandler { } public sealed class StreamHandler : IStreamRequestHandler { } } """; const string expected = """ // #nullable enable [assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))] namespace GFramework.Generated.Cqrs; internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry : global::GFramework.Cqrs.ICqrsHandlerRegistry { public void Register(global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::GFramework.Core.Abstractions.Logging.ILogger logger) { if (services is null) throw new global::System.ArgumentNullException(nameof(services)); if (logger is null) throw new global::System.ArgumentNullException(nameof(logger)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), typeof(global::TestApp.AlphaQueryHandler)); logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler), typeof(global::TestApp.StreamHandler)); logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler."); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler), typeof(global::TestApp.ZetaNotificationHandler)); logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler."); } } """; await GeneratorTest.RunAsync( source, ("CqrsHandlerRegistry.g.cs", expected)); } /// /// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会放弃产出并让运行时回退到反射扫描。 /// [Test] public async Task Skips_Generation_When_Assembly_Contains_Private_Nested_Handler() { const string source = """ using System; 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 { } } namespace GFramework.Cqrs { public interface ICqrsHandlerRegistry { void Register(Microsoft.Extensions.DependencyInjection.IServiceCollection services, GFramework.Core.Abstractions.Logging.ILogger logger); } [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 record HiddenRequest() : IRequest; private sealed class HiddenHandler : IRequestHandler { } } public sealed class VisibleHandler : IRequestHandler { } } """; var test = new CSharpSourceGeneratorTest { TestState = { Sources = { source } }, DisabledDiagnostics = { "GF_Common_Trace_001" } }; await test.RunAsync(); } /// /// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。 /// [Test] public void Escape_String_Literal_Handles_Control_Characters() { var method = typeof(CqrsHandlerRegistryGenerator).GetMethod( "EscapeStringLiteral", BindingFlags.NonPublic | BindingFlags.Static); Assert.That(method, Is.Not.Null); const string input = "line1\r\nline2\\\""; const string expected = "line1\\r\\nline2\\\\\\\""; var escaped = method!.Invoke(null, [input]) as string; Assert.That(escaped, Is.EqualTo(expected)); } }