mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 13:14:30 +08:00
test(cqrs): 补充 registrar 激活失败分支测试
- 补充 generated registry 为抽象类型时的回退与抛错覆盖 - 补充 generated registry 缺少无参构造器时的回退与抛错覆盖
This commit is contained in:
parent
75e7785592
commit
5e9b903d0f
@ -138,6 +138,68 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 generated registry 是抽象类型时,registrar 会记录告警并回退到反射扫描。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Fall_Back_To_Reflection_When_Generated_Registry_Is_Abstract()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedRegistryAssembly(
|
||||
"GFramework.Cqrs.Tests.Cqrs.AbstractGeneratedRegistryAssembly, Version=1.0.0.0",
|
||||
typeof(AbstractGeneratedNotificationHandlerRegistry));
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetTypes())
|
||||
.Returns([typeof(GeneratedRegistryNotificationHandler)]);
|
||||
|
||||
var container = new MicrosoftDiContainer();
|
||||
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
GetGeneratedRegistryNotificationHandlerTypes(container),
|
||||
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
||||
Assert.That(
|
||||
GetWarningLogs().Any(log =>
|
||||
log.Message.Contains("because it is abstract", StringComparison.Ordinal)),
|
||||
Is.True);
|
||||
});
|
||||
|
||||
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 generated registry 不暴露可访问无参构造器时,registrar 会记录告警并回退到反射扫描。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterHandlers_Should_Fall_Back_To_Reflection_When_Generated_Registry_Has_No_Parameterless_Constructor()
|
||||
{
|
||||
var generatedAssembly = CreateGeneratedRegistryAssembly(
|
||||
"GFramework.Cqrs.Tests.Cqrs.NoParameterlessGeneratedRegistryAssembly, Version=1.0.0.0",
|
||||
typeof(ConstructorArgumentNotificationHandlerRegistry));
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetTypes())
|
||||
.Returns([typeof(GeneratedRegistryNotificationHandler)]);
|
||||
|
||||
var container = new MicrosoftDiContainer();
|
||||
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(
|
||||
GetGeneratedRegistryNotificationHandlerTypes(container),
|
||||
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
||||
Assert.That(
|
||||
GetWarningLogs().Any(log =>
|
||||
log.Message.Contains(
|
||||
"does not expose an accessible parameterless constructor",
|
||||
StringComparison.Ordinal)),
|
||||
Is.True);
|
||||
});
|
||||
|
||||
generatedAssembly.Verify(static assembly => assembly.GetTypes(), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个仅通过 generated registry 注册主 handler、并附带指定 fallback 元数据的程序集替身。
|
||||
/// </summary>
|
||||
@ -161,6 +223,24 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
|
||||
return generatedAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只声明 generated registry attribute 的程序集替身,用于验证 registry 激活失败后的回退行为。
|
||||
/// </summary>
|
||||
/// <param name="assemblyName">用于日志与缓存键的程序集名。</param>
|
||||
/// <param name="registryType">要暴露给 registrar 的 generated registry 类型。</param>
|
||||
/// <returns>已完成基础接线的程序集 mock。</returns>
|
||||
private static Mock<Assembly> CreateGeneratedRegistryAssembly(string assemblyName, Type registryType)
|
||||
{
|
||||
var generatedAssembly = new Mock<Assembly>();
|
||||
generatedAssembly
|
||||
.SetupGet(static assembly => assembly.FullName)
|
||||
.Returns(assemblyName);
|
||||
generatedAssembly
|
||||
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
||||
.Returns([new CqrsHandlerRegistryAttribute(registryType)]);
|
||||
return generatedAssembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 提取容器中针对 generated notification 注册的处理器实现类型。
|
||||
/// </summary>
|
||||
@ -259,4 +339,55 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
|
||||
.Where(static log => log.Level == LogLevel.Warning)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟 generated registry 被错误声明为抽象类型时的激活失败场景。
|
||||
/// </summary>
|
||||
private abstract class AbstractGeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象 registry 即便具备注册逻辑,也不应被运行时实例化。
|
||||
/// </summary>
|
||||
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||
/// <param name="logger">记录注册诊断的日志器。</param>
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(INotificationHandler<GeneratedRegistryNotification>),
|
||||
typeof(GeneratedRegistryNotificationHandler));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟 generated registry 缺少可访问无参构造器时的激活失败场景。
|
||||
/// </summary>
|
||||
private sealed class ConstructorArgumentNotificationHandlerRegistry : ICqrsHandlerRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化一个只能通过额外参数构造的测试 registry。
|
||||
/// </summary>
|
||||
/// <param name="marker">用于区分测试场景的占位参数。</param>
|
||||
public ConstructorArgumentNotificationHandlerRegistry(string marker)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(marker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 此实现仅用于满足接口契约;本用例关注的是实例化失败前的回退行为。
|
||||
/// </summary>
|
||||
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||
/// <param name="logger">记录注册诊断的日志器。</param>
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(INotificationHandler<GeneratedRegistryNotification>),
|
||||
typeof(GeneratedRegistryNotificationHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ using GFramework.Core.Architectures;
|
||||
using GFramework.Core.Ioc;
|
||||
using GFramework.Core.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Internal;
|
||||
using GFramework.Cqrs.Tests.Logging;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
@ -170,6 +171,74 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 direct generated-registry 激活入口在 registry 为抽象类型时会抛出异常,并保留契约告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterGeneratedRegistry_Should_Throw_When_Generated_Registry_Is_Abstract()
|
||||
{
|
||||
var capturingProvider = new CapturingLoggerFactoryProvider(LogLevel.Warning);
|
||||
var logger = capturingProvider.CreateLogger(nameof(CqrsHandlerRegistrarTests));
|
||||
var container = new MicrosoftDiContainer();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
CqrsHandlerRegistrar.RegisterGeneratedRegistry(
|
||||
container,
|
||||
typeof(AbstractGeneratedNotificationHandlerRegistry),
|
||||
logger));
|
||||
|
||||
var warningLogs = capturingProvider.Loggers
|
||||
.SelectMany(static createdLogger => createdLogger.Logs)
|
||||
.Where(static log => log.Level == LogLevel.Warning)
|
||||
.ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(exception, Is.Not.Null);
|
||||
Assert.That(exception!.Message, Does.Contain(typeof(AbstractGeneratedNotificationHandlerRegistry).FullName));
|
||||
Assert.That(
|
||||
warningLogs.Any(log =>
|
||||
log.Message.Contains("because it is abstract", StringComparison.Ordinal)),
|
||||
Is.True);
|
||||
Assert.That(container.GetServicesUnsafe, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证 direct generated-registry 激活入口在 registry 缺少无参构造器时会抛出异常,并保留契约告警。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void RegisterGeneratedRegistry_Should_Throw_When_Generated_Registry_Has_No_Parameterless_Constructor()
|
||||
{
|
||||
var capturingProvider = new CapturingLoggerFactoryProvider(LogLevel.Warning);
|
||||
var logger = capturingProvider.CreateLogger(nameof(CqrsHandlerRegistrarTests));
|
||||
var container = new MicrosoftDiContainer();
|
||||
|
||||
var exception = Assert.Throws<InvalidOperationException>(() =>
|
||||
CqrsHandlerRegistrar.RegisterGeneratedRegistry(
|
||||
container,
|
||||
typeof(ConstructorArgumentNotificationHandlerRegistry),
|
||||
logger));
|
||||
|
||||
var warningLogs = capturingProvider.Loggers
|
||||
.SelectMany(static createdLogger => createdLogger.Logs)
|
||||
.Where(static log => log.Level == LogLevel.Warning)
|
||||
.ToArray();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(exception, Is.Not.Null);
|
||||
Assert.That(exception!.Message, Does.Contain(typeof(ConstructorArgumentNotificationHandlerRegistry).FullName));
|
||||
Assert.That(
|
||||
warningLogs.Any(log =>
|
||||
log.Message.Contains(
|
||||
"does not expose an accessible parameterless constructor",
|
||||
StringComparison.Ordinal)),
|
||||
Is.True);
|
||||
Assert.That(container.GetServicesUnsafe, Is.Empty);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
|
||||
/// </summary>
|
||||
@ -695,4 +764,55 @@ internal sealed class CqrsHandlerRegistrarTests
|
||||
return typeof(CqrsReflectionFallbackAttribute).Assembly
|
||||
.GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟被错误声明为抽象类型的 generated registry。
|
||||
/// </summary>
|
||||
private abstract class AbstractGeneratedNotificationHandlerRegistry : ICqrsHandlerRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// 抽象 registry 即便具备注册逻辑,也不应被 direct 激活入口实例化。
|
||||
/// </summary>
|
||||
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||
/// <param name="logger">记录注册诊断的日志器。</param>
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(INotificationHandler<GeneratedRegistryNotification>),
|
||||
typeof(GeneratedRegistryNotificationHandler));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 模拟缺少无参构造器的 generated registry。
|
||||
/// </summary>
|
||||
private sealed class ConstructorArgumentNotificationHandlerRegistry : ICqrsHandlerRegistry
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化一个只能通过额外参数构造的测试 registry。
|
||||
/// </summary>
|
||||
/// <param name="marker">用于区分测试场景的占位参数。</param>
|
||||
public ConstructorArgumentNotificationHandlerRegistry(string marker)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(marker);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 此实现仅用于满足接口契约;本用例关注的是构造阶段失败后的异常语义。
|
||||
/// </summary>
|
||||
/// <param name="services">承载处理器映射的服务集合。</param>
|
||||
/// <param name="logger">记录注册诊断的日志器。</param>
|
||||
public void Register(IServiceCollection services, ILogger logger)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
services.AddTransient(
|
||||
typeof(INotificationHandler<GeneratedRegistryNotification>),
|
||||
typeof(GeneratedRegistryNotificationHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user