Skip to content
bitzorcas
中EN

Guide

Files 测试、运营与 GA 门禁

给出 Files 的现有测试证据、缺口矩阵、Provider 契约、指标、告警、排障、演练和商业交付门禁。

Last updated

Files 的验收不能停在“能上传和下载一个小文件”。真正的交付证据必须跨客户端、API、数据库、对象存储和 JobHost,证明非可信字节不会提前变成业务资产,租户与 Owner 不会越界,失败后两个事实源能够收敛。

1. 当前已有测试证据

测试已证明尚未证明
FileAssetLifecycleCommandHandlerTests服务端 Key、元数据 fail-closed、保存后发布、软删除并发、事务、真实 S3、扫描
FileAssetFinalizeTestsUploaded→Finalized、重复冲突、绑定红线Handler 的 Provider 回退风险
FileAssetPolicyTests租户/Owner/提升权限/非 FinalizedReadModel 真正跨租户隔离、Catalog 可分配性
FileDownloadAccessQueryHandlerTests签发、五分钟响应、Provider fail-closedURL 真实 TTL、重放、撤权
LocalFileStorageTests直接读写、删除、路径穿越、presign 不支持当前 HTTP 闭环
ReadModelStoreParityTestsSqlSugar/EF Core 摘要一致命令并发与软删除外部合同
FilesInfrastructureArchitectureTests统一聚合、读模型端口、无旧 1:1 模型生产运行时就绪

当前没有 S3-compatible 行为契约、孤儿清理回归测试、HTTP 端到端文件测试、恶意文件测试、配额测试或负载测试。

2. 单元状态矩阵

状态测试应覆盖每个来源、动作和错误码:

FileAsset 状态迁移矩阵
[Theory]
[InlineData("PendingUpload", "mark", true, "Uploaded")]
[InlineData("PendingUpload", "finalize", false, "PendingUpload")]
[InlineData("Uploaded", "finalize", true, "Finalized")]
[InlineData("Finalized", "finalize", false, "Finalized")]
[InlineData("Finalized", "delete", true, "Deleted")]
[InlineData("Deleted", "delete", false, "Deleted")]
public void State_transition_should_be_explicit(
string from,
string action,
bool expectedSuccess,
string expectedState)
{
// 每一行从显式状态开始,禁止 Fixture 预先执行隐藏迁移。
var state = new FileAssetState(FileAssetStatus.FromName(from));
// switch 强制新增动作进入测试决策,避免默认成功或静默忽略。
var result = action switch
{
"mark" => state.MarkUploaded(),
"finalize" => state.Finalize(),
"delete" => state.Delete(),
_ => throw new ArgumentOutOfRangeException(nameof(action))
};
result.IsSuccess.ShouldBe(expectedSuccess);
state.Status.ShouldBe(FileAssetStatus.FromName(expectedState));
}

还要单独测试聚合允许 Pending 在一次 Finalize 中推进两步,避免把 State 类型与 Aggregate 类型的合同混为一谈。

3. 元数据与攻击输入

至少覆盖:

  • Size 为 0、负数、1、最大允许值和溢出边界;
  • 36/37 字符 FileId、50/51 OwnerType、255/256 FileName;
  • 空、大小写差异、参数化与伪造 ContentType;
  • SHA-256 空值、非十六进制、错误长度、大小写和不同内容同声明;
  • ETag 为 Multipart 格式;
  • ../、CRLF、双引号、Unicode 组合字符与双向文本文件名;
  • StorageKey 跨租户前缀和相似前缀 tenant-a2;
  • 0 字节对象、上传中 HEAD、最终一致性延迟和对象被覆盖。

当前聚合不验证哈希格式,也不设置平台最大 Size;测试应先固定现状,再用目标测试驱动合同升级。

4. Provider 契约套件

同一组测试必须运行在 Local、S3-compatible 和每个 Connector Provider 上:

