Skip to content
bitzorcas
中EN

Guide

Comments 测试、运营与 GA 门禁

汇总 Comments 当前测试资产、缺失契约、可观测性、容量、审核、迁移对账、事故处置与生产发布门禁。

Last updated

Comments 是小模块,但安全半径不小:自由资源键可以跨业务挂载,正文是用户生成内容,软删除需要保持线程,迁移还会清空旧数据。GA 验收应以访问控制、租户一致性和可恢复性为核心。

1. 当前测试资产

源码中能审计到:

  • 1 个 Application 查询测试:验证 store 委派、回复树和删除占位;
  • 3 个 Comments Infrastructure 架构测试:ORM 中立、查询 handler 使用 store、fail-closed 持久化端口;
  • 1 个双 ORM PortRepositoryParity 场景:Insert、Update、SoftDelete、IncludeDeleted;
  • API Shell build smoke 能解析 ICommentStore;
  • 持久化元数据注册测试覆盖 CommentThreadSupportRecord。

没有专门覆盖 Create、Update、Delete、迁移、通用权限、HTTP 契约、并发幂等、目标 owner 授权、深树或租户模拟的测试。

2. 测试层级

E2E
Auth + owner + Comments + DB

契约/集成
HTTP / 权限 / tenant / ORM / migration

单元
规则 / 树 / 时间窗口 / 输入

Handler 单元测试

  • Create 的非空/10k、已有结果、父缺失/删除、AuthorId;
  • Update 的作者/admin/窗口边界/trim/EditedAt;
  • Delete 的作者/admin/重复删除;
  • GetComments 的排序、孤儿、删除占位、环保护;
  • Migration 的计数、坏 JSON、重复、失败和取消。

持久化与契约测试

  • SqlSugar/EF Core 的 unique index、soft delete、include deleted 和 tenant filter;
  • CommentId 并发首请求只产生一行并返回一致结果;
  • User.TenantId != EffectiveTenantId 的四个用例;
  • 每个 HTTP 路由、方法、Problem Details、timeout/rate limit;
  • owner policy 对 Documents/Tickets 的 view/create/update/delete;
  • 迁移 source/target 对账与事务范围。

3. 目标资源授权测试

防止通用权限绕过 owner 资源
[Fact]
public async Task Create_Should_Reject_When_Subject_Is_Not_Visible()
{
// Arrange:调用者有 Comments Create,但不在目标 Ticket 的可见范围。
using var client = fixture.CreateClient(
tenantId: "tenant-a",
userId: "outsider",
permissions: ["comments.comment.create"]);
await fixture.SeedPrivateTicketAsync("tenant-a", "ticket-42", ownerId: "owner");
// Act:自由 EntityType/EntityId 不能绕过 Ticket owner policy。
using var response = await client.PostAsJsonAsync(
"/api/comments/",
new { commentId = Guid.NewGuid(), entityType = "Ticket", entityId = "ticket-42", body = "probe" });
response.StatusCode.ShouldBe(HttpStatusCode.Forbidden);
(await fixture.CountCommentsAsync("tenant-a", "Ticket", "ticket-42")).ShouldBe(0);
}

当前实现会在通用权限通过后创建记录,所以这是资源策略落地的 red test。

4. 租户一致性测试

模拟租户下的幂等合同
await using var scope = fixture.CreateScope(
userTenantId: "operator-home",
effectiveTenantId: "tenant-target");
var command = new CreateComment.Command(
operationId, "Document", "doc-42", null, "tenant-scoped comment");
// 两次调用必须都在 tenant-target 查询和写入,而不是混用 userTenantId。
var first = await scope.Mediator.Send(command);
var replay = await scope.Mediator.Send(command);
// 幂等重放必须返回同一物理评论。
first.Value!.Id.ShouldBe(replay.Value!.Id);
// 同时证明 home tenant 没有被意外写入。
(await scope.Comments.CountAsync("tenant-target")).ShouldBe(1);
(await scope.Comments.CountAsync("operator-home")).ShouldBe(0);

同一测试矩阵要覆盖 Get、Update、Delete 和 Ticket migration。

5. 可观测性

正文、作者 Id、原始 EntityId 不应进入高基数 metric 或普通日志。推荐字段:

