Skip to content
bitzorcas
中EN

Guide

Documents 测试、可观测性与生产门禁

给出 Documents 模块的测试资产、缺口矩阵、日志指标、告警、数据对账、迁移和 GA 上线清单。

Last updated

Documents 的生产验收不能只看 CRUD 是否返回 200。版本历史会不断放大聚合,成员/ACL 当前没有执行,树可能含坏引用,搜索会截断在 200 条,协作是内存实现。测试与运营门禁必须围绕这些真实风险建立。

1. 当前测试资产

源码已经有较完整的领域单元测试基线:

测试文件当前 Fact 数重点
DocumentTests21创建、版本、发布、标签、访问规则、浏览计数
KnowledgeBaseTests13成员、计数与还原
DocumentCategoryTests10名称、父级、计数
WhiteboardTests14数据版本、归档、成员
CollaborationSessionTests11加入、离开、结束与还原
DocumentSearchServiceTests13评分、过滤、分页、建议
JSON/QueryShape/Filter/RecycleBin18序列化、租户读模型、过滤与回收站
Application read handlers4可信租户与读模型委派
Infrastructure architecture8依赖与持久化结构规则

这些测试证明了局部方法语义,不证明 HTTP 授权、跨 ORM 行为、并发、CAP 事件、缓存、生产数据库索引或多实例协作。

2. 风险驱动测试金字塔

少量 E2E
认证 + HTTP + DB + CAP/Redis

契约/集成
路由、权限、租户、ORM parity、并发

大量单元
聚合状态、JSON、评分、树、Diff

聚合单元测试

  • Document 的每个状态 × 每个可变方法;
  • 完整快照的 SHA-256、UTF-8 字节数和 Patch 递增;
  • 回滚创建新版本且不删除未来历史;
  • Unpublish 后 Document 与 Version 发布标记差异;
  • KnowledgeBase/Whiteboard/Category 嵌入 JSON 还原失败关闭;
  • 树算法对孤儿和环的预期行为;
  • 简化 Diff 对头、中、尾、多处分散修改的明确限制。

应用与授权契约测试

  • 每个 [GenerateEndpoint] 的方法、路由、认证、RateLimit 和 Timeout;
  • 所有写消息派生的 module.resource.action 必须存在于权限目录;
  • 每个读消息都明确选择 IAuthorizedRequest 或文档化的匿名/认证策略;
  • 当前 User.TenantId 与 EffectiveTenantId 分歧;
  • ACL、成员、公开性、创建者和平台管理员的决策表;
  • CreateDocument 的 KB/Category/Parent 引用完整性;
  • Delete 的权威引用检查与软删除后不可见性。

持久化与并发测试

  • SqlSugar 与 EF Core 对四个统一聚合的字段、JSON、软删除与租户行为一致;
  • 分类同级名称唯一索引在并发请求下只允许一条;
  • Document UpdateContent 的两个并发写入不能丢版本;
  • Whiteboard 两个相同 Version 的更新只能一个成功;
  • 大型 VersionsJson/Canvas Data 的读取、更新、迁移和备份成本;
  • KnowledgeBase/Category 文档计数对账。

3. 授权契约测试示例

防止无权限详情查询回归
[Theory]
[InlineData("tenant-a", "tenant-b", false)]
[InlineData("tenant-a", "tenant-a", true)]
public async Task Document_Detail_Must_Enforce_Tenant_And_Resource_Access(
string callerTenant,
string documentTenant,
bool sameTenant)
{
// Arrange:播种一个没有成员的私有文档。
await fixture.SeedDocumentAsync(
tenantId: documentTenant,
documentId: "doc-secret",
isPublic: false,
members: []);
using var client = fixture.CreateAuthenticatedClient(
tenantId: callerTenant,
userId: "outsider");
// Act:以目标租户外或同租户非成员身份读取同一资源。
using var response = await client.GetAsync("/api/v1/documents/doc-secret");
// 跨租户必须隐藏存在性;同租户非成员在目标 ACL 落地后也必须被拒绝。
response.StatusCode.ShouldBe(
sameTenant ? HttpStatusCode.Forbidden : HttpStatusCode.NotFound);
}

这是目标安全契约。以当前实现运行时,同租户非成员会读到文档,因此该测试应先红后绿地驱动资源策略接入。

4. 事件与缓存测试

需要先定义集成事件生产方,再验证完整链路:

Search index/cacheIndex subscriberCAP deliveryTransaction + OutboxDocument commandSearch index/cacheIndex subscriberCAP deliveryTransaction + OutboxDocument commandaggregate + outbox eventcommitevent, possibly repeatedidempotent upsert/deletesuccess only after durable change

当前订阅方抑制异常且 Index/Delete 为 no-op,不能用“消息消费成功”证明索引一致。测试应覆盖重复 EventId、乱序 Update/Delete、索引不可用、重建期间查询、ACL 撤销、缓存失效和 poison message。

5. 可观测性模型

日志不要记录完整 Content、Canvas Data、AccessRules、Members、搜索词或云盘下载 URL。推荐的低基数字段:

维度示例说明
module/use_casedocuments.update_content稳定枚举
resultsuccess/validation/conflict/forbidden/dependency不用错误消息做标签
tenant_hash不可逆短哈希避免原始租户 ID 高基数泄漏
content_type/statusMarkdown/Published低基数业务维度
version_transitionpatch/rollback/publish/archive生命周期
dependencydatabase/cache/search/cloud_drive/realtime外部边界