可复用的存储 Provider 契约
public abstract class FileStorageContract
{
// 派生类只替换 Provider 构造,行为断言保持完全一致。
protected abstract IFileStorage CreateStorage();
[Fact]
public async Task Upload_metadata_read_and_delete_should_converge()
{
// 每次生成租户范围 Key,测试结束不与其他并发用例共享对象。
var storage = CreateStorage();
var key = $"tenants/contract/{Guid.CreateVersion7():N}";
var bytes = "contract-body"u8.ToArray();
await storage.UploadAsync(
key,
new MemoryStream(bytes),
"text/plain",
CancellationToken.None);
var metadata = await storage.GetMetadataAsync(key, CancellationToken.None);
metadata.ContentLength.ShouldBe(bytes.Length);
await storage.DeleteAsync(key, CancellationToken.None);
(await storage.ExistsAsync(key, CancellationToken.None)).ShouldBeFalse();
}
}

Presign 契约还要通过真实 HTTP PUT/GET 验证:过期时间、Content-Type、下载名、范围请求、404、撤销权限和对象覆盖。只断言 URL 非空没有意义。

5. 双事实源故障注入

注入点期望数据库状态期望对象状态必须有的恢复证据
签发后 Save 失败无 FileAssetKey 可能可写Inventory 清理
PUT 中途断线Pending缺失或部分对象不可 Finalized;过期清理
HEAD 暂时超时Pending存在安全重试,不改变状态
Metadata 不匹配Pending存在隔离/删除与审计
Finalize Save 死锁Pending存在同 FileId 重试
Outbox 发布失败取决于事务存在重启后恢复,不重复消费
Delete 后 Storage 失败Deleted存在Purge 重试队列
JobHost 崩溃不丢 Checkpoint部分已删幂等续跑

现有实现没有显式 Purge 状态或 Checkpoint;这些目标测试会暴露需要新增的工作流合同。

6. 租户与 Owner HTTP 测试

构造 Tenant A/B、User A1/A2、App A 和 Operator:

  1. A1 创建的 private 文件只能由 A1 或获准 Operator 下载;
  2. A2 不能因拥有普通 View 就绕过 Owner Policy;
  3. App Owner 只匹配 ClientId,不匹配 UserId;
  4. Tenant A 的 public 不能由 Tenant B 下载;
  5. 伪造 FileId 不能通过错误差异探测跨租户存在性;
  6. 未 Finalized 文件不能下载或绑定;
  7. Delete 权限边界与 Owner 策略必须由产品明确;
  8. operate-as 分开记录 Actor Tenant 与 Effective Tenant。

路由测试必须从治理 Permission Catalog 建角色,不能在测试里直接塞入实现硬编码但目录不存在的权限。

7. 并发与幂等测试

使用真实数据库和同一对象并发执行:

  • 两个相同会话创建请求的业务幂等策略;
  • 两个 finalize 是否只有一个状态提交和一个事件;
  • finalize 与 delete 竞争的唯一合法结果;
  • download 与 delete 同时发生时已签发 URL 的语义;
  • 清理与 finalize 竞争,尤其 PendingUpload 当前立即入选缺陷;
  • 多 JobHost 实例是否重复清理同一 Key;
  • Provider Delete 的重复调用是否成功收敛。

每个测试都断言数据库、对象、事件和审计四个面,不只断言 HTTP 状态码。

8. 性能与容量

重点不是 API 传输带宽,而是控制面和对象存储限额:

  • 会话创建 P50/P95/P99;
  • S3 HEAD 与 presign 延迟;
  • 每租户并发上传和未完成会话数;
  • Bucket 对象数、总字节与 Inventory 扫描成本;
  • finalize 事务冲突率;
  • 下载签发率与对象 GET 出站带宽;
  • 清理每批对象数、API 限速与执行窗口;
  • Event/Outbox 积压。

负载测试不要上传客户数据。使用可生成、可校验哈希、可批量销毁的测试对象,并为测试 Bucket 设置独立生命周期。

9. 指标

指标用途
files_upload_sessions_total{result}会话成功/拒绝/Provider 失败
files_pending_assets未完成积压
files_session_age_seconds最老 Pending 年龄
files_finalize_total{result,reason}完成率与不匹配原因
files_metadata_verification_seconds{provider}HEAD/metadata 延迟
files_download_grants_total{result}签发与拒绝
files_assets_bytes{state}状态容量
files_cleanup_total{reason,result}会话/删除/Inventory 清理
files_orphan_objects对象有、元数据无
files_missing_objects元数据有、对象无

