mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
- 新增 generated request invoker provider seam,并让 registrar 与 dispatcher 复用编译期请求调用元数据 - 扩展 CQRS source generator 发射 request invoker provider 成员与最小 request invoker 方法 - 补充 runtime 与 source-generator 回归测试,并更新 cqrs-rewrite 追踪到 RP-067
178 lines
6.7 KiB
C#
178 lines
6.7 KiB
C#
using System.Reflection;
|
|
using GFramework.Core.Abstractions.Logging;
|
|
using GFramework.Core.Architectures;
|
|
using GFramework.Core.Ioc;
|
|
using GFramework.Core.Logging;
|
|
using GFramework.Cqrs.Abstractions.Cqrs;
|
|
|
|
namespace GFramework.Cqrs.Tests.Cqrs;
|
|
|
|
/// <summary>
|
|
/// 验证 generated request invoker provider 的 registrar 接线与 dispatcher 消费语义。
|
|
/// </summary>
|
|
[TestFixture]
|
|
[NonParallelizable]
|
|
internal sealed class CqrsGeneratedRequestInvokerProviderTests
|
|
{
|
|
/// <summary>
|
|
/// 在每个用例前重置 registrar / dispatcher 的静态缓存,避免跨用例共享状态影响断言。
|
|
/// </summary>
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
|
|
ClearRegistrarCaches();
|
|
ClearDispatcherCaches();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 在每个用例后清理静态缓存。
|
|
/// </summary>
|
|
[TearDown]
|
|
public void TearDown()
|
|
{
|
|
ClearRegistrarCaches();
|
|
ClearDispatcherCaches();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证 registrar 激活 generated registry 后,会把 request invoker provider 注册到容器中。
|
|
/// </summary>
|
|
[Test]
|
|
public void RegisterHandlers_Should_Register_Generated_Request_Invoker_Provider()
|
|
{
|
|
var generatedAssembly = CreateGeneratedRequestInvokerAssembly();
|
|
var container = new MicrosoftDiContainer();
|
|
|
|
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
|
|
|
var providers = container.GetAll<ICqrsRequestInvokerProvider>();
|
|
|
|
Assert.That(
|
|
providers.Select(static provider => provider.GetType()),
|
|
Is.EqualTo([typeof(GeneratedRequestInvokerProviderRegistry)]));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 验证 dispatcher 在首次创建 request binding 时,会优先消费 generated request invoker provider。
|
|
/// </summary>
|
|
[Test]
|
|
public async Task SendAsync_Should_Use_Generated_Request_Invoker_When_Provider_Is_Registered()
|
|
{
|
|
var generatedAssembly = CreateGeneratedRequestInvokerAssembly();
|
|
var container = new MicrosoftDiContainer();
|
|
|
|
CqrsTestRuntime.RegisterHandlers(container, generatedAssembly.Object);
|
|
container.Freeze();
|
|
|
|
var context = new ArchitectureContext(container);
|
|
var response = await context.SendRequestAsync(new GeneratedRequestInvokerRequest("payload"));
|
|
|
|
var requestBindings = GetDispatcherCacheField("RequestDispatchBindings");
|
|
var binding = GetRequestDispatchBindingValue(
|
|
requestBindings,
|
|
typeof(GeneratedRequestInvokerRequest),
|
|
typeof(string));
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(response, Is.EqualTo("generated:payload"));
|
|
Assert.That(binding, Is.Not.Null);
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建带有 generated request invoker registry 元数据的程序集替身。
|
|
/// </summary>
|
|
private static Mock<Assembly> CreateGeneratedRequestInvokerAssembly()
|
|
{
|
|
var generatedAssembly = new Mock<Assembly>();
|
|
generatedAssembly
|
|
.SetupGet(static assembly => assembly.FullName)
|
|
.Returns("GFramework.Cqrs.Tests.Cqrs.GeneratedRequestInvokerAssembly, Version=1.0.0.0");
|
|
generatedAssembly
|
|
.Setup(static assembly => assembly.GetCustomAttributes(typeof(CqrsHandlerRegistryAttribute), false))
|
|
.Returns([new CqrsHandlerRegistryAttribute(typeof(GeneratedRequestInvokerProviderRegistry))]);
|
|
return generatedAssembly;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清空 registrar 静态缓存。
|
|
/// </summary>
|
|
private static void ClearRegistrarCaches()
|
|
{
|
|
ClearCache(GetRegistrarCacheField("AssemblyMetadataCache"));
|
|
ClearCache(GetRegistrarCacheField("RegistryActivationMetadataCache"));
|
|
ClearCache(GetRegistrarCacheField("LoadableTypesCache"));
|
|
ClearCache(GetRegistrarCacheField("SupportedHandlerInterfacesCache"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清空 dispatcher 静态缓存。
|
|
/// </summary>
|
|
private static void ClearDispatcherCaches()
|
|
{
|
|
ClearCache(GetDispatcherCacheField("NotificationDispatchBindings"));
|
|
ClearCache(GetDispatcherCacheField("RequestDispatchBindings"));
|
|
ClearCache(GetDispatcherCacheField("StreamDispatchBindings"));
|
|
ClearCache(GetDispatcherCacheField("GeneratedRequestInvokers"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 通过反射读取 registrar 的静态缓存字段。
|
|
/// </summary>
|
|
private static object GetRegistrarCacheField(string fieldName)
|
|
{
|
|
var field = typeof(CqrsReflectionFallbackAttribute).Assembly
|
|
.GetType("GFramework.Cqrs.Internal.CqrsHandlerRegistrar", throwOnError: true)!
|
|
.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
|
Assert.That(field, Is.Not.Null, $"Missing registrar cache field {fieldName}.");
|
|
return field!.GetValue(null)
|
|
?? throw new InvalidOperationException($"Registrar cache field {fieldName} returned null.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 通过反射读取 dispatcher 的静态缓存字段。
|
|
/// </summary>
|
|
private static object GetDispatcherCacheField(string fieldName)
|
|
{
|
|
var field = typeof(CqrsReflectionFallbackAttribute).Assembly
|
|
.GetType("GFramework.Cqrs.Internal.CqrsDispatcher", throwOnError: true)!
|
|
.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
|
Assert.That(field, Is.Not.Null, $"Missing dispatcher cache field {fieldName}.");
|
|
return field!.GetValue(null)
|
|
?? throw new InvalidOperationException($"Dispatcher cache field {fieldName} returned null.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 清空目标缓存实例。
|
|
/// </summary>
|
|
private static void ClearCache(object cache)
|
|
{
|
|
_ = cache.GetType()
|
|
.GetMethod("Clear", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
|
|
.Invoke(cache, Array.Empty<object>());
|
|
}
|
|
|
|
/// <summary>
|
|
/// 读取指定请求/响应类型对当前缓存的 request dispatch binding。
|
|
/// </summary>
|
|
private static object? GetRequestDispatchBindingValue(object requestBindings, Type requestType, Type responseType)
|
|
{
|
|
var bindingBox = requestBindings.GetType()
|
|
.GetMethod("GetValueOrDefaultForTesting", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
|
|
.Invoke(requestBindings, [requestType, responseType]);
|
|
if (bindingBox is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return bindingBox.GetType()
|
|
.GetMethod("Get", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
|
|
.MakeGenericMethod(responseType)
|
|
.Invoke(bindingBox, Array.Empty<object>());
|
|
}
|
|
}
|