using System.Reflection; using GFramework.Cqrs.SourceGenerators.Cqrs; using GFramework.SourceGenerators.Tests.Core; namespace GFramework.SourceGenerators.Tests.Cqrs; /// /// 验证 CQRS 处理器注册生成器的输出与回退边界。 /// [TestFixture] public class CqrsHandlerRegistryGeneratorTests { private const string HiddenNestedHandlerSelfRegistrationSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } } } 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 { } } """; private const string HiddenNestedHandlerSelfRegistrationExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = registryAssembly.GetType("TestApp.Container+HiddenHandler", throwOnError: false, ignoreCase: false); if (implementationType0 is not null) { var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); if (serviceType0_0Argument0 is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, typeof(string)); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), typeof(global::TestApp.VisibleHandler)); logger.Debug("Registered CQRS handler TestApp.VisibleHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } """; private const string HiddenImplementationDirectInterfaceRegistrationSource = """ 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 class HiddenHandler : IRequestHandler { } } } """; private const string HiddenImplementationDirectInterfaceRegistrationExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = registryAssembly.GetType("TestApp.Container+HiddenHandler", throwOnError: false, ignoreCase: false); if (implementationType0 is not null) { global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } } """; private const string HiddenArrayResponseFallbackSource = """ 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 class Container { private sealed record HiddenResponse(); private sealed record HiddenRequest() : IRequest; private sealed class HiddenHandler : IRequestHandler { } } } """; private const string HiddenArrayResponseFallbackExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = registryAssembly.GetType("TestApp.Container+HiddenHandler", throwOnError: false, ignoreCase: false); if (implementationType0 is not null) { var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); var serviceType0_0Argument1Element = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false); if (serviceType0_0Argument0 is not null && serviceType0_0Argument1Element is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType()); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } } } """; private const string HiddenGenericEnvelopeResponseSource = """ 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 class Container { private sealed class HiddenEnvelope { } private sealed record HiddenRequest() : IRequest>; private sealed class HiddenHandler : IRequestHandler> { } } } """; private const string HiddenGenericEnvelopeResponseExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = registryAssembly.GetType("TestApp.Container+HiddenHandler", throwOnError: false, ignoreCase: false); if (implementationType0 is not null) { var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); var serviceType0_0Argument1GenericDefinition = registryAssembly.GetType("TestApp.Container+HiddenEnvelope`1", throwOnError: false, ignoreCase: false); if (serviceType0_0Argument0 is not null && serviceType0_0Argument1GenericDefinition is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1GenericDefinition.MakeGenericType(typeof(string))); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler>."); } } } } """; private const string MixedDirectAndPreciseRegistrationsExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = typeof(global::TestApp.Container.MixedHandler); if (implementationType0 is not null) { var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); var serviceType0_0Argument1Element = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false); if (serviceType0_0Argument0 is not null && serviceType0_0Argument1Element is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType()); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.MixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.MixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } } """; private const string MixedReflectedImplementationAndPreciseRegistrationsExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = registryAssembly.GetType("TestApp.Container+HiddenMixedHandler", throwOnError: false, ignoreCase: false); if (implementationType0 is not null) { var serviceType0_0Argument0 = registryAssembly.GetType("TestApp.Container+HiddenRequest", throwOnError: false, ignoreCase: false); var serviceType0_0Argument1Element = registryAssembly.GetType("TestApp.Container+HiddenResponse", throwOnError: false, ignoreCase: false); if (serviceType0_0Argument0 is not null && serviceType0_0Argument1Element is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType()); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenMixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), implementationType0); logger.Debug("Registered CQRS handler TestApp.Container.HiddenMixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } } """; private const string ExternalAssemblyPreciseLookupExpected = """ // #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)); var registryAssembly = typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry).Assembly; var implementationType0 = typeof(global::TestApp.DerivedHandler); if (implementationType0 is not null) { var serviceType0_0Argument0 = ResolveReferencedAssemblyType("Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Dep.VisibilityScope+ProtectedRequest"); var serviceType0_0Argument1Element = ResolveReferencedAssemblyType("Dependency, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null", "Dep.VisibilityScope+ProtectedResponse"); if (serviceType0_0Argument0 is not null && serviceType0_0Argument1Element is not null) { var serviceType0_0 = typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<,>).MakeGenericType(serviceType0_0Argument0, serviceType0_0Argument1Element.MakeArrayType()); global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, serviceType0_0, implementationType0); logger.Debug("Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient( services, typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler), implementationType0); logger.Debug("Registered CQRS handler TestApp.DerivedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler."); } } private static global::System.Type? ResolveReferencedAssemblyType(string assemblyIdentity, string typeMetadataName) { var assembly = ResolveReferencedAssembly(assemblyIdentity); return assembly?.GetType(typeMetadataName, throwOnError: false, ignoreCase: false); } private static global::System.Reflection.Assembly? ResolveReferencedAssembly(string assemblyIdentity) { global::System.Reflection.AssemblyName targetAssemblyName; try { targetAssemblyName = new global::System.Reflection.AssemblyName(assemblyIdentity); } catch { return null; } foreach (var assembly in global::System.AppDomain.CurrentDomain.GetAssemblies()) { if (global::System.Reflection.AssemblyName.ReferenceMatchesDefinition(targetAssemblyName, assembly.GetName())) return assembly; } try { return global::System.Reflection.Assembly.Load(targetAssemblyName); } catch { return null; } } } """; private const string AssemblyLevelCqrsHandlerRegistrySource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } } } 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 { } } """; private const string AssemblyLevelCqrsHandlerRegistryExpected = """ // #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."); } } """; // Keep large source fixtures at class scope so MA0051 reduction stays behavior-neutral for generator tests. private const string HiddenPointerResponseCompilationErrorSource = """ 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 class Container { private unsafe struct HiddenResponse { } private unsafe sealed record HiddenRequest() : IRequest; public unsafe sealed class HiddenHandler : IRequestHandler { } } } """; private const string MixedDirectAndPreciseRegistrationsSource = """ 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 HiddenResponse(); private sealed record HiddenRequest() : IRequest; public sealed class MixedHandler : IRequestHandler, IRequestHandler { } } } """; private const string MixedReflectedImplementationAndPreciseRegistrationsSource = """ 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 HiddenResponse(); private sealed record HiddenRequest() : IRequest; private sealed class HiddenMixedHandler : IRequestHandler, IRequestHandler { } } } """; private const string ExternalProtectedTypeContractsSource = """ 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 { } } """; private const string ExternalProtectedTypeDependencySource = """ using GFramework.Cqrs.Abstractions.Cqrs; namespace Dep; public sealed record VisibleRequest() : IRequest; public abstract class VisibilityScope { protected internal sealed record ProtectedResponse(); protected internal sealed record ProtectedRequest() : IRequest; } public abstract class HandlerBase : VisibilityScope, IRequestHandler, IRequestHandler { } """; private const string ExternalProtectedTypeLookupSource = """ using System; using Dep; 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 { 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 { public sealed class DerivedHandler : HandlerBase { } } """; private const string LegacyFallbackMarkerHiddenHandlerSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute() { } } } 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 { } } """; private const string FallbackMarkerUnavailableHiddenHandlerSource = """ 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 { } } """; private const string MissingFallbackAttributeDiagnosticSource = """ 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 class Container { private unsafe struct HiddenResponse { } private unsafe sealed record HiddenRequest() : IRequest>; public unsafe sealed class HiddenHandler : IRequestHandler> { } } } """; private const string UnresolvedErrorTypeRuntimeLookupSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } } } namespace TestApp { using GFramework.Cqrs.Abstractions.Cqrs; public sealed record BrokenRequest() : IRequest; public sealed class BrokenHandler : IRequestHandler { } } """; private const string DynamicResponseNormalizationSource = """ 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 DynamicRequest() : IRequest; public sealed class DynamicHandler : IRequestHandler { } } """; private const string AssemblyLevelFallbackMetadataSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } } } namespace TestApp { using GFramework.Cqrs.Abstractions.Cqrs; public sealed class Container { private unsafe struct AlphaResponse { } private unsafe struct BetaResponse { } private unsafe sealed record AlphaRequest() : IRequest>; private unsafe sealed record BetaRequest() : IRequest>; public unsafe sealed class BetaHandler : IRequestHandler> { } public unsafe sealed class AlphaHandler : IRequestHandler> { } } } """; private const string AssemblyLevelDirectFallbackMetadataSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } public CqrsReflectionFallbackAttribute(params Type[] fallbackHandlerTypes) { } } } namespace TestApp { using GFramework.Cqrs.Abstractions.Cqrs; public sealed class Container { private unsafe struct AlphaResponse { } private unsafe struct BetaResponse { } private unsafe sealed record AlphaRequest() : IRequest>; private unsafe sealed record BetaRequest() : IRequest>; public unsafe sealed class BetaHandler : IRequestHandler> { } public unsafe sealed class AlphaHandler : IRequestHandler> { } } } """; private const string AssemblyLevelMixedFallbackMetadataSource = """ 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) { } } [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public sealed class CqrsReflectionFallbackAttribute : Attribute { public CqrsReflectionFallbackAttribute(params string[] fallbackHandlerTypeNames) { } public CqrsReflectionFallbackAttribute(params Type[] fallbackHandlerTypes) { } } } namespace TestApp { using GFramework.Cqrs.Abstractions.Cqrs; public sealed class Container { private unsafe struct AlphaResponse { } private unsafe struct BetaResponse { } private unsafe sealed record AlphaRequest() : IRequest>; private unsafe sealed record BetaRequest() : IRequest>; public unsafe sealed class AlphaHandler : IRequestHandler> { } private unsafe sealed class BetaHandler : IRequestHandler> { } } } """; /// /// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。 /// [Test] public async Task Generates_Assembly_Level_Cqrs_Handler_Registry() { await GeneratorTest.RunAsync( AssemblyLevelCqrsHandlerRegistrySource, ("CqrsHandlerRegistry.g.cs", AssemblyLevelCqrsHandlerRegistryExpected)); } /// /// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会在生成注册器内部执行定向反射注册, /// 不再依赖程序集级 fallback marker。 /// [Test] public async Task Generates_Visible_Handlers_And_Self_Registers_Private_Nested_Handler_When_Assembly_Contains_Hidden_Handler() { await GeneratorTest.RunAsync( HiddenNestedHandlerSelfRegistrationSource, ("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected)); } /// /// 验证当隐藏实现类型的 handler 接口仍可被生成代码直接引用时, /// 生成器只会定向反射实现类型,而不会再生成基于 GetInterfaces() 的接口发现辅助逻辑。 /// [Test] public async Task Generates_Direct_Interface_Registrations_For_Hidden_Implementation_When_Handler_Interface_Is_Public() { await GeneratorTest.RunAsync( HiddenImplementationDirectInterfaceRegistrationSource, ("CqrsHandlerRegistry.g.cs", HiddenImplementationDirectInterfaceRegistrationExpected)); } /// /// 验证精确重建路径会递归覆盖隐藏元素类型数组, /// 使这类 handler interface 也能直接生成 closed service type,而不再退回 GetInterfaces()。 /// [Test] public async Task Generates_Precise_Service_Type_For_Hidden_Array_Type_Arguments() { await GeneratorTest.RunAsync( HiddenArrayResponseFallbackSource, ("CqrsHandlerRegistry.g.cs", HiddenArrayResponseFallbackExpected)); } /// /// 验证精确重建路径会递归覆盖隐藏泛型定义, /// 使“隐藏泛型定义 + 可见/常量型实参”的闭包类型也能直接生成 closed service type。 /// [Test] public async Task Generates_Precise_Service_Type_For_Hidden_Generic_Type_Definitions() { await GeneratorTest.RunAsync( HiddenGenericEnvelopeResponseSource, ("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected)); } /// /// 验证当 handler 合同把 pointer 响应类型放进 CQRS 泛型参数时, /// 生成器会保守回退而不是继续发射不可构造的精确注册代码。 /// [Test] public void Reports_Compilation_Error_And_Skips_Precise_Registration_For_Hidden_Pointer_Response() { var execution = ExecuteGenerator( HiddenPointerResponseCompilationErrorSource, allowUnsafe: true); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var missingContractDiagnostic = generatorErrors.SingleOrDefault(static diagnostic => string.Equals(diagnostic.Id, "GF_Cqrs_001", StringComparison.Ordinal)); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(execution.GeneratedSources, Is.Empty); Assert.That(missingContractDiagnostic, Is.Not.Null); Assert.That( missingContractDiagnostic!.GetMessage(), Does.Contain("TestApp.Container+HiddenHandler")); Assert.That( missingContractDiagnostic.GetMessage(), Does.Contain("GFramework.Cqrs.CqrsReflectionFallbackAttribute")); }); } /// /// 验证同一个 implementation 同时包含可直接注册接口与需精确重建接口时, /// 生成器会保留两类注册,并继续按 handler interface 名称稳定排序。 /// [Test] public async Task Generates_Mixed_Direct_And_Precise_Registrations_For_Same_Implementation() { await GeneratorTest.RunAsync( MixedDirectAndPreciseRegistrationsSource, ("CqrsHandlerRegistry.g.cs", MixedDirectAndPreciseRegistrationsExpected)); } /// /// 验证隐藏 implementation 同时包含可见 handler interface 与需精确重建接口时, /// 生成器会保留两类注册,而不会让可见接口被整实现回退吞掉。 /// [Test] public async Task Generates_Mixed_Reflected_Implementation_And_Precise_Registrations_For_Same_Implementation() { await GeneratorTest.RunAsync( MixedReflectedImplementationAndPreciseRegistrationsSource, ("CqrsHandlerRegistry.g.cs", MixedReflectedImplementationAndPreciseRegistrationsExpected)); } /// /// 验证当外部基类暴露的 handler interface 含有生成注册器顶层上下文不可直接引用的 protected 类型时, /// 生成器会输出定向程序集查找,而不是继续退回 implementation 级接口发现。 /// [Test] public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_Protected_Types() { var contractsReference = MetadataReferenceTestBuilder.CreateFromSource( "Contracts", ExternalProtectedTypeContractsSource); var dependencyReference = MetadataReferenceTestBuilder.CreateFromSource( "Dependency", ExternalProtectedTypeDependencySource, contractsReference); var generatedSource = RunGenerator( ExternalProtectedTypeLookupSource, contractsReference, dependencyReference); Assert.That( generatedSource, Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces(")); Assert.That( generatedSource, Does.Not.Contain("Remaining runtime interface discovery target:")); Assert.That( generatedSource, Is.EqualTo(ExternalAssemblyPreciseLookupExpected)); } /// /// 验证即使 runtime 仍暴露旧版无参 fallback marker,生成器也会优先在生成注册器内部处理隐藏 handler, /// 不再输出 fallback marker。 /// [Test] public async Task Does_Not_Emit_Legacy_Fallback_Marker_When_Generated_Registry_Can_Self_Register_Hidden_Handler() { await GeneratorTest.RunAsync( LegacyFallbackMarkerHiddenHandlerSource, ("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected)); } /// /// 验证即使 runtime 合同中完全不存在 reflection fallback 标记特性, /// 生成器仍能通过生成注册器内部的定向反射逻辑覆盖隐藏 handler。 /// [Test] public async Task Generates_Registry_For_Hidden_Handler_When_Fallback_Marker_Is_Unavailable() { await GeneratorTest.RunAsync( FallbackMarkerUnavailableHiddenHandlerSource, ("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected)); } /// /// 验证当某轮生成仍然需要程序集级 reflection fallback 元数据,且 runtime 合同缺少承载该元数据的特性时, /// 生成器会给出明确诊断并停止输出注册器。 /// [Test] public void Reports_Diagnostic_And_Skips_Registry_When_Fallback_Metadata_Is_Required_But_Runtime_Contract_Lacks_Fallback_Attribute() { var execution = ExecuteGenerator( MissingFallbackAttributeDiagnosticSource, allowUnsafe: true); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var missingContractDiagnostic = generatorErrors.SingleOrDefault(static diagnostic => string.Equals(diagnostic.Id, "GF_Cqrs_001", StringComparison.Ordinal)); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(execution.GeneratedSources, Is.Empty); Assert.That(missingContractDiagnostic, Is.Not.Null); Assert.That( missingContractDiagnostic!.GetMessage(), Does.Contain("TestApp.Container+HiddenHandler")); Assert.That( missingContractDiagnostic.GetMessage(), Does.Contain("GFramework.Cqrs.CqrsReflectionFallbackAttribute")); }); } /// /// 验证 handler 合同里出现未解析错误类型时,生成器会改为运行时精确查找该类型, /// 而不会把无效类型名直接写进生成代码中的 typeof(...)。 /// [Test] public void Emits_Runtime_Type_Lookup_When_Handler_Contract_Contains_Unresolved_Error_Types() { var execution = ExecuteGenerator(UnresolvedErrorTypeRuntimeLookupSource); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0246")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); var generatedSource = execution.GeneratedSources[0].content; Assert.That( generatedSource, Does.Contain("registryAssembly.GetType(\"MissingResponse\", throwOnError: false, ignoreCase: false);")); Assert.That( generatedSource, Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry")); Assert.That(generatedSource, Does.Not.Contain("typeof(MissingResponse)")); Assert.That(generatedSource, Does.Not.Contain("CqrsReflectionFallbackAttribute(")); }); } /// /// 验证 响应类型会在生成阶段归一化为 , /// 避免注册器发射非法的 typeof(dynamic)。 /// [Test] public void Emits_Object_Type_Reference_When_Handler_Response_Uses_Dynamic() { var execution = ExecuteGenerator(DynamicResponseNormalizationSource); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS1966")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); var generatedSource = execution.GeneratedSources[0].content; Assert.That(generatedSource, Does.Contain("typeof(global::System.Object)")); Assert.That(generatedSource, Does.Not.Contain("typeof(dynamic)")); Assert.That(generatedSource, Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry")); }); } /// /// 验证当 fallback metadata 仍然必需且 runtime 提供了承载契约时, /// 生成器会继续产出注册器并发射程序集级 CqrsReflectionFallbackAttribute。 /// [Test] public void Emits_Assembly_Level_Fallback_Metadata_When_Fallback_Is_Required_And_Runtime_Contract_Is_Available() { var execution = ExecuteGenerator( AssemblyLevelFallbackMetadataSource, allowUnsafe: true); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); Assert.That( execution.GeneratedSources[0].content, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]")); Assert.That( execution.GeneratedSources[0].content, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsHandlerRegistryAttribute(typeof(global::GFramework.Generated.Cqrs.__GFrameworkGeneratedCqrsHandlerRegistry))]")); Assert.That( execution.GeneratedSources[0].content, Does.Contain("internal sealed class __GFrameworkGeneratedCqrsHandlerRegistry")); }); } /// /// 验证当所有 fallback handlers 本身都可直接引用,且 runtime 同时支持字符串与 元数据承载时, /// 生成器会优先发射直接 typeof(...) 的 fallback 特性,减少运行时按名称回查程序集类型。 /// [Test] public void Emits_Direct_Type_Fallback_Metadata_When_All_Fallback_Handlers_Are_Referenceable_And_Runtime_Type_Contract_Is_Available() { var execution = ExecuteGenerator( AssemblyLevelDirectFallbackMetadataSource, allowUnsafe: true); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); Assert.That( execution.GeneratedSources[0].content, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler), typeof(global::TestApp.Container.BetaHandler))]")); Assert.That( execution.GeneratedSources[0].content, Does.Not.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+AlphaHandler\", \"TestApp.Container+BetaHandler\")]")); }); } /// /// 验证当 runtime 允许多个 fallback 特性实例,且本轮 fallback 同时包含可直接引用与仅能按名称恢复的 handlers 时, /// 生成器会拆分出直接 与字符串两类元数据,避免 mixed 场景整体退回字符串 fallback。 /// [Test] public void Emits_Mixed_Direct_Type_And_String_Fallback_Metadata_When_Runtime_Allows_Multiple_Fallback_Attributes() { var execution = ExecuteGenerator( AssemblyLevelMixedFallbackMetadataSource, allowUnsafe: true); var inputCompilationErrors = execution.InputCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatedCompilationErrors = execution.GeneratedCompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.Multiple(() => { Assert.That(inputCompilationErrors.Select(static diagnostic => diagnostic.Id), Does.Contain("CS0306")); Assert.That(generatedCompilationErrors, Is.Empty); Assert.That(generatorErrors, Is.Empty); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); Assert.That(execution.GeneratedSources[0].filename, Is.EqualTo("CqrsHandlerRegistry.g.cs")); Assert.That( execution.GeneratedSources[0].content, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(typeof(global::TestApp.Container.AlphaHandler))]")); Assert.That( execution.GeneratedSources[0].content, Does.Contain( "[assembly: global::GFramework.Cqrs.CqrsReflectionFallbackAttribute(\"TestApp.Container+BetaHandler\")]")); }); } /// /// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。 /// [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)); } /// /// 运行 CQRS handler registry generator,并返回单个生成文件的源码文本。 /// private static string RunGenerator( string source, params MetadataReference[] additionalReferences) { var execution = ExecuteGenerator( source, allowUnsafe: false, additionalReferences: additionalReferences); var generatorErrors = execution.GeneratorDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.That( generatorErrors, Is.Empty, () => $"执行生成器时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, generatorErrors.Select(static diagnostic => diagnostic.ToString()))}"); var compilationErrors = execution.CompilationDiagnostics .Where(static diagnostic => diagnostic.Severity == DiagnosticSeverity.Error) .ToArray(); Assert.That( compilationErrors, Is.Empty, () => $"编译生成的代码时出现错误:{Environment.NewLine}{string.Join(Environment.NewLine, compilationErrors.Select(static diagnostic => diagnostic.ToString()))}"); Assert.That(execution.GeneratedSources, Has.Length.EqualTo(1)); return execution.GeneratedSources[0].content; } /// /// 运行 CQRS handler registry generator,并返回生成输出及相关诊断。 /// /// 输入源码。 /// /// 是否允许测试编译包含 unsafe 代码。 /// 某些回归用例会故意构造带指针类型的非法 handler 合同,以覆盖 fallback 防御分支,此时需要启用该选项避免把缺少 /// unsafe 编译上下文的错误与目标生成器行为混淆。 /// /// 附加元数据引用,用于构造跨程序集场景。 /// 包含生成源、生成器诊断和更新后编译诊断的执行结果。 private static GeneratorExecutionResult ExecuteGenerator( string source, bool allowUnsafe = false, params MetadataReference[] additionalReferences) { var syntaxTree = CSharpSyntaxTree.ParseText(source); var compilation = CSharpCompilation.Create( "TestProject", [syntaxTree], MetadataReferenceTestBuilder.GetRuntimeMetadataReferences().AddRange(additionalReferences), new CSharpCompilationOptions( OutputKind.DynamicallyLinkedLibrary, allowUnsafe: allowUnsafe)); GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [new CqrsHandlerRegistryGenerator().AsSourceGenerator()], parseOptions: (CSharpParseOptions)syntaxTree.Options); driver = driver.RunGeneratorsAndUpdateCompilation( compilation, out var updatedCompilation, out var generatorDiagnostics); var runResult = driver.GetRunResult(); Assert.That(runResult.Results, Has.Length.EqualTo(1)); var generatedSyntaxTrees = runResult.Results[0].GeneratedSources .Select(static sourceResult => sourceResult.SyntaxTree) .ToHashSet(); var generatedSources = runResult.Results[0].GeneratedSources .Select(static sourceResult => (filename: sourceResult.HintName, content: sourceResult.SourceText.ToString())) .ToArray(); var compilationDiagnostics = updatedCompilation.GetDiagnostics().ToArray(); var inputCompilationDiagnostics = compilationDiagnostics .Where(diagnostic => diagnostic.Location.SourceTree is null || !generatedSyntaxTrees.Contains(diagnostic.Location.SourceTree)) .ToArray(); var generatedCompilationDiagnostics = compilationDiagnostics .Where(diagnostic => diagnostic.Location.SourceTree is not null && generatedSyntaxTrees.Contains(diagnostic.Location.SourceTree)) .ToArray(); return new GeneratorExecutionResult( generatedSources, generatorDiagnostics.ToArray(), compilationDiagnostics, inputCompilationDiagnostics, generatedCompilationDiagnostics); } /// /// 封装 CQRS handler registry generator 的单次执行结果。 /// /// 本轮生成产生的源文件集合。 /// 生成器自身报告的诊断集合。 /// 将生成结果并回编译后的完整编译诊断集合。 /// 仅来自输入源文件的编译诊断集合。 /// 仅来自生成源文件的编译诊断集合。 private sealed record GeneratorExecutionResult( (string filename, string content)[] GeneratedSources, Diagnostic[] GeneratorDiagnostics, Diagnostic[] CompilationDiagnostics, Diagnostic[] InputCompilationDiagnostics, Diagnostic[] GeneratedCompilationDiagnostics); }