mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-12 22:03: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>
|
/// <summary>
|
||||||
/// 创建一个仅通过 generated registry 注册主 handler、并附带指定 fallback 元数据的程序集替身。
|
/// 创建一个仅通过 generated registry 注册主 handler、并附带指定 fallback 元数据的程序集替身。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -161,6 +223,24 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
|
|||||||
return generatedAssembly;
|
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>
|
/// <summary>
|
||||||
/// 提取容器中针对 generated notification 注册的处理器实现类型。
|
/// 提取容器中针对 generated notification 注册的处理器实现类型。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -259,4 +339,55 @@ internal sealed class CqrsHandlerRegistrarFallbackFailureTests
|
|||||||
.Where(static log => log.Level == LogLevel.Warning)
|
.Where(static log => log.Level == LogLevel.Warning)
|
||||||
.ToArray();
|
.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.Ioc;
|
||||||
using GFramework.Core.Logging;
|
using GFramework.Core.Logging;
|
||||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||||
|
using GFramework.Cqrs.Internal;
|
||||||
using GFramework.Cqrs.Tests.Logging;
|
using GFramework.Cqrs.Tests.Logging;
|
||||||
|
|
||||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||||
@ -170,6 +171,74 @@ internal sealed class CqrsHandlerRegistrarTests
|
|||||||
Is.EqualTo([typeof(GeneratedRegistryNotificationHandler)]));
|
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>
|
||||||
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
|
/// 验证当生成注册器元数据损坏时,运行时会记录告警并回退到反射扫描路径。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -695,4 +764,55 @@ internal sealed class CqrsHandlerRegistrarTests
|
|||||||
return typeof(CqrsReflectionFallbackAttribute).Assembly
|
return typeof(CqrsReflectionFallbackAttribute).Assembly
|
||||||
.GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!;
|
.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