字段示例
use_casecreate/get/update/delete/migrate
resultsuccess/validation/forbidden/not_found/conflict/dependency
entity_type注册表中的低基数 canonical code
tenant_hash不可逆短哈希
actor_typeuser/client/moderator
operation_kindtop_level/reply/edit/author_delete/moderator_delete

关键指标:请求耗时/结果、owner policy deny、幂等 replay/conflict、线程记录数/深度、Body 长度、软删除率、迁移剩余/失败/对账差异、数据库唯一冲突和 sanitizer 拒绝数。

6. 告警与事故处置

信号可能原因首轮处置
同资源评论量异常上升垃圾请求、无 owner 校验限流/关闭 Create,核对 actor 与 subject
唯一冲突增加并发重试、模拟租户幂等分裂检查 CommentId、User/EffectiveTenant
GET P95/响应大小上升热点长线程、无分页对资源临时限流,启用分页/深度限制
StackOverflow/500环或极深回复链下线该 subject,导出并修复 ParentCommentId
非成员读到评论owner policy 缺失/缓存过期网关封禁 Get,审计访问,撤销缓存
迁移后源清空目标缺失tenant mismatch/事务错误停迁移、从备份恢复、按哈希对账
XSS 报告UI raw render立即禁用富文本渲染,清洗输出并排查历史正文

7. 容量与分页

当前一个 GET 读取资源全部评论并构建整树。生产容量测试至少覆盖:

  • 1/10/100/1000/10000 条评论;
  • 10000 层链与宽度 10000 的单根;
  • 50% 软删除占位;
  • 10k Body 的响应与压缩;
  • 同一热点 subject 并发读写;
  • Entity/Parent/Author 索引在多租户大表上的执行计划。

目标 API 可采用根评论游标分页,响应每根前 N 条回复与 replyCount,再提供按 ParentCommentId 的分页端点。稳定排序使用 CreateTime + CommentId。

8. 数据对账

日常只读检查:

  • EntityType 不在 registry;
  • EntityId 对应 owner 资源不存在;
  • ParentCommentId 指向另一 subject/tenant 或形成环;
  • IsDeleted=true 但 Body 非 null;
  • AuthorId=0;
  • Body/Id 超过契约或异常 Unicode;
  • 同 subject 的重复 CommentId;
  • 迁移后 Ticket.CommentsJson 非 [];
  • Ticket 源评论与 SysComment 的 CommentId/Body 哈希差异。

9. GA 阻断项

  • EntityType 使用 registry,未知值失败关闭并规范化大小写;
  • 所有 Get/Create/Update/Delete 调用目标 owner policy;
  • Documents.AllowComments 与 Ticket 状态/可见性真实生效;
  • User.TenantId/EffectiveTenantId 统一,模拟与后台场景有契约测试;
  • Create 要求明确 ActorType/ActorId,不再写 AuthorId=0;
  • admin 字符串替换为 moderator 权限/策略与审计;
  • 并发幂等冲突归一,软删除后键语义明确;
  • 标识/Body 长度在 Application 提前验证;
  • GET 有分页、深度/总量限制、环保护和稳定排序;
  • 富文本/Markdown 输出经过安全 renderer/sanitizer;
  • 创建/编辑/删除的事件、通知和审核明确实现或明确不承诺;
  • Ticket migration 的租户、事务、分页、坏数据、报告和对账修复;
  • 移除不必要的 Tickets.Infrastructure 长期依赖;
  • Handler、HTTP、权限、owner、双 ORM、并发、容量、备份恢复测试通过。

10. 验证命令

Terminal window
# 当前 Comments 相关测试。
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--filter 'FullyQualifiedName~Comments'
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \
--filter 'FullyQualifiedName~CommentsInfrastructureArchitectureTests'
dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj \
--filter 'FullyQualifiedName~CommentStore_Should_Behave'
# 全局扫出仍缺失的目标策略和租户分裂。
rg -n 'EnsureCanComment|ICommentSubjectPolicy|AllowComments' src/Platform -g '*.cs'
rg -n 'User.TenantId|EffectiveTenantId|AdminRole|\?\? "0"' \
src/Platform/Comments -g '*.cs'

返回 Comments 总览

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%