DocumentStructure 已有图校验、租户 Store 和部分读侧测试,但商业 GA 必须证明模板应用不会留下半棵树、收藏不会泄漏失权资源、Purge 不会绕过保留/法律留存,并且双 ORM 行为一致。
1. 当前自动化证据
| 测试面 | 已覆盖 |
|---|---|
| Command | 重复节点 ID、缺父节点、持久 ID 回传 |
| Query | 模板列表调用 Store、回收站查询调用 Port |
| Store | EffectiveTenant 传播、收藏主键租户过滤、旧节点快照 |
| Architecture | Infrastructure ORM 中立、默认端口、跨模块实现位置 |
| Integration | DocumentStructure Store parity 纳入双 ORM 体系 |
缺少:真实 HTTP 权限、环/深度边界、并发名称/更新、Apply 目标授权、部分失败回滚、Scriban 安全/取消、收藏目标授权、Restore/Purge 竞态和容量恢复。
2. 最小回归组合
# 应用层图校验和读用例。dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \ --filter "FullyQualifiedName~DocumentStructure"
# Store 与 Documents 回收站单元证据。dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \ --filter "FullyQualifiedName~DocumentStructure|FullyQualifiedName~DocumentRecycleBinStore"
# 架构边界与默认端口。dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \ --filter "FullyQualifiedName~DocumentStructure"发布候选还必须运行两种真实 ORM 的模板多表事务、唯一冲突和回收站级联场景,不能用 Substitute Store 代替发布证据。
3. HTTP 授权矩阵
| 场景 | 预期 |
|---|---|
| 匿名任一路由 | 401/统一认证错误 |
| 无模板 View | list/get/preview 拒绝 |
| 有 View 无 Apply | apply 拒绝 |
| 租户 A 访问租户 B TemplateId | NotFound |
| 有模板权限但无 KB 管理权 | apply 拒绝且无分类 |
| 无目标资源访问权添加收藏 | 拒绝 |
| 无 KB 权限查询回收站 | 拒绝 |
| 普通 Delete 权限执行 Purge | 按高风险策略拒绝 |
必须确认请求类型约定最终映射到 docs.folder-template.* / docs.recycle-bin.*,并为 Favorite 建立明确权限;只断言 IAuthorizedRequest 存在不够。
4. 图与版本测试
覆盖空 ID、重复 ID、自环、双节点环、缺父、深度 10 成功/11 失败、1/100/1000 节点、同级 SortOrder、过长字段、空 Name、未知 TargetType。更新还要测格式异常版本、并发双写、名称唯一冲突、快照 JSON schema 和中途删除/插入失败回滚。
// 在删除旧节点后、插入新节点前注入持久化失败。storeFaults.FailNextNodeInsert(new IOException("storage unavailable"));
var result = await sender.Send(updateCommand, cancellationToken);
result.IsFailure.ShouldBeTrue();// 旧根、CurrentVersion、节点集合和版本历史必须保持一致。await evidence.AssertTemplateVersionAsync( updateCommand.Id, "1.0.0", expectedOldNodes, cancellationToken);5. Apply 故障注入
依次在目标授权、Compile、Render、第一个/中间/最后一个 Category Save、事务提交和响应返回前注入失败。每次断言:数据库无部分树或补偿完成;幂等记录可恢复;重复请求不重复创建;错误包含稳定 code,不泄漏 NamingContext。
// 同一业务意图的并发请求必须复用相同 ApplicationId。var command = NewApplyCommand(applicationId: "apply-2026-0042");
// 同时发起请求,用数据库唯一约束验证并发门禁。var results = await Task.WhenAll( sender.Send(command, cancellationToken).AsTask(), sender.Send(command, cancellationToken).AsTask());
results.ShouldAllBe(result => result.IsSuccess);results[0].Value!.CreatedCategoryIds.ShouldBe(results[1].Value!.CreatedCategoryIds);await evidence.AssertSingleTreeAsync(command.TargetId, command.ApplicationId, cancellationToken);ApplicationId 当前不存在;这是 GA 目标测试,不是现状示例。
6. Scriban 安全测试
测试未知变量、语法错误、巨大输出、深层循环、超时、取消、敏感上下文、禁止函数/对象、Unicode 和同名渲染。Preview 返回逐节点 diagnostics;Apply 默认在任何必需表达式失败时零写入。
记录编译/渲染耗时与错误码,不记录表达式中的 Secret 或完整上下文。若编译器支持缓存,缓存键必须包含租户安全边界、表达式哈希、引擎和策略版本。
7. 收藏测试
覆盖 RequireUserId、允许类型、目标不存在、跨租户目标、当前无权、后续失权/删除、重复 active、软删后重加、并发重复、分组筛选、本人/他人删除和大列表分页。双 ORM 必须对唯一与软删恢复给出相同行为。
8. 回收站测试
对 Document/Category 分别覆盖不存在、未删除、跨 KB、跨租户、父已删除、名称冲突、分类子树、附件/版本、Search 索引、收藏引用、重复 Restore、重复 Purge、Restore/Purge 并发、保留期与 Legal Hold。
Purge 中途失败要有可恢复 Job/报告;事件消费者要能重放而不重复删除外部对象。
9. 容量基线
至少测:模板 1/10/100/1000 节点,租户 10/1000/100000 模板,用户 10/1000/10000 收藏,回收站 1万/百万项,分类级联 10/1000/10万对象。记录 P50/P95/P99、数据库 round trips、事务日志、锁、分配、编译耗时和事件积压。
当前模板列表逐模板 Count 节点,是 N+1;模板数量上升时应改成批量聚合 Read Store。
10. 指标与告警
- template create/update/delete/conflict;
- graph validation error by code、node count/depth;
- preview compile/render/fallback duration;
- apply duration、planned/created/failed/compensated nodes;
- application id replay/conflict;
- favorite add/conflict/unresolved/denied/list size;
- recycle query/restore/purge、cascade size、legal-hold denial;
- transaction rollback、outbox lag、dead letter。
告警重点是 apply partial、compensation failed、Purge unauthorized/failed、unresolved favorite 激增、Scriban 超时和跨租户拒绝异常增长。
11. 备份与恢复
备份包括模板根、当前节点、历史快照、收藏、Documents 回收站事实和相关 Outbox。恢复后验证父引用、CurrentVersion/快照一致、收藏自然键唯一、软删过滤、回收站树完整和 Search/Files 消费者可重建。
历史快照没有 schema version 时,恢复升级存在风险;GA 前必须给旧快照迁移和不可解析隔离策略。
12. GA 阻断项
- Apply 未验证目标实例和 TargetType;
- 逐节点写没有事务/补偿故障证据;
- unresolved remaining 可部分成功;
- 没有应用幂等键;
- Scriban 同步阻塞、忽略取消、吞错;
- 版本无查询/diff/回滚/schema version/并发控制;
- 收藏不验证目标或授权,UserId 可回退 0;
- Favorite 权限未显式治理;
- 回收站对象授权、Purge 保留/法律留存不足;
- 跨模块恢复/Purge 事件一致性未证明;
- 模板列表 N+1 和大树容量无基线;
- HTTP、双 ORM、并发、故障、恢复和运维证据缺失。
13. 分阶段落地
P0:目标授权、原子/补偿 Apply、完整结果、幂等、异步 Scriban、收藏 owner 授权、回收站对象策略。P1:版本产品、并发控制、批量读模型、Purge Job/保留、事件与观测。模板组合、共享收藏和高级蓝图属于后续产品能力。
14. 扫尾命令
# 高风险现状:预期在 P0 后归零或只留已登记兼容入口。rg -n "GetAwaiter\(\)\.GetResult|CancellationToken.None|UserId\?\.ToString\(\) \?\? \"0\"" \ src/Platform/DocumentStructure -g '*.cs'
# 逐节点跨模块写:目标是改为 owner contract 后归零。rg -n "IRepository<DocumentCategory>|SaveAsync\(category" \ src/Platform/DocumentStructure -g '*.cs'