// Copyright (c) 2025-2026 GeWuYou
// SPDX-License-Identifier: Apache-2.0
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;
///
/// 验证 CQRS dispatcher 会缓存热路径中的 dispatch binding。
///
[TestFixture]
[NonParallelizable]
internal sealed class CqrsDispatcherCacheTests
{
private MicrosoftDiContainer? _container;
private ArchitectureContext? _context;
///
/// 初始化测试上下文。
///
[SetUp]
public void SetUp()
{
LoggerFactoryResolver.Provider = new ConsoleLoggerFactoryProvider();
_container = new MicrosoftDiContainer();
ConfigureDispatcherCacheFixture(_container);
_container.Freeze();
_context = new ArchitectureContext(_container);
DispatcherNotificationContextRefreshState.Reset();
DispatcherPipelineContextRefreshState.Reset();
DispatcherStreamPipelineOrderState.Reset();
DispatcherStreamContextRefreshState.Reset();
ClearDispatcherCaches();
}
///
/// 清理测试上下文引用。
///
[TearDown]
public void TearDown()
{
_context = null;
_container = null;
}
///
/// 验证相同消息类型重复分发时,不会重复扩张 dispatch binding 缓存。
///
[Test]
public async Task Dispatcher_Should_Cache_Dispatch_Bindings_After_First_Dispatch()
{
var notificationBindings = GetCacheField("NotificationDispatchBindings");
var requestBindings = GetCacheField("RequestDispatchBindings");
var streamBindings = GetCacheField("StreamDispatchBindings");
Assert.Multiple(() =>
{
Assert.That(
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification)),
Is.Null);
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
Is.Null);
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int)),
Is.Null);
Assert.That(
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int)),
Is.Null);
});
await _context!.SendRequestAsync(new DispatcherCacheRequest());
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
await _context.PublishAsync(new DispatcherCacheNotification());
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
var notificationAfterFirstDispatch =
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification));
var requestAfterFirstDispatch =
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int));
var pipelineAfterFirstDispatch =
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int));
var streamAfterFirstDispatch =
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int));
await _context.SendRequestAsync(new DispatcherCacheRequest());
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
await _context.PublishAsync(new DispatcherCacheNotification());
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
Assert.Multiple(() =>
{
Assert.That(notificationAfterFirstDispatch, Is.Not.Null);
Assert.That(requestAfterFirstDispatch, Is.Not.Null);
Assert.That(pipelineAfterFirstDispatch, Is.Not.Null);
Assert.That(streamAfterFirstDispatch, Is.Not.Null);
Assert.That(
GetSingleKeyCacheValue(notificationBindings, typeof(DispatcherCacheNotification)),
Is.SameAs(notificationAfterFirstDispatch));
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
Is.SameAs(requestAfterFirstDispatch));
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherPipelineCacheRequest), typeof(int)),
Is.SameAs(pipelineAfterFirstDispatch));
Assert.That(
GetPairCacheValue(streamBindings, typeof(DispatcherCacheStreamRequest), typeof(int)),
Is.SameAs(streamAfterFirstDispatch));
});
}
///
/// 验证 request dispatch binding 会按响应类型分别缓存,避免不同响应类型共用 object 结果桥接。
///
[Test]
public async Task Dispatcher_Should_Cache_Request_Dispatch_Bindings_Per_Response_Type()
{
var requestBindings = GetCacheField("RequestDispatchBindings");
await _context!.SendRequestAsync(new DispatcherCacheRequest());
await _context.SendRequestAsync(new DispatcherStringCacheRequest());
var intAfterFirstDispatch =
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int));
var stringAfterFirstDispatch =
GetPairCacheValue(requestBindings, typeof(DispatcherStringCacheRequest), typeof(string));
await _context.SendRequestAsync(new DispatcherCacheRequest());
await _context.SendRequestAsync(new DispatcherStringCacheRequest());
Assert.Multiple(() =>
{
Assert.That(intAfterFirstDispatch, Is.Not.Null);
Assert.That(stringAfterFirstDispatch, Is.Not.Null);
Assert.That(intAfterFirstDispatch, Is.Not.SameAs(stringAfterFirstDispatch));
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherCacheRequest), typeof(int)),
Is.SameAs(intAfterFirstDispatch));
Assert.That(
GetPairCacheValue(requestBindings, typeof(DispatcherStringCacheRequest), typeof(string)),
Is.SameAs(stringAfterFirstDispatch));
});
}
///
/// 验证 request 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
/// 让零行为请求在首次分发后不再重复查询容器,同时不同 dispatcher 不共享该实例级状态。
///
[Test]
public async Task Dispatcher_Should_Cache_Zero_Pipeline_Request_Presence_Per_Dispatcher_Instance()
{
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
var firstDispatcher = GetDispatcherFromContext(firstContext);
var secondDispatcher = GetDispatcherFromContext(secondContext);
using var isolatedContainer = CreateFrozenContainer();
var isolatedContext = new ArchitectureContext(isolatedContainer);
var isolatedDispatcher = GetDispatcherFromContext(isolatedContext);
AssertRequestBehaviorPresenceIsUnset(firstDispatcher, typeof(IPipelineBehavior));
AssertRequestBehaviorPresenceIsUnset(secondDispatcher, typeof(IPipelineBehavior));
AssertRequestBehaviorPresenceIsUnset(isolatedDispatcher, typeof(IPipelineBehavior));
AssertRequestBehaviorPresenceIsUnset(
firstDispatcher,
typeof(IPipelineBehavior));
await firstContext.SendRequestAsync(new DispatcherCacheRequest());
await firstContext.SendRequestAsync(new DispatcherPipelineCacheRequest());
var zeroPipelinePresence = GetRequestBehaviorPresenceCacheValue(
firstDispatcher,
typeof(IPipelineBehavior));
var onePipelinePresence = GetRequestBehaviorPresenceCacheValue(
firstDispatcher,
typeof(IPipelineBehavior));
AssertSharedDispatcherCacheState(
firstDispatcher,
secondDispatcher,
isolatedDispatcher,
zeroPipelinePresence,
onePipelinePresence);
await isolatedContext.SendRequestAsync(new DispatcherCacheRequest());
AssertRequestBehaviorPresenceEquals(
isolatedDispatcher,
typeof(IPipelineBehavior),
false);
}
///
/// 验证 stream 的“是否存在 pipeline behavior”判定会按 dispatcher 实例缓存,
/// 并与当前容器的实际服务可见性保持一致,同时不同 dispatcher 不共享该实例级状态。
///
[Test]
public async Task Dispatcher_Should_Cache_Stream_Behavior_Presence_Per_Dispatcher_Instance()
{
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
var firstDispatcher = GetDispatcherFromContext(firstContext);
var secondDispatcher = GetDispatcherFromContext(secondContext);
using var isolatedContainer = CreateFrozenContainer();
var isolatedContext = new ArchitectureContext(isolatedContainer);
var isolatedDispatcher = GetDispatcherFromContext(isolatedContext);
var zeroPipelineBehaviorType = typeof(IStreamPipelineBehavior);
var twoPipelineBehaviorType = typeof(IStreamPipelineBehavior);
var expectedZeroPipelinePresence = _container!.HasRegistration(zeroPipelineBehaviorType);
var expectedTwoPipelinePresence = _container.HasRegistration(twoPipelineBehaviorType);
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, zeroPipelineBehaviorType);
AssertStreamBehaviorPresenceIsUnset(secondDispatcher, zeroPipelineBehaviorType);
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
AssertStreamBehaviorPresenceIsUnset(firstDispatcher, twoPipelineBehaviorType);
await DrainAsync(firstContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
await DrainAsync(firstContext.CreateStream(new DispatcherStreamPipelineOrderRequest()));
var zeroPipelinePresence = GetStreamBehaviorPresenceCacheValue(
firstDispatcher,
zeroPipelineBehaviorType);
var twoPipelinePresence = GetStreamBehaviorPresenceCacheValue(
firstDispatcher,
twoPipelineBehaviorType);
AssertSharedStreamDispatcherCacheState(
firstDispatcher,
secondDispatcher,
isolatedDispatcher,
zeroPipelinePresence,
twoPipelinePresence,
zeroPipelineBehaviorType,
expectedZeroPipelinePresence,
expectedTwoPipelinePresence);
await DrainAsync(isolatedContext.CreateStream(new DispatcherZeroPipelineStreamRequest()));
AssertStreamBehaviorPresenceEquals(isolatedDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
}
///
/// 验证 request pipeline executor 会按行为数量在 binding 内首次创建并在后续分发中复用。
///
[Test]
public async Task Dispatcher_Should_Cache_Request_Pipeline_Executors_Per_Behavior_Count()
{
var requestBindings = GetCacheField("RequestDispatchBindings");
Assert.Multiple(() =>
{
Assert.That(
GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineCacheRequest),
typeof(int),
1),
Is.Null);
Assert.That(
GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineOrderCacheRequest),
typeof(int),
2),
Is.Null);
});
await _context!.SendRequestAsync(new DispatcherPipelineCacheRequest());
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
var singleBehaviorExecutor = GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineCacheRequest),
typeof(int),
1);
var twoBehaviorExecutor = GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineOrderCacheRequest),
typeof(int),
2);
await _context.SendRequestAsync(new DispatcherPipelineCacheRequest());
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
Assert.Multiple(() =>
{
Assert.That(singleBehaviorExecutor, Is.Not.Null);
Assert.That(twoBehaviorExecutor, Is.Not.Null);
Assert.That(singleBehaviorExecutor, Is.Not.SameAs(twoBehaviorExecutor));
Assert.That(
GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineCacheRequest),
typeof(int),
1),
Is.SameAs(singleBehaviorExecutor));
Assert.That(
GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineOrderCacheRequest),
typeof(int),
2),
Is.SameAs(twoBehaviorExecutor));
});
}
///
/// 验证 stream pipeline executor 会按行为数量在 binding 内首次创建并在后续建流中复用。
///
[Test]
public async Task Dispatcher_Should_Cache_Stream_Pipeline_Executors_Per_Behavior_Count()
{
var streamBindings = GetCacheField("StreamDispatchBindings");
Assert.Multiple(() =>
{
Assert.That(
GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherCacheStreamRequest),
typeof(int),
1),
Is.Null);
Assert.That(
GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherStreamPipelineOrderRequest),
typeof(int),
2),
Is.Null);
});
await DrainAsync(_context!.CreateStream(new DispatcherCacheStreamRequest()));
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
var singleBehaviorExecutor = GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherCacheStreamRequest),
typeof(int),
1);
var twoBehaviorExecutor = GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherStreamPipelineOrderRequest),
typeof(int),
2);
await DrainAsync(_context.CreateStream(new DispatcherCacheStreamRequest()));
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
Assert.Multiple(() =>
{
Assert.That(singleBehaviorExecutor, Is.Not.Null);
Assert.That(twoBehaviorExecutor, Is.Not.Null);
Assert.That(singleBehaviorExecutor, Is.Not.SameAs(twoBehaviorExecutor));
Assert.That(
GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherCacheStreamRequest),
typeof(int),
1),
Is.SameAs(singleBehaviorExecutor));
Assert.That(
GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherStreamPipelineOrderRequest),
typeof(int),
2),
Is.SameAs(twoBehaviorExecutor));
});
}
///
/// 验证复用缓存的 request pipeline executor 后,行为顺序和最终处理器顺序保持不变。
///
[Test]
public async Task Dispatcher_Should_Preserve_Request_Pipeline_Order_When_Reusing_Cached_Executor()
{
DispatcherPipelineOrderState.Reset();
await _context!.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
var firstInvocation = DispatcherPipelineOrderState.Steps.ToArray();
DispatcherPipelineOrderState.Reset();
await _context.SendRequestAsync(new DispatcherPipelineOrderCacheRequest());
var secondInvocation = DispatcherPipelineOrderState.Steps.ToArray();
var expectedOrder = new[]
{
"Outer:Before",
"Inner:Before",
"Handler",
"Inner:After",
"Outer:After"
};
Assert.Multiple(() =>
{
Assert.That(firstInvocation, Is.EqualTo(expectedOrder));
Assert.That(secondInvocation, Is.EqualTo(expectedOrder));
});
}
///
/// 验证复用缓存的 stream pipeline executor 后,行为顺序和最终处理器顺序保持不变。
///
[Test]
public async Task Dispatcher_Should_Preserve_Stream_Pipeline_Order_When_Reusing_Cached_Executor()
{
DispatcherStreamPipelineOrderState.Reset();
await DrainAsync(_context!.CreateStream(new DispatcherStreamPipelineOrderRequest()));
var firstInvocation = DispatcherStreamPipelineOrderState.Steps.ToArray();
DispatcherStreamPipelineOrderState.Reset();
await DrainAsync(_context.CreateStream(new DispatcherStreamPipelineOrderRequest()));
var secondInvocation = DispatcherStreamPipelineOrderState.Steps.ToArray();
var expectedOrder = new[]
{
"Outer:Before",
"Inner:Before",
"Handler",
"Inner:After",
"Outer:After"
};
Assert.Multiple(() =>
{
Assert.That(firstInvocation, Is.EqualTo(expectedOrder));
Assert.That(secondInvocation, Is.EqualTo(expectedOrder));
});
}
///
/// 验证缓存的 request pipeline executor 在重复分发时仍会重新解析 handler/behavior,
/// 并为当次实例重新注入当前架构上下文。
///
[Test]
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Request_Pipeline_Executor()
{
DispatcherPipelineContextRefreshState.Reset();
var requestBindings = GetCacheField("RequestDispatchBindings");
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
await firstContext.SendRequestAsync(new DispatcherPipelineContextRefreshRequest("first"));
var executorAfterFirstDispatch = GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineContextRefreshRequest),
typeof(int),
1);
await secondContext.SendRequestAsync(new DispatcherPipelineContextRefreshRequest("second"));
var executorAfterSecondDispatch = GetRequestPipelineExecutorValue(
requestBindings,
typeof(DispatcherPipelineContextRefreshRequest),
typeof(int),
1);
var behaviorSnapshots = DispatcherPipelineContextRefreshState.BehaviorSnapshots.ToArray();
var handlerSnapshots = DispatcherPipelineContextRefreshState.HandlerSnapshots.ToArray();
Assert.Multiple(() =>
{
Assert.That(executorAfterFirstDispatch, Is.Not.Null);
Assert.That(executorAfterSecondDispatch, Is.SameAs(executorAfterFirstDispatch));
Assert.That(behaviorSnapshots, Has.Length.EqualTo(2));
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
Assert.That(behaviorSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(behaviorSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(behaviorSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(behaviorSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(behaviorSnapshots[1].Context, Is.Not.SameAs(behaviorSnapshots[0].Context));
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
});
}
///
/// 验证缓存的 notification dispatch binding 在重复分发时仍会重新解析 handler,
/// 并为当次实例重新注入当前架构上下文。
///
[Test]
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Notification_Dispatch_Binding()
{
DispatcherNotificationContextRefreshState.Reset();
var notificationBindings = GetCacheField("NotificationDispatchBindings");
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
await firstContext.PublishAsync(new DispatcherNotificationContextRefreshNotification("first"));
var bindingAfterFirstDispatch = GetSingleKeyCacheValue(
notificationBindings,
typeof(DispatcherNotificationContextRefreshNotification));
await secondContext.PublishAsync(new DispatcherNotificationContextRefreshNotification("second"));
var bindingAfterSecondDispatch = GetSingleKeyCacheValue(
notificationBindings,
typeof(DispatcherNotificationContextRefreshNotification));
var handlerSnapshots = DispatcherNotificationContextRefreshState.HandlerSnapshots.ToArray();
Assert.Multiple(() =>
{
Assert.That(bindingAfterFirstDispatch, Is.Not.Null);
Assert.That(bindingAfterSecondDispatch, Is.SameAs(bindingAfterFirstDispatch));
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
});
}
///
/// 验证缓存的 stream dispatch binding 在重复建流时仍会重新解析 handler,
/// 并为当次实例重新注入当前架构上下文。
///
[Test]
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Stream_Dispatch_Binding()
{
DispatcherStreamContextRefreshState.Reset();
var streamBindings = GetCacheField("StreamDispatchBindings");
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
var firstStream = firstContext.CreateStream(new DispatcherStreamContextRefreshRequest("first"));
await DrainAsync(firstStream);
var bindingAfterFirstDispatch = GetPairCacheValue(
streamBindings,
typeof(DispatcherStreamContextRefreshRequest),
typeof(int));
var secondStream = secondContext.CreateStream(new DispatcherStreamContextRefreshRequest("second"));
await DrainAsync(secondStream);
var bindingAfterSecondDispatch = GetPairCacheValue(
streamBindings,
typeof(DispatcherStreamContextRefreshRequest),
typeof(int));
var handlerSnapshots = DispatcherStreamContextRefreshState.HandlerSnapshots.ToArray();
Assert.Multiple(() =>
{
Assert.That(bindingAfterFirstDispatch, Is.Not.Null);
Assert.That(bindingAfterSecondDispatch, Is.SameAs(bindingAfterFirstDispatch));
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
});
}
///
/// 验证缓存的 stream pipeline executor 在重复建流时仍会重新解析 behavior/handler,
/// 并为当次实例重新注入当前架构上下文。
///
[Test]
public async Task Dispatcher_Should_Reinject_Current_Context_When_Reusing_Cached_Stream_Pipeline_Executor()
{
DispatcherStreamContextRefreshState.Reset();
var streamBindings = GetCacheField("StreamDispatchBindings");
var firstContext = new ArchitectureContext(_container!);
var secondContext = new ArchitectureContext(_container!);
await DrainAsync(firstContext.CreateStream(new DispatcherStreamContextRefreshRequest("first")));
var executorAfterFirstDispatch = GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherStreamContextRefreshRequest),
typeof(int),
1);
await DrainAsync(secondContext.CreateStream(new DispatcherStreamContextRefreshRequest("second")));
var executorAfterSecondDispatch = GetStreamPipelineExecutorValue(
streamBindings,
typeof(DispatcherStreamContextRefreshRequest),
typeof(int),
1);
var behaviorSnapshots = DispatcherStreamContextRefreshState.BehaviorSnapshots.ToArray();
var handlerSnapshots = DispatcherStreamContextRefreshState.HandlerSnapshots.ToArray();
Assert.Multiple(() =>
{
Assert.That(executorAfterFirstDispatch, Is.Not.Null);
Assert.That(executorAfterSecondDispatch, Is.SameAs(executorAfterFirstDispatch));
Assert.That(behaviorSnapshots, Has.Length.EqualTo(2));
Assert.That(handlerSnapshots, Has.Length.EqualTo(2));
Assert.That(behaviorSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(behaviorSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(behaviorSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(behaviorSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(behaviorSnapshots[1].Context, Is.Not.SameAs(behaviorSnapshots[0].Context));
Assert.That(handlerSnapshots[0].DispatchId, Is.EqualTo("first"));
Assert.That(handlerSnapshots[0].Context, Is.SameAs(firstContext));
Assert.That(handlerSnapshots[1].DispatchId, Is.EqualTo("second"));
Assert.That(handlerSnapshots[1].Context, Is.SameAs(secondContext));
Assert.That(handlerSnapshots[1].Context, Is.Not.SameAs(handlerSnapshots[0].Context));
Assert.That(handlerSnapshots[1].InstanceId, Is.Not.EqualTo(handlerSnapshots[0].InstanceId));
});
}
///
/// 通过反射读取 dispatcher 的静态缓存对象。
///
private static object GetCacheField(string fieldName)
{
var dispatcherType = GetDispatcherType();
var field = dispatcherType.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.");
}
///
/// 从架构上下文中解析当前延迟创建的 dispatcher 实例,便于验证其实例级热路径缓存。
///
private static object GetDispatcherFromContext(ArchitectureContext context)
{
ArgumentNullException.ThrowIfNull(context);
var lazyRuntimeField = typeof(ArchitectureContext).GetField(
"_cqrsRuntime",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(lazyRuntimeField, Is.Not.Null, "Missing ArchitectureContext._cqrsRuntime field.");
var lazyRuntime = lazyRuntimeField!.GetValue(context)
?? throw new InvalidOperationException(
"ArchitectureContext._cqrsRuntime returned null.");
var lazyValueProperty = lazyRuntime.GetType().GetProperty(
"Value",
BindingFlags.Instance | BindingFlags.Public);
Assert.That(lazyValueProperty, Is.Not.Null, "Missing Lazy.Value accessor.");
return lazyValueProperty!.GetValue(lazyRuntime)
?? throw new InvalidOperationException("Resolved CQRS runtime instance was null.");
}
///
/// 创建与当前 fixture 注册形状一致、但拥有独立 runtime 实例的冻结容器,
/// 用于验证 dispatcher 的实例级缓存不会跨容器共享。
///
private static MicrosoftDiContainer CreateFrozenContainer()
{
var container = new MicrosoftDiContainer();
ConfigureDispatcherCacheFixture(container);
container.Freeze();
return container;
}
///
/// 组装当前 fixture 依赖的 CQRS 容器注册形状,确保默认上下文与隔离容器复用同一份装配基线。
///
/// 待补齐 CQRS 注册的目标容器。
private static void ConfigureDispatcherCacheFixture(MicrosoftDiContainer container)
{
container.RegisterCqrsPipelineBehavior();
container.RegisterCqrsPipelineBehavior();
container.RegisterCqrsPipelineBehavior();
container.RegisterCqrsPipelineBehavior();
container.RegisterCqrsStreamPipelineBehavior();
container.RegisterCqrsStreamPipelineBehavior();
container.RegisterCqrsStreamPipelineBehavior();
container.RegisterCqrsStreamPipelineBehavior();
CqrsTestRuntime.RegisterHandlers(
container,
typeof(CqrsDispatcherCacheTests).Assembly,
typeof(ArchitectureContext).Assembly);
}
///
/// 清空本测试依赖的 dispatcher 静态缓存,避免跨用例共享进程级状态导致断言漂移。
///
private static void ClearDispatcherCaches()
{
ClearCache(GetCacheField("NotificationDispatchBindings"));
ClearCache(GetCacheField("RequestDispatchBindings"));
ClearCache(GetCacheField("StreamDispatchBindings"));
}
///
/// 读取单键缓存中当前保存的对象。
///
private static object? GetSingleKeyCacheValue(object cache, Type key)
{
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", key);
}
///
/// 读取双键缓存中当前保存的对象。
///
private static object? GetPairCacheValue(object cache, Type primaryType, Type secondaryType)
{
return InvokeInstanceMethod(cache, "GetValueOrDefaultForTesting", primaryType, secondaryType);
}
///
/// 读取指定 dispatcher 实例中当前保存的 request behavior presence 缓存项。
///
private static object? GetRequestBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
{
var field = dispatcher.GetType().GetField(
"_requestBehaviorPresenceCache",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(field, Is.Not.Null, "Missing dispatcher request behavior presence cache field.");
var cache = field!.GetValue(dispatcher)
?? throw new InvalidOperationException(
"Dispatcher request behavior presence cache returned null.");
var tryGetValueMethod = cache.GetType().GetMethod(
"TryGetValue",
BindingFlags.Instance | BindingFlags.Public);
Assert.That(tryGetValueMethod, Is.Not.Null, "Missing ConcurrentDictionary.TryGetValue accessor.");
object?[] arguments = [behaviorType, null];
var found = (bool)(tryGetValueMethod!.Invoke(cache, arguments)
?? throw new InvalidOperationException(
"ConcurrentDictionary.TryGetValue returned null."));
return found ? arguments[1] : null;
}
///
/// 读取指定 dispatcher 实例中当前保存的 stream behavior presence 缓存项。
///
private static object? GetStreamBehaviorPresenceCacheValue(object dispatcher, Type behaviorType)
{
var field = dispatcher.GetType().GetField(
"_streamBehaviorPresenceCache",
BindingFlags.Instance | BindingFlags.NonPublic);
Assert.That(field, Is.Not.Null, "Missing dispatcher stream behavior presence cache field.");
var cache = field!.GetValue(dispatcher)
?? throw new InvalidOperationException(
"Dispatcher stream behavior presence cache returned null.");
var tryGetValueMethod = cache.GetType().GetMethod(
"TryGetValue",
BindingFlags.Instance | BindingFlags.Public);
Assert.That(tryGetValueMethod, Is.Not.Null, "Missing ConcurrentDictionary.TryGetValue accessor.");
object?[] arguments = [behaviorType, null];
var found = (bool)(tryGetValueMethod!.Invoke(cache, arguments)
?? throw new InvalidOperationException(
"ConcurrentDictionary.TryGetValue returned null."));
return found ? arguments[1] : null;
}
///
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项尚未建立。
///
private static void AssertRequestBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
{
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
}
///
/// 断言指定 dispatcher 上某个 request behavior presence 缓存项等于预期值。
///
private static void AssertRequestBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
{
Assert.That(GetRequestBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
}
///
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项尚未建立。
///
private static void AssertStreamBehaviorPresenceIsUnset(object dispatcher, Type behaviorType)
{
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.Null);
}
///
/// 断言指定 dispatcher 上某个 stream behavior presence 缓存项等于预期值。
///
private static void AssertStreamBehaviorPresenceEquals(object dispatcher, Type behaviorType, bool expected)
{
Assert.That(GetStreamBehaviorPresenceCacheValue(dispatcher, behaviorType), Is.EqualTo(expected));
}
///
/// 断言同一容器解析出的 dispatcher 会共享实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
///
private static void AssertSharedDispatcherCacheState(
object firstDispatcher,
object secondDispatcher,
object isolatedDispatcher,
object? zeroPipelinePresence,
object? onePipelinePresence)
{
Assert.Multiple(() =>
{
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
Assert.That(zeroPipelinePresence, Is.EqualTo(false));
Assert.That(onePipelinePresence, Is.EqualTo(true));
AssertRequestBehaviorPresenceEquals(
secondDispatcher,
typeof(IPipelineBehavior),
false);
AssertRequestBehaviorPresenceIsUnset(
isolatedDispatcher,
typeof(IPipelineBehavior));
});
}
///
/// 断言同一容器解析出的 dispatcher 会共享 stream 的实例级缓存,而另一独立容器的 dispatcher 不会提前命中。
///
private static void AssertSharedStreamDispatcherCacheState(
object firstDispatcher,
object secondDispatcher,
object isolatedDispatcher,
object? zeroPipelinePresence,
object? twoPipelinePresence,
Type zeroPipelineBehaviorType,
bool expectedZeroPipelinePresence,
bool expectedTwoPipelinePresence)
{
Assert.Multiple(() =>
{
Assert.That(secondDispatcher, Is.SameAs(firstDispatcher));
Assert.That(zeroPipelinePresence, Is.EqualTo(expectedZeroPipelinePresence));
Assert.That(twoPipelinePresence, Is.EqualTo(expectedTwoPipelinePresence));
AssertStreamBehaviorPresenceEquals(secondDispatcher, zeroPipelineBehaviorType, expectedZeroPipelinePresence);
AssertStreamBehaviorPresenceIsUnset(isolatedDispatcher, zeroPipelineBehaviorType);
});
}
///
/// 读取 request dispatch binding 中指定行为数量的 pipeline executor 缓存项。
///
/// dispatcher 内部的 request binding 缓存对象。
/// 要读取的请求运行时类型。
/// 要读取的响应运行时类型。
/// 目标 executor 对应的行为数量。
/// 已缓存的 executor;若 binding 或 executor 尚未建立则返回 。
private static object? GetRequestPipelineExecutorValue(
object requestBindings,
Type requestType,
Type responseType,
int behaviorCount)
{
var binding = GetRequestDispatchBindingValue(requestBindings, requestType, responseType);
return binding is null
? null
: InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount);
}
///
/// 读取 stream dispatch binding 中指定行为数量的 pipeline executor 缓存项。
///
/// dispatcher 内部的 stream binding 缓存对象。
/// 要读取的流式请求运行时类型。
/// 要读取的响应元素类型。
/// 目标 executor 对应的行为数量。
/// 已缓存的 executor;若 binding 或 executor 尚未建立则返回 。
private static object? GetStreamPipelineExecutorValue(
object streamBindings,
Type requestType,
Type responseType,
int behaviorCount)
{
var binding = GetStreamDispatchBindingValue(streamBindings, requestType, responseType);
return binding is null
? null
: InvokeInstanceMethod(binding, "GetPipelineExecutorForTesting", behaviorCount);
}
///
/// 调用缓存实例上的无参清理方法。
///
private static void ClearCache(object cache)
{
_ = InvokeInstanceMethod(cache, "Clear");
}
///
/// 调用缓存对象上的实例方法。
///
private static object? InvokeInstanceMethod(object target, string methodName, params object[] arguments)
{
var method = target.GetType().GetMethod(
methodName,
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null, $"Missing cache method {target.GetType().FullName}.{methodName}.");
return method!.Invoke(target, arguments);
}
///
/// 读取指定请求/响应类型对对应的强类型 request dispatch binding。
///
/// dispatcher 内部的 request binding 缓存对象。
/// 要读取的请求运行时类型。
/// 要读取的响应运行时类型。
/// 强类型 binding;若缓存尚未建立则返回 。
private static object? GetRequestDispatchBindingValue(object requestBindings, Type requestType, Type responseType)
{
var bindingBox = GetPairCacheValue(requestBindings, requestType, responseType);
if (bindingBox is null)
{
return null;
}
var method = bindingBox.GetType().GetMethod(
"Get",
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
Assert.That(method, Is.Not.Null, $"Missing request binding accessor on {bindingBox.GetType().FullName}.");
return method!
.MakeGenericMethod(responseType)
.Invoke(bindingBox, Array.Empty