Skip to content
bitzorcas
中EN

Guide

GDPR 测试、运营与 GA 门禁

给出当前测试证据、缺口矩阵、SLO、指标、告警、排障、演练和商业交付门禁。

Last updated

GDPR 模块的测试目标不是“端点返回 200”,而是证明请求不会串租户、不会走错状态机、不会漏掉数据 Owner、不会把不完整导出标成完成,并能在超时和部分失败后恢复。

1. 当前已有测试证据

测试已证明未证明
GdprQueryHandlerTestsConsent/SAR 列表通过 IGdprStore,并传 actor tenant/user路由、授权、有效租户、详情安全
GdprInfrastructureArchitectureTestsInfrastructure 不依赖具体 ORM;Store fail-closed 注册运行时数据库和合规语义
PortRepositoryParityTests两个 ORM 的基础 Consent、DSR 更新和 SysUser 匿名化结果一致Command 状态机、并发、回滚、关联数据
API Shell Smoke无数据库时解析 Unavailable Port生产 Adapter Readiness
Shared ownership tests两张表归 GDPR Owner,旧 1:1 模型不存在数据分类和保留

目前没有 GDPR Command 单元测试、HTTP 授权测试、SAR 文件测试或多 Owner 擦除测试。

2. 状态机测试

每种请求类型都应使用表驱动测试固定允许与拒绝矩阵。

Erasure 状态迁移契约
[Theory]
[InlineData("CoolingOff", true, "Executing")]
[InlineData("Submitted", false, null)]
[InlineData("Executing", false, null)]
[InlineData("Completed", false, null)]
[InlineData("Cancelled", false, null)]
[InlineData("Rejected", false, null)]
public void Execute_transition_is_closed(
string from,
bool expectedAllowed,
string? expectedTo)
{
// 每个来源状态都必须有显式期望,新增枚举项时测试会要求决策。
var result = ErasureTransitions.TryExecute(
ErasureStatus.Parse(from),
coolingOffExpired: true);
// 允许路径还要固定唯一目标状态,避免隐式跳转。
result.IsSuccess.ShouldBe(expectedAllowed);
if (expectedAllowed)
result.Value.ShouldBe(ErasureStatus.Parse(expectedTo!));
}

当前没有 ErasureTransitions;测试形状表达生产目标。还要逐一把 SAR ID 传给 Erasure 命令、把 Erasure ID 传给 SAR 命令,验证统一失败。

3. 租户与主体测试

至少构造四个维度:

  • Actor Tenant;
  • Effective Tenant;
  • Subject Tenant;
  • 记录 Tenant。
operate-as 不得混淆 Actor 与 Subject
using var tenantScope = tenantAccessor.Push(
effectiveTenantId: "tenant-b",
actorTenantId: "tenant-host");
// 管理员代表目标租户发起,但主体必须是目标租户内的用户。
var result = await mediator.Send(
new CreateSarForSubject.Command(
SubjectUserId: "user-b",
RequestId: "sar-b-001"),
cancellationToken);
result.IsSuccess.ShouldBeTrue();
// 分别断言数据归属、主体和操作人证据。
stored.TenantId.ShouldBe("tenant-b");
stored.UserId.ShouldBe("user-b");
stored.ActorTenantId.ShouldBe("tenant-host");

自助端点若不支持 operate-as,应明确拒绝,而不是写入混合上下文。

4. 幂等与并发测试

必须在真实数据库上并发发送相同 RequestId:

  1. 同租户、同类型、同 RequestId 只产生一行;
  2. 同租户、不同类型的命名空间策略明确;
  3. 不同租户可以使用相同 RequestId;
  4. 两个 Operator 不能同时完成同一请求;
  5. 重复撤回不改写首次时间;
  6. 重复 Owner Step 不重复通知或删除。

只用 Mock 顺序调用“查询后插入”无法证明并发幂等。

5. SAR 完整性测试

为每个 Contributor 准备黄金数据集,验证:

  • 0、1、1000、1001 和多页记录;
  • 稳定游标跨页无重复、无缺失;
  • Manifest 中 Owner 数等于冻结清单;
  • 每个 Artifact Hash 与实际文件一致;
  • 被保留/排除的数据有原因码;
  • Schema Version 可由旧客户端识别;
  • 生成失败不发布下载票据;
  • 票据一次消费、过期、跨主体和跨租户均拒绝。

当前实现会在 1001 条审计记录时静默漏掉后续页,这应作为现状回归测试固定,直到修复。

6. 擦除证据测试

多 Owner 场景需要故障注入:

故障期望
Owner A 成功、B 暂时失败请求保持 Partial/Retrying,不得 Completed
Legal Hold对应数据 Retained,保存原因与期限
外部 Processor 超时幂等重试并升级,不重复成功 Step
Identity 用户不存在按策略记录 NoData 或错误,不泄露跨租户存在性
提交前进程终止重启后从持久化 Checkpoint 恢复
Evidence Store 不可用高价值执行失败关闭

7. 权限与 Feature 测试

HTTP 集成测试应从实际 Permission Catalog 创建角色,再调用所有九条路由。当前最需要捕获的是 privacy.*.manage 与实际 *.update/delete 不一致。

还要覆盖:

  • 无 Token 返回 401;
  • 已认证无决策 Allow 返回 403;
  • 本人权限不允许处理他人请求;
  • Operator 权限不能跨 Effective Tenant;
  • Feature 关闭时自助、运营和后台路径行为一致;
  • admin 角色名不能替代必要权限。

8. 指标

建议按 Tenant Hash、RequestType、Status 和 OwnerCode 聚合,禁止把 UserId、RequestId 原值放入指标标签。

指标用途
gdpr_requests_created_total请求流量与类型
gdpr_requests_in_stateSubmitted/CoolingOff/Partial 等积压
gdpr_request_age_seconds最老未完成请求
gdpr_owner_step_duration_secondsOwner 延迟分布
gdpr_owner_step_failures_total可重试/永久失败
gdpr_export_bytes包大小与容量
gdpr_download_ticket_rejections_total票据攻击与客户端错误
gdpr_erasure_retained_total法律保全/保留裁决

当前模块没有专属指标。

9. SLO 与告警

法定时限由组织配置,不应硬编码成本文数字。工程 SLO 可以定义为法定截止前的内部预算:

  • 未确认请求超过接收预算;
  • SAR/Erasure 距截止时间不足安全缓冲;
  • CoolingOff 已结束但没有进入审批队列;
  • Partial Step 重试次数或年龄超阈值;
  • 导出文件已到期但对象仍存在;
  • 同一请求出现两个 Completed Evidence;
  • 权限拒绝率突然变化;
  • Unavailable Port 或 Evidence Store Readiness 失败。

告警通知中只使用内部 RecordId 和 CorrelationId,不包含主体姓名、邮箱或导出 URL。

10. 排障路径

否是是否

收到投诉或告警

用 RecordId + Tenant 定位 DSR

RequestType 与状态值匹配?

检查权限决策与 Actor/Effective Tenant

检查 Step/Manifest/文件证据

可安全重试?

使用同一幂等键恢复

冻结执行并升级隐私事件

在当前实现中若 SAR API 返回 ProcessingFailed 而数据库仍是 Submitted,先检查事务回滚语义,不要手工把状态直接改成 Completed。

11. 发布演练

每次商业发布前至少执行:

  1. 双 ORM 各完成一份多页 SAR;
  2. 使用错误类型 ID 攻击三个处理端点;
  3. 在冷静期前一毫秒、等于截止和后一毫秒执行;
  4. 模拟 Owner 部分失败、数据库死锁、对象存储超时;
  5. 下载票据重放与跨租户访问;
  6. 删除到期包并从对象存储核验;
  7. 模拟 operate-as 与 Host Caller;
  8. 恢复备份,验证过期导出不会重新暴露;
  9. 从证据重建一次请求时间线;
  10. 由安全、运维、法务共同签署结果。

12. GA 阻断门禁

下列任一项未完成,GDPR 模块不得宣称生产完整:

  • 所有处理器验证 RequestType 和集中状态迁移;
  • RequestId 唯一约束与并发测试通过;
  • 权限目录、实际决策码和 Feature 行为一致;
  • Actor、Effective Tenant、Subject 分离;
  • SAR Contributor、全量分页、Manifest、加密文件和下载票据落地;
  • Erasure Owner、Legal Hold、Checkpoint 和外部 Processor 证据落地;
  • 失败状态可持久化,不被 Result 回滚吞掉;
  • Identity 关联凭据/会话策略完成;
  • 数据分类、保留、销毁和备份策略批准;
  • 双 ORM、HTTP、安全、故障恢复和性能测试通过;
  • 指标、告警、Runbook 和值班责任人就绪;
  • 软件行为与组织法律流程完成联合验收。

13. 本地校验

Terminal window
# 运行现有 GDPR 查询、架构和双 ORM 契约测试。
dotnet test tests/BitzOrcas.Application.Tests \
--filter FullyQualifiedName~Gdpr
dotnet test tests/BitzOrcas.Architecture.Tests \
--filter FullyQualifiedName~Gdpr
dotnet test tests/BitzOrcas.Integration.Tests \
--filter FullyQualifiedName~GdprStore_Should_Behave
# 全局暴露尚未形成实现的生产合同;修复前预期多数无命中。
rg -n "IDataSubjectExportContributor|IDataErasureContributor|LegalHold|ExportManifest" \
src tests -g '*.cs'

返回 GDPR 总览

100%

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