mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-07 00:39:00 +08:00
test(cqrs): 补充上下文前置条件失败回归
- 新增 dispatcher 上下文校验测试,锁定非 IArchitectureContext 上下文的 request、notification 与 stream 失败语义 - 通过公开 runtime 工厂与最小容器 mock 覆盖调用前校验路径,不改 runtime 实现 - 更新 cqrs-rewrite 跟踪与 trace,记录 RP-060 的验证结论
This commit is contained in:
parent
57d848546f
commit
52b9ddd4a7
@ -0,0 +1,165 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GFramework.Core.Abstractions.Ioc;
|
||||
using GFramework.Core.Abstractions.Logging;
|
||||
using GFramework.Cqrs.Abstractions.Cqrs;
|
||||
using GFramework.Cqrs.Cqrs;
|
||||
using GFramework.Cqrs.Tests.Logging;
|
||||
|
||||
namespace GFramework.Cqrs.Tests.Cqrs;
|
||||
|
||||
/// <summary>
|
||||
/// 验证默认 dispatcher 在上下文注入前置条件不满足时的失败语义。
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
internal sealed class CqrsDispatcherContextValidationTests
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证当 request handler 需要上下文注入、但当前 CQRS 上下文不实现 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContext" /> 时,
|
||||
/// dispatcher 会在调用前显式失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void SendAsync_Should_Throw_When_Context_Does_Not_Implement_IArchitectureContext()
|
||||
{
|
||||
var runtime = CreateRuntime(
|
||||
container =>
|
||||
{
|
||||
container
|
||||
.Setup(currentContainer => currentContainer.Get(typeof(IRequestHandler<ContextAwareRequest, int>)))
|
||||
.Returns(new ContextAwareRequestHandler());
|
||||
container
|
||||
.Setup(currentContainer => currentContainer.GetAll(typeof(IPipelineBehavior<ContextAwareRequest, int>)))
|
||||
.Returns(Array.Empty<object>());
|
||||
});
|
||||
|
||||
Assert.That(
|
||||
async () => await runtime.SendAsync(new FakeCqrsContext(), new ContextAwareRequest()).ConfigureAwait(false),
|
||||
Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 notification handler 需要上下文注入、但当前 CQRS 上下文不实现 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContext" /> 时,
|
||||
/// dispatcher 会在发布前显式失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void PublishAsync_Should_Throw_When_Context_Does_Not_Implement_IArchitectureContext()
|
||||
{
|
||||
var runtime = CreateRuntime(
|
||||
container =>
|
||||
{
|
||||
container
|
||||
.Setup(currentContainer => currentContainer.GetAll(typeof(INotificationHandler<ContextAwareNotification>)))
|
||||
.Returns([new ContextAwareNotificationHandler()]);
|
||||
});
|
||||
|
||||
Assert.That(
|
||||
async () => await runtime.PublishAsync(new FakeCqrsContext(), new ContextAwareNotification()).ConfigureAwait(false),
|
||||
Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证当 stream handler 需要上下文注入、但当前 CQRS 上下文不实现 <see cref="GFramework.Core.Abstractions.Architectures.IArchitectureContext" /> 时,
|
||||
/// dispatcher 会在建流前显式失败。
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void CreateStream_Should_Throw_When_Context_Does_Not_Implement_IArchitectureContext()
|
||||
{
|
||||
var runtime = CreateRuntime(
|
||||
container =>
|
||||
{
|
||||
container
|
||||
.Setup(currentContainer => currentContainer.Get(typeof(IStreamRequestHandler<ContextAwareStreamRequest, int>)))
|
||||
.Returns(new ContextAwareStreamHandler());
|
||||
});
|
||||
|
||||
Assert.That(
|
||||
() => runtime.CreateStream(new FakeCqrsContext(), new ContextAwareStreamRequest()),
|
||||
Throws.InvalidOperationException.With.Message.Contains("does not implement IArchitectureContext"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个只满足当前测试最小依赖面的 dispatcher runtime。
|
||||
/// </summary>
|
||||
/// <param name="configureContainer">对容器 mock 的额外配置。</param>
|
||||
/// <returns>默认 CQRS runtime。</returns>
|
||||
private static GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime CreateRuntime(
|
||||
Action<Mock<IIocContainer>> configureContainer)
|
||||
{
|
||||
var container = new Mock<IIocContainer>(MockBehavior.Strict);
|
||||
var logger = new TestLogger("CqrsDispatcherContextValidationTests", LogLevel.Debug);
|
||||
|
||||
configureContainer(container);
|
||||
return CqrsRuntimeFactory.CreateRuntime(container.Object, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为失败语义测试提供最小 CQRS 上下文标记,但故意不实现架构上下文能力。
|
||||
/// </summary>
|
||||
private sealed class FakeCqrsContext : ICqrsContext
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 request 上下文校验提供最小测试请求。
|
||||
/// </summary>
|
||||
private sealed record ContextAwareRequest : IRequest<int>;
|
||||
|
||||
/// <summary>
|
||||
/// 为 notification 上下文校验提供最小测试通知。
|
||||
/// </summary>
|
||||
private sealed record ContextAwareNotification : INotification;
|
||||
|
||||
/// <summary>
|
||||
/// 为 stream 上下文校验提供最小测试请求。
|
||||
/// </summary>
|
||||
private sealed record ContextAwareStreamRequest : IStreamRequest<int>;
|
||||
|
||||
/// <summary>
|
||||
/// 为 request 上下文校验提供需要注入架构上下文的最小 handler。
|
||||
/// </summary>
|
||||
private sealed class ContextAwareRequestHandler : CqrsContextAwareHandlerBase, IRequestHandler<ContextAwareRequest, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回固定结果;当前测试只关心调用前的上下文校验。
|
||||
/// </summary>
|
||||
public ValueTask<int> Handle(ContextAwareRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.FromResult(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 notification 上下文校验提供需要注入架构上下文的最小 handler。
|
||||
/// </summary>
|
||||
private sealed class ContextAwareNotificationHandler
|
||||
: CqrsContextAwareHandlerBase,
|
||||
INotificationHandler<ContextAwareNotification>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回已完成任务;当前测试只关心调用前的上下文校验。
|
||||
/// </summary>
|
||||
public ValueTask Handle(ContextAwareNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
return ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为 stream 上下文校验提供需要注入架构上下文的最小 handler。
|
||||
/// </summary>
|
||||
private sealed class ContextAwareStreamHandler
|
||||
: CqrsContextAwareHandlerBase,
|
||||
IStreamRequestHandler<ContextAwareStreamRequest, int>
|
||||
{
|
||||
/// <summary>
|
||||
/// 返回一个最小流;当前测试只关心建流前的上下文校验。
|
||||
/// </summary>
|
||||
public async IAsyncEnumerable<int> Handle(
|
||||
ContextAwareStreamRequest request,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
yield return 1;
|
||||
await ValueTask.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 当前恢复点
|
||||
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-059`
|
||||
- 恢复点编号:`CQRS-REWRITE-RP-060`
|
||||
- 当前阶段:`Phase 8`
|
||||
- 当前焦点:
|
||||
- 当前功能历史已归档,active 跟踪仅保留 `Phase 8` 主线的恢复入口
|
||||
@ -19,6 +19,7 @@ CQRS 迁移与收敛。
|
||||
- 已补充 dispatcher pipeline executor 缓存与双行为顺序回归,锁定缓存复用后仍保持现有行为执行顺序
|
||||
- 已补充 cached request pipeline executor 的上下文刷新回归,锁定 executor 复用时仍会为当次 handler / singleton behavior 重新注入当前 `ArchitectureContext`
|
||||
- 已补充 cached notification / stream dispatch binding 的上下文刷新回归,锁定 binding 复用时仍会为当次 handler 重新注入当前 `ArchitectureContext`
|
||||
- 已补充非 `IArchitectureContext` 的 dispatcher 失败语义回归,锁定 context-aware request / notification / stream handler 在注入前置条件不满足时会显式抛出异常
|
||||
- 已完成 generated registry 激活路径收敛:`CqrsHandlerRegistrar` 现优先复用缓存工厂委托,避免重复 `ConstructorInfo.Invoke`
|
||||
- 已补充私有无参构造 generated registry 的回归测试,确保兼容现有生成器产物
|
||||
- 已修正 pointer / function pointer 泛型合同的错误覆盖:生成器不再为这两类类型发射 precise runtime type 重建代码
|
||||
@ -101,6 +102,10 @@ CQRS 迁移与收敛。
|
||||
- `GFramework.Cqrs.Tests` 已新增 `DispatcherNotificationContextRefresh*` 与 `DispatcherStreamContextRefresh*` 测试替身,分别记录 notification handler 与 stream handler 在重复分发时观察到的实例身份与 `ArchitectureContext`
|
||||
- `CqrsDispatcherCacheTests` 现明确断言:同一个 cached notification / stream dispatch binding 在重复分发时会继续命中同一 binding,但不会跨分发保留旧上下文
|
||||
- 本轮定向测试未暴露新的 runtime 缺口,因此没有改动 `GFramework.Cqrs/Internal/CqrsDispatcher.cs`
|
||||
- `2026-04-29` 已完成一轮 dispatcher 上下文前置条件失败语义回归:
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs` 已通过公开工厂 `CqrsRuntimeFactory.CreateRuntime(...)` 锁定默认 dispatcher 的失败语义
|
||||
- 当 context-aware request / notification / stream handler 遇到仅实现 `ICqrsContext`、但未实现 `IArchitectureContext` 的上下文时,dispatcher 会在调用前显式抛出 `InvalidOperationException`
|
||||
- 本轮只补测试,不改 runtime 实现与文档口径
|
||||
- `2026-04-29` 已接受一轮 delegated 叶子级 fallback 合同测试:
|
||||
- `GFramework.Cqrs.Tests/Cqrs/CqrsReflectionFallbackAttributeTests.cs` 已锁定空 marker、字符串 fallback 名称去空/去重/排序、直接 `Type` fallback 去空/去重/排序与空参数数组防御语义
|
||||
- 当前 runtime 读取程序集级 fallback 元数据时所依赖的 attribute 归一化合同,现已有独立叶子级测试文件覆盖
|
||||
@ -140,6 +145,9 @@ CQRS 迁移与收敛。
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherCacheTests"`
|
||||
- 结果:通过
|
||||
- 备注:`7/7` 测试通过;本轮新增 cached notification / stream binding 上下文刷新回归,确认 binding 复用时仍按当次分发重新注入上下文
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherContextValidationTests"`
|
||||
- 结果:通过
|
||||
- 备注:`3/3` 测试通过;本轮锁定默认 dispatcher 对非 `IArchitectureContext` 上下文的 request / notification / stream 失败语义,且未引入新增 warning
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --no-restore -p:RestoreFallbackFolders= -m:1 -nodeReuse:false`
|
||||
- 结果:通过
|
||||
- 备注:`63/63` 测试通过;当前沙箱限制了 MSBuild named pipe,验证需在提权环境下运行
|
||||
@ -209,6 +217,6 @@ CQRS 迁移与收敛。
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 继续 `Phase 8` 主线,优先再找一个收益明确且写集独立的 generator 或 registrar/dispatcher 热点;当前工作区若提交主线程 notification / stream 回归批次,相对 `origin/main` 的累计 diff 将达到 `29 files`,仍低于本轮 `gframework-batch-boot 50` 的主要 stop condition
|
||||
1. 继续 `Phase 8` 主线,优先再找一个收益明确且写集独立的 generator 或 registrar/dispatcher 热点;当前工作区若提交 dispatcher 上下文前置条件回归批次,相对 `origin/main` 的累计 diff 将达到 `31 files`,仍低于本轮 `gframework-batch-boot 50` 的主要 stop condition
|
||||
2. 若继续文档主线,优先再扫教程入口页与 API 参考中的 CQRS 采用说明,确认是否还有旧 Command / Query 迁移口径残留
|
||||
3. 若后续再出现新的 PR review 或 review thread 变化,再重新执行 `$gframework-pr-review` 作为独立验证步骤
|
||||
|
||||
@ -2,6 +2,23 @@
|
||||
|
||||
## 2026-04-29
|
||||
|
||||
### 阶段:dispatcher 上下文前置条件失败语义回归(CQRS-REWRITE-RP-060)
|
||||
|
||||
- 延续 `gframework-batch-boot 50` 的 `Phase 8` 主线,本轮选择一个新的单文件测试切片:锁定默认 dispatcher 对“仅实现 `ICqrsContext`、但未实现 `IArchitectureContext` 的上下文”会如何失败
|
||||
- 主线程先复核当前公开契约与实现后确认:
|
||||
- `GFramework.Cqrs.Abstractions.Cqrs.ICqrsRuntime` 的 XML 文档已经把这类失败语义写成公开契约
|
||||
- `CqrsDispatcher.PrepareHandler(...)` 当前正是唯一的上下文前置条件检查点,因此本轮最稳妥的切片仍是测试补强,而不是继续改 runtime
|
||||
- 已完成的测试补强:
|
||||
- 新增 `GFramework.Cqrs.Tests/Cqrs/CqrsDispatcherContextValidationTests.cs`
|
||||
- 通过 `CqrsRuntimeFactory.CreateRuntime(...)` + `Mock<IIocContainer>` 构造最小 runtime,分别锁定 request、notification、stream 三条路径的失败语义
|
||||
- 三个测试都只在需要上下文注入的 handler 已解析出来时触发,避免把“找不到 handler”与“上下文不满足注入前置条件”混淆成同一种异常
|
||||
- 定向验证已通过:
|
||||
- `dotnet test GFramework.Cqrs.Tests/GFramework.Cqrs.Tests.csproj -c Release --filter "FullyQualifiedName~GFramework.Cqrs.Tests.Cqrs.CqrsDispatcherContextValidationTests"`
|
||||
- `3/3` passed
|
||||
- 结果:
|
||||
- 本轮只补测试,不改 `GFramework.Cqrs/Internal/CqrsDispatcher.cs`
|
||||
- 若连同当前工作区一起计算,当前分支相对 `origin/main` 的累计 diff 将达到 `31 files`
|
||||
|
||||
### 阶段:notification / stream binding 上下文刷新回归(CQRS-REWRITE-RP-059)
|
||||
|
||||
- 延续 `gframework-batch-boot 50` 的 `Phase 8` 主线,本轮继续沿着上一批 dispatcher cached executor 上下文回归往外扩一圈,但只覆盖 notification / stream 两条非 request 路径
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user