关键指标:

  • documents_command_duration_seconds{use_case,result};
  • documents_version_snapshot_bytes 分布;
  • documents_versions_per_document 分布;
  • documents_authorization_denied_total{resource,operation};
  • documents_search_candidates_total 与截断计数;
  • documents_index_lag_seconds;
  • documents_tree_invalid_reference_total{kind};
  • documents_collaboration_active_sessions、操作拒绝和重放数;
  • documents_count_reconciliation_delta{parent_type}。

6. 告警与运行手册

信号可能原因首轮处置
版本写入冲突/失败上升并发编辑、聚合过大、DB 锁查看文档版本数和行大小;停止盲目重试
详情 P95 持续增加includeVersions 被忽略、历史膨胀抽样 VersionsJson 大小,切换专用投影
树接口超时/栈异常ParentId 环或异常深度暂停编辑层级,导出关系并运行环检测
搜索突然归零读模型故障被缓存为空清除 docs:search 键,检查 DB;修复失败缓存
搜索漏数候选超过 200比较租户文档量,切换真实索引
跨租户/非成员可见查询缺授权谓词立即网关封禁相关端点,审计访问并补策略
白板保存冲突激增全量快照并发、DTO 类型差异检查客户端版本转换和重试策略
协作内容丢失节点重启/切换当前内存实现无法恢复;停止作为生产编辑入口

7. 数据对账

上线定时只读对账,至少报告:

知识库文档计数对账思路
-- 实际表/软删除列名以生成 schema 为准;先在只读副本验证。
-- 聚合保存值只用于比较,实际数量从未删除文档权威计算。
SELECT
kb.Id,
kb.DocumentCount AS StoredCount,
COUNT(doc.Id) AS ActualCount
FROM DocsKnowledgeBase kb
LEFT JOIN DocsDocument doc
ON doc.TenantId = kb.TenantId
AND doc.KnowledgeBaseId = kb.Id
AND doc.IsDeleted = 0
WHERE kb.IsDeleted = 0
GROUP BY kb.Id, kb.DocumentCount
HAVING kb.DocumentCount <> COUNT(doc.Id);

还应检查孤儿 CategoryId/ParentId/KnowledgeBaseId、跨知识库分类引用、树环、CurrentVersion 不在 VersionsJson、Current Content/Hash 与对应版本不一致、Published 状态缺发布信息和软删除父资源下的活跃子资源。

8. 容量与保留

每个版本保存完整正文,所以总存储近似 正文大小 × 版本数,且每次更新可能读写整段 JSON。生产前必须测量真实 P50/P95/P99 正文和版本数量。

可选演进方向:

  • 当前内容留在 Document,版本快照拆成独立表/对象存储;
  • 热版本与冷历史分层;
  • 以不可变 FileAsset 保存超大附件,Document 只保存引用;
  • 版本保留与法律保留策略独立建模;
  • 后台压缩/归档必须保持哈希与审计清单。

不要在没有审计、导出和法律保留决策的情况下直接裁剪历史。

9. GA 门禁

阻断上线

  • 所有公开 GET 与写入共享资源访问策略,成员/ACL 不再是装饰数据;
  • Category/Whiteboard 权限目录补齐,Publish/Manage 动作映射明确;
  • Documents Feature 有运行时决策或从产品承诺中移除;
  • CreateDocument/Whiteboard/KnowledgeBase 的引用与环检查闭环;
  • 文档和分类计数由权威查询或一致性机制维护;
  • UpdateContent 与 WhiteboardData 具有数据库级并发保护;
  • includeVersions=false 使用轻量投影;
  • 搜索不再截断 200 条、不缓存依赖失败为空成功、Highlighter 防 XSS;
  • 文件服务不再生成伪 PDF/Word,二进制走 Files;
  • 协作若对外开放,必须替换内存/Null Hub 并实现多租户、多实例、持久化与 ACL;
  • 协作会话读模型增加 Tenant 谓词;
  • 事件生产、幂等索引、失败重试与索引重建有契约测试;
  • 双 ORM、HTTP、权限、租户、并发、备份恢复和容量测试全部通过。

可作为后续增强

  • 字符级/语义 Diff;
  • Major/Minor 版本策略;
  • 云盘双向同步;
  • 内容预览、转换和全文抽取;
  • CRDT 离线协作;
  • 浏览分析与推荐。

10. 验证命令

Terminal window
# 模块测试。
dotnet test tests/BitzOrcas.Unit.Tests/BitzOrcas.Unit.Tests.csproj \
--filter 'FullyQualifiedName~BitzOrcas.Unit.Tests.Docs'
dotnet test tests/BitzOrcas.Application.Tests/BitzOrcas.Application.Tests.csproj \
--filter 'FullyQualifiedName~Documents'
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj \
--filter 'FullyQualifiedName~DocumentsInfrastructureArchitectureTests'
# 全局扫出仍未接入的关键方法;修复完成后的预期由对应迁移任务定义。
rg -n 'HasAccess\(|SetStorage\(|IncrementViewCount\(' src tests -g '*.cs'
rg -n 'default-tenant|NullRealtimeCollaborationHub|NullWhiteboardCollaborationHub' \
src/Platform/Documents -g '*.cs'

返回 Documents 总览

100%

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