TenantId、UserId、FileId、StorageKey 和 FileName 不能作为低基数指标标签。诊断用 Hash 或 Trace 属性,并设置访问控制与保留期。

10. SLO 与告警

示例内部 SLO 需要按部署容量校准:

  • 可用 Provider 下,会话签发成功率与 P95 延迟;
  • PUT 成功后的 finalize 成功预算;
  • Pending 超过 URL TTL + 宽限的数量;
  • Finalized 但对象 NotFound 必须为零;
  • Deleted 超过保留期但对象仍存在的数量;
  • 清理连续失败次数、最老积压和删除速率;
  • S3 Readiness、时钟偏差与凭据到期;
  • metadata mismatch 或跨租户拒绝突增。

单次用户误传不要告警;系统性 mismatch、清理停摆和跨租户尝试需要告警。

11. 排障路径

否是否是否是是否

上传/下载投诉

用 CorrelationId + FileId 定位

SysFileAsset 存在且租户正确?

对象 Key 存在?

状态与 metadata 一致?

Owner / 权限 / Provider 正确?

按同一 FileId 安全重试或签发

隔离对象并修复双事实源

安全失败,不手改 Finalized

不要通过数据库直接把 Status 改成 Finalized。这样会绕过 metadata 不变量、FinalizedAt、事件与安全扫描目标。

12. 生产演练

每次重大升级至少执行:

  1. 真实浏览器跨域上传并完成;
  2. URL 过期前后 PUT/GET;
  3. 大小、类型、哈希不匹配;
  4. API 保存失败后的对象 Inventory;
  5. finalize 与清理并发;
  6. 删除后对象 Purge;
  7. S3 凭据轮换与最小权限;
  8. API/JobHost Provider 配置漂移;
  9. Bucket 不可用、慢响应与限速;
  10. 从备份恢复数据库后对账对象;
  11. Tenant-public 与跨租户攻击;
  12. 恶意样本使用专用安全测试环境验证隔离。

13. GA 阻断门禁

  • 上传准入含大小上限、租户配额、OwnerType/Visibility 目录和 MIME 策略;
  • 预签名上传约束大小、类型和可信 checksum;
  • Finalize 检查 UploadExpiresAt,服务端计算/验证哈希;
  • 扫描/隔离状态机落地,扫描前不可绑定或下载;
  • Create/Finalize/Delete 的幂等和并发合同完成;
  • Catalog、普通授权、Owner Policy 与 operate-as 一致;
  • 删除对象、失败重试、Tombstone 与保留策略形成闭环;
  • 修复 PendingUpload 立即清理和 Deleted 不清理缺陷;
  • API 与 JobHost Provider、Bucket、Key、凭据权限一致;
  • S3 与所有 Connector Provider 通过统一契约;
  • 下载响应不泄露 StorageKey,真实 TTL 与 DTO 一致;
  • 指标、告警、Inventory 对账、Runbook 与值班责任就绪;
  • 双 ORM、HTTP、安全、故障注入、并发和容量测试通过;
  • 文档中的现状、受限能力和目标能力与源码一致。

14. 本地校验

Terminal window
# 运行 Files 相关单元、应用、集成与架构测试。
dotnet test tests/BitzOrcas.Unit.Tests --filter FullyQualifiedName~FileAsset
dotnet test tests/BitzOrcas.Application.Tests --filter FullyQualifiedName~File
dotnet test tests/BitzOrcas.Integration.Tests \
--filter "FullyQualifiedName~FileAsset|FullyQualifiedName~LocalFileStorage"
dotnet test tests/BitzOrcas.Architecture.Tests --filter FullyQualifiedName~Files
# 全局暴露当前生产缺口;扫描/配额/删除工作流修复前预期无关键实现命中。
rg -n "IFileScanner|UploadQuota|DeleteRequested|Purged|UploadSessionExpired" \
src tests -g '*.cs'
# 核对清理 SQL,Pending 分支修复后应包含时间条件,Deleted 应有独立生命周期路径。
rg -n "Status.*PendingUpload|Status.*Deleted|UploadExpiresAt|CreateTime.*cutoff" \
src/Framework/BitzOrcas.Infrastructure.SqlSugar/DataLifecycle -g '*.cs'

上一页:存储组合、删除与清理 · 返回 Files 概览

100%

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