mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-06 16:16:44 +08:00
- 新增 CQRS 核心概念、命令查询处理器实现指南 - 添加 CQRS 高级用法包括通知发布、管道行为和流式处理 - 提供 CQRS 最佳实践和常见问题解决方案 - 添加游戏配置系统完整接入模板和运行时读取示例 - 包含 YAML 配置文件和 JSON Schema 结构定义说明 - 提供 Godot 引擎配置桥接和热重载功能使用指南 - 添加架构模块集成和生成查询辅助功能文档
1172 lines
70 KiB
C#
1172 lines
70 KiB
C#
using System.Reflection;
|
||
using GFramework.SourceGenerators.Cqrs;
|
||
using GFramework.SourceGenerators.Tests.Core;
|
||
|
||
namespace GFramework.SourceGenerators.Tests.Cqrs;
|
||
|
||
/// <summary>
|
||
/// 验证 CQRS 处理器注册生成器的输出与回退边界。
|
||
/// </summary>
|
||
[TestFixture]
|
||
public class CqrsHandlerRegistryGeneratorTests
|
||
{
|
||
private const string HiddenNestedHandlerSelfRegistrationExpected = """
|
||
// <auto-generated />
|
||
#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<TestApp.Container.HiddenRequest, string>.");
|
||
}
|
||
}
|
||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||
services,
|
||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.VisibleRequest, string>),
|
||
typeof(global::TestApp.VisibleHandler));
|
||
logger.Debug("Registered CQRS handler TestApp.VisibleHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
private const string HiddenImplementationDirectInterfaceRegistrationExpected = """
|
||
// <auto-generated />
|
||
#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<global::TestApp.VisibleRequest, string>),
|
||
implementationType0);
|
||
logger.Debug("Registered CQRS handler TestApp.Container.HiddenHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
|
||
}
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
private const string HiddenArrayResponseFallbackExpected = """
|
||
// <auto-generated />
|
||
#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<TestApp.Container.HiddenRequest, TestApp.Container.HiddenResponse[]>.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
private const string HiddenGenericEnvelopeResponseExpected = """
|
||
// <auto-generated />
|
||
#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<TestApp.Container.HiddenRequest, TestApp.Container.HiddenEnvelope<string>>.");
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
private const string MixedDirectAndPreciseRegistrationsExpected = """
|
||
// <auto-generated />
|
||
#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<TestApp.Container.HiddenRequest, TestApp.Container.HiddenResponse[]>.");
|
||
}
|
||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||
services,
|
||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.VisibleRequest, string>),
|
||
implementationType0);
|
||
logger.Debug("Registered CQRS handler TestApp.Container.MixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
|
||
}
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
private const string MixedReflectedImplementationAndPreciseRegistrationsExpected = """
|
||
// <auto-generated />
|
||
#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<TestApp.Container.HiddenRequest, TestApp.Container.HiddenResponse[]>.");
|
||
}
|
||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||
services,
|
||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::TestApp.VisibleRequest, string>),
|
||
implementationType0);
|
||
logger.Debug("Registered CQRS handler TestApp.Container.HiddenMixedHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.VisibleRequest, string>.");
|
||
}
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
/// <summary>
|
||
/// 验证生成器会为当前程序集中的 request、notification 和 stream 处理器生成稳定顺序的注册器。
|
||
/// </summary>
|
||
[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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
public sealed record DomainEvent() : INotification;
|
||
public sealed record NumberStream() : IStreamRequest<int>;
|
||
|
||
public sealed class ZetaNotificationHandler : INotificationHandler<DomainEvent> { }
|
||
public sealed class AlphaQueryHandler : IRequestHandler<PingQuery, string> { }
|
||
public sealed class StreamHandler : IStreamRequestHandler<NumberStream, int> { }
|
||
}
|
||
""";
|
||
|
||
const string expected = """
|
||
// <auto-generated />
|
||
#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<global::TestApp.PingQuery, string>),
|
||
typeof(global::TestApp.AlphaQueryHandler));
|
||
logger.Debug("Registered CQRS handler TestApp.AlphaQueryHandler as GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<TestApp.PingQuery, string>.");
|
||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||
services,
|
||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<global::TestApp.NumberStream, int>),
|
||
typeof(global::TestApp.StreamHandler));
|
||
logger.Debug("Registered CQRS handler TestApp.StreamHandler as GFramework.Cqrs.Abstractions.Cqrs.IStreamRequestHandler<TestApp.NumberStream, int>.");
|
||
global::Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions.AddTransient(
|
||
services,
|
||
typeof(global::GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<global::TestApp.DomainEvent>),
|
||
typeof(global::TestApp.ZetaNotificationHandler));
|
||
logger.Debug("Registered CQRS handler TestApp.ZetaNotificationHandler as GFramework.Cqrs.Abstractions.Cqrs.INotificationHandler<TestApp.DomainEvent>.");
|
||
}
|
||
}
|
||
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", expected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证当程序集包含生成代码无法合法引用的私有嵌套处理器时,生成器会在生成注册器内部执行定向反射注册,
|
||
/// 不再依赖程序集级 fallback marker。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task
|
||
Generates_Visible_Handlers_And_Self_Registers_Private_Nested_Handler_When_Assembly_Contains_Hidden_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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed record HiddenRequest() : IRequest<string>;
|
||
|
||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, string> { }
|
||
}
|
||
|
||
public sealed class VisibleHandler : IRequestHandler<VisibleRequest, string> { }
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证当隐藏实现类型的 handler 接口仍可被生成代码直接引用时,
|
||
/// 生成器只会定向反射实现类型,而不会再生成基于 <c>GetInterfaces()</c> 的接口发现辅助逻辑。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task
|
||
Generates_Direct_Interface_Registrations_For_Hidden_Implementation_When_Handler_Interface_Is_Public()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed class HiddenHandler : IRequestHandler<VisibleRequest, string> { }
|
||
}
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenImplementationDirectInterfaceRegistrationExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证精确重建路径会递归覆盖隐藏元素类型数组,
|
||
/// 使这类 handler interface 也能直接生成 closed service type,而不再退回 <c>GetInterfaces()</c>。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Generates_Precise_Service_Type_For_Hidden_Array_Type_Arguments()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<HiddenResponse[]>;
|
||
|
||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenResponse[]> { }
|
||
}
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenArrayResponseFallbackExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证精确重建路径会递归覆盖隐藏泛型定义,
|
||
/// 使“隐藏泛型定义 + 可见/常量型实参”的闭包类型也能直接生成 closed service type。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Generates_Precise_Service_Type_For_Hidden_Generic_Type_Definitions()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<T> { }
|
||
|
||
private sealed record HiddenRequest() : IRequest<HiddenEnvelope<string>>;
|
||
|
||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, HiddenEnvelope<string>> { }
|
||
}
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenGenericEnvelopeResponseExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证同一个 implementation 同时包含可直接注册接口与需精确重建接口时,
|
||
/// 生成器会保留两类注册,并继续按 handler interface 名称稳定排序。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Generates_Mixed_Direct_And_Precise_Registrations_For_Same_Implementation()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed record HiddenResponse();
|
||
|
||
private sealed record HiddenRequest() : IRequest<HiddenResponse[]>;
|
||
|
||
public sealed class MixedHandler :
|
||
IRequestHandler<HiddenRequest, HiddenResponse[]>,
|
||
IRequestHandler<VisibleRequest, string>
|
||
{
|
||
}
|
||
}
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", MixedDirectAndPreciseRegistrationsExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证隐藏 implementation 同时包含可见 handler interface 与需精确重建接口时,
|
||
/// 生成器会保留两类注册,而不会让可见接口被整实现回退吞掉。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Generates_Mixed_Reflected_Implementation_And_Precise_Registrations_For_Same_Implementation()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed record HiddenResponse();
|
||
|
||
private sealed record HiddenRequest() : IRequest<HiddenResponse[]>;
|
||
|
||
private sealed class HiddenMixedHandler :
|
||
IRequestHandler<HiddenRequest, HiddenResponse[]>,
|
||
IRequestHandler<VisibleRequest, string>
|
||
{
|
||
}
|
||
}
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", MixedReflectedImplementationAndPreciseRegistrationsExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证当外部基类暴露的 handler interface 含有生成注册器顶层上下文不可直接引用的 protected 类型时,
|
||
/// 生成器会输出定向程序集查找,而不是继续退回 implementation 级接口发现。
|
||
/// </summary>
|
||
[Test]
|
||
public void Generates_Precise_Assembly_Type_Lookups_For_Inaccessible_External_Protected_Types()
|
||
{
|
||
const string contractsSource = """
|
||
namespace GFramework.Cqrs.Abstractions.Cqrs
|
||
{
|
||
public interface IRequest<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
""";
|
||
|
||
const string dependencySource = """
|
||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||
|
||
namespace Dep;
|
||
|
||
public sealed record VisibleRequest() : IRequest<string>;
|
||
|
||
public abstract class VisibilityScope
|
||
{
|
||
protected internal sealed record ProtectedResponse();
|
||
|
||
protected internal sealed record ProtectedRequest() : IRequest<ProtectedResponse[]>;
|
||
}
|
||
|
||
public abstract class HandlerBase :
|
||
VisibilityScope,
|
||
IRequestHandler<VisibleRequest, string>,
|
||
IRequestHandler<VisibilityScope.ProtectedRequest, VisibilityScope.ProtectedResponse[]>
|
||
{
|
||
}
|
||
""";
|
||
|
||
const string source = """
|
||
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
|
||
{
|
||
}
|
||
}
|
||
""";
|
||
|
||
var contractsReference = MetadataReferenceTestBuilder.CreateFromSource(
|
||
"Contracts",
|
||
contractsSource);
|
||
var dependencyReference = MetadataReferenceTestBuilder.CreateFromSource(
|
||
"Dependency",
|
||
dependencySource,
|
||
contractsReference);
|
||
var generatedSource = RunGenerator(
|
||
source,
|
||
contractsReference,
|
||
dependencyReference);
|
||
|
||
Assert.Multiple(() =>
|
||
{
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Contain("var implementationType0 = typeof(global::TestApp.DerivedHandler);"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Contain(
|
||
"ResolveReferencedAssemblyType(\"Dependency\", \"Dep.VisibilityScope+ProtectedRequest\")"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Contain(
|
||
"ResolveReferencedAssemblyType(\"Dependency\", \"Dep.VisibilityScope+ProtectedResponse\")"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Contain(
|
||
"typeof(global::GFramework.Cqrs.Abstractions.Cqrs.IRequestHandler<global::Dep.VisibleRequest, string>)"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Contain("ResolveReferencedAssembly(string assemblyName)"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Not.Contain("knownServiceTypes0"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Not.Contain("RegisterRemainingReflectedHandlerInterfaces"));
|
||
Assert.That(
|
||
generatedSource,
|
||
Does.Not.Contain(
|
||
"// Remaining runtime interface discovery target:"));
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证即使 runtime 仍暴露旧版无参 fallback marker,生成器也会优先在生成注册器内部处理隐藏 handler,
|
||
/// 不再输出 fallback marker。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Does_Not_Emit_Legacy_Fallback_Marker_When_Generated_Registry_Can_Self_Register_Hidden_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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed record HiddenRequest() : IRequest<string>;
|
||
|
||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, string> { }
|
||
}
|
||
|
||
public sealed class VisibleHandler : IRequestHandler<VisibleRequest, string> { }
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证即使 runtime 合同中完全不存在 reflection fallback 标记特性,
|
||
/// 生成器仍能通过生成注册器内部的定向反射逻辑覆盖隐藏 handler。
|
||
/// </summary>
|
||
[Test]
|
||
public async Task Generates_Registry_For_Hidden_Handler_When_Fallback_Marker_Is_Unavailable()
|
||
{
|
||
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<TResponse> { }
|
||
public interface INotification { }
|
||
public interface IStreamRequest<TResponse> { }
|
||
|
||
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse> { }
|
||
public interface INotificationHandler<in TNotification> where TNotification : INotification { }
|
||
public interface IStreamRequestHandler<in TRequest, out TResponse> where TRequest : IStreamRequest<TResponse> { }
|
||
}
|
||
|
||
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<string>;
|
||
|
||
public sealed class Container
|
||
{
|
||
private sealed record HiddenRequest() : IRequest<string>;
|
||
|
||
private sealed class HiddenHandler : IRequestHandler<HiddenRequest, string> { }
|
||
}
|
||
|
||
public sealed class VisibleHandler : IRequestHandler<VisibleRequest, string> { }
|
||
}
|
||
""";
|
||
|
||
await GeneratorTest<CqrsHandlerRegistryGenerator>.RunAsync(
|
||
source,
|
||
("CqrsHandlerRegistry.g.cs", HiddenNestedHandlerSelfRegistrationExpected));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证日志字符串转义会覆盖换行、反斜杠和双引号,避免生成代码中的字符串字面量被意外截断。
|
||
/// </summary>
|
||
[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));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 运行 CQRS handler registry generator,并返回单个生成文件的源码文本。
|
||
/// </summary>
|
||
private static string RunGenerator(
|
||
string source,
|
||
params MetadataReference[] additionalReferences)
|
||
{
|
||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||
var compilation = CSharpCompilation.Create(
|
||
"TestProject",
|
||
[syntaxTree],
|
||
MetadataReferenceTestBuilder.GetRuntimeMetadataReferences().AddRange(additionalReferences),
|
||
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
|
||
|
||
GeneratorDriver driver = CSharpGeneratorDriver.Create(
|
||
generators: [new CqrsHandlerRegistryGenerator().AsSourceGenerator()],
|
||
parseOptions: (CSharpParseOptions)syntaxTree.Options);
|
||
driver = driver.RunGeneratorsAndUpdateCompilation(
|
||
compilation,
|
||
out var updatedCompilation,
|
||
out _);
|
||
|
||
var compilationErrors = updatedCompilation.GetDiagnostics()
|
||
.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()))}");
|
||
|
||
var runResult = driver.GetRunResult();
|
||
Assert.That(runResult.Results, Has.Length.EqualTo(1));
|
||
Assert.That(runResult.Results[0].GeneratedSources, Has.Length.EqualTo(1));
|
||
|
||
return runResult.Results[0].GeneratedSources[0].SourceText.ToString();
|
||
}
|
||
}
|