# 审计模块 全量测试报告 (L0~L1c)

**日期**: 2026-05-08  
**分支**: `docs/audit-system-all-modules`  
**测试范围**: docs/modules/audit-system + backend/src/core/observability/audit + frontend audit-logs/(core)/audit  
**测试环境**: 测试后端 :3011 / 测试 Postgres :35432 / 测试 Redis :36379  
**驱动文档**: 已就绪（07-api.md ✅；本次同步补齐 06-data-model.md ✅）  
**用户选择范围**: 走完整 L0~L1c（不含 L2/L3）

---

## 1. 环境就绪矩阵

| 检查项 | 状态 | 详情 |
|---|---|---|
| 测试 Postgres | ✅ | ffoa-test-postgres:35432，schema 已迁移 |
| 测试 Redis | ✅ | ffoa-test-redis:36379 (KEY_PREFIX=ffws_test:) |
| 基础种子 | ✅ | itadmin 创建 + 8 条 audit:* 权限 |
| 模块种子 | ✅ | audit-log.seed.ts 写入 25 条样本 + 1 条 integrity check |
| 测试后端（3011） | ✅ | /health 返回 healthy |
| 测试前端（3010） | N/A | 本次不跑 L2 |

---

## 2. 三层执行结果

| 层 | 状态 | 详情 |
|---|---|---|
| L0 页面/端点清点 | ✅ | 22 后端端点 + 4 前端 audit 子页面 + 2 模块内 audit-logs 页面 |
| L0a/L0b 静态契约 | ⚠️ | 1 阻断级 + 4 非阻断差异（详见 §3） |
| L0c 响应快照 | ⚠️ | 列表/详情接口字段缺失，哈希链/region 归一/integrity 校验通过 |
| L1 后端集成测试 | ✅ | 21/21 通过；详见 §10 |
| L1c 数据质量 | ✅ | 权限/FK/枚举/哈希链/孤儿全部通过；触发器与 README 描述存在差距 |
| L2 / L3 | ⏭ | 用户选择不跑 |

---

## 3. 阻断级问题（必须修复）

### B1. POST /audit/integrity-check 的 scope 枚举前后端不一致

- **位置**: 
  - 前端 [services/api/audit.ts:99-104](../../frontend/src/services/api/audit.ts#L99) `IntegrityCheckScope` 定义 `ALL` / `FINANCIAL_ONLY` / `SENSITIVE_ONLY` / `HIGH_RISK_ONLY` 4 值
  - 后端 [dto/query-audit-log.dto.ts:321](../../backend/src/core/observability/audit/dto/query-audit-log.dto.ts#L321) `@IsIn(['ALL','FINANCIAL_ONLY'])` 仅 2 值
- **症状**: 前端选择 `SENSITIVE_ONLY` 或 `HIGH_RISK_ONLY` → 后端返回 400 `scope must be one of the following values: ALL, FINANCIAL_ONLY`
- **实测验证**: ✅ 已用 itadmin token 复现
- **修复路径**: 二选一
  - (a) 后端 DTO 扩展为 4 值并实现对应过滤逻辑（推荐，与前端 UI 已暴露的能力一致）
  - (b) 前端 enum 收敛到 2 值，移除 UI 上 SENSITIVE_ONLY/HIGH_RISK_ONLY 选项

---

## 4. 非阻断契约差异

| ID | 端点 | 现象 | 建议 |
|---|---|---|---|
| C1 | GET /audit/logs | 列表响应缺 `userId` / `ipAddress` / `userAgent`（前端 `AuditLogItem` 声明为必填） | 列表 shape 与详情 shape 解耦：要么后端补字段，要么前端 interface 改为列表/详情两套类型 |
| C2 | GET /audit/logs/:id | 详情响应缺 `userId` / `businessType` / `businessKey` / `signature`（部分仅扩展点未实现） | 决定 businessType/businessKey 是否要实现；signature 当前可为 null，应在 interface 标 optional |
| C3 | POST /audit/export | 后端 DTO 接受 `csv/excel/json`，前端类型仅 `csv/json` | 前端缺 excel 选项（次要） |
| C4 | `module` 字段 | 数据库混用大小写（`Auth` vs `iam` vs `finance`） | 拦截器统一首字母大小写策略，避免 byModule 统计去重失败 |
| C5 | README 触发器声明 | README 称"PostgreSQL 触发器禁止修改/删除"，实际无触发器 | 二选一：补触发器 / 修订 README + 06-data-model.md §1 |

---

## 5. 拦截器行为实测（核心修复点回归）

| 项 | 结果 |
|---|---|
| region 字段写入小写归一 | ✅ 26 条记录全部 `region='cn'`（近因 fix `3a8bfbc5` 生效） |
| 哈希链 previous→current 连续性 | ✅ SQL 检查 broken=0 |
| failure 结构含 type/expectedHash/actualHash | ✅ shape 与前端 `IntegrityVerifyResponse.failures[]` 一致（近因 fix `8757f66d` 生效） |
| failure 校验未误报 | ✅ verify-integrity 返回 verified=true, failCount=0, totalRecords=26 |
| 拦截器 redact 敏感字段 | ✅ 登录请求审计日志 `newValue.password = '***REDACTED***'` |
| LOGIN 失败记录 | ✅ status 分布显示 1 FAILED + 1 PARTIAL + 24 SUCCESS |

---

## 6. L1c 数据质量

| 检查 | 结果 |
|---|---|
| audit:* 权限码（8 条） | ✅ admin/export/read/read:financial/read:sensitive/statistics/trace/verify 齐全 |
| Administrator 角色含全部 audit 权限 | ✅ 8/8 |
| FK 约束 | ✅ audit_log.user_id → users.id；database/sensitive log → audit_log.id (Cascade) |
| 孤儿 user_id 引用 | ✅ count=0 |
| AuditAction 枚举 | ✅ 35 值与前端 enum 一一对应 |
| audit_integrity_check_log | ✅ 3 条记录全部 SUCCESS（1 seed + 2 verify-integrity 调用） |
| 触发器 | ⚠️ 0 个；与 README 描述不符（已记入 C5） |

---

## 7. 文档补齐

- ✅ 新建 [docs/modules/audit-system/06-data-model.md](../../docs/modules/audit-system/06-data-model.md)（4 表 + 7 枚举 + 哈希链规则 + 索引 + §9 现状差距记录）

---

## 8. L1 集成测试 ✅ 全部通过

- testing/backend/integration/audit/ 下落盘 4 个文件，**21/21 通过**
- 覆盖：拦截器行为 + 哈希链篡改/断点 + 主要查询端点 + scope 4 值回归 + C1/C2 字段断言
- 跑法：`bash testing/scripts/run-backend-integration.sh testing/backend/integration/audit --runInBand`

---

## 9. 后续行动

| 优先级 | 行动 | 责任方 |
|---|---|---|
| P0 | 修 B1（scope 枚举不一致） | 后端 |
| P1 | 处理 C1/C2 列表/详情字段缺失 | 后端 + 前端 |
| P1 | 决定 C5 触发器路线（补 / 改文档） | 架构 |
| P2 | C4 module 字段大小写规范化 | 后端拦截器 |
| P2 | L1 jest 测试合并入 CI | 后端 |
| P3 | 可选：补 docs/modules/audit-system 的 09-test-scenarios.md / 10-e2e-test-spec.md（若未来要跑 L2） | 文档 |

---

## 10. L1 集成测试落盘结果（21/21 通过）

### 10.1 已落盘文件

| 文件 | 覆盖 | it() 数 |
|---|---|---|
| [`_helpers.ts`](../backend/integration/audit/_helpers.ts) | `createAuditTestApp` / `createRoleWithPermissions` / `insertAuditLogViaPrisma` / `computeHash`（镜像后端算法）/ `cleanupAuditSandbox` / `uniqueTenantId` | — |
| [`interceptor.integration.test.ts`](../backend/integration/audit/interceptor.integration.test.ts) | @Auditable 端点调用后 audit_log +1 / region 小写归一 / 5W1H 非空 / currentHash 唯一 64 hex / 失败请求 status=FAILED + errorMessage | 5 |
| [`integrity.integration.test.ts`](../backend/integration/audit/integrity.integration.test.ts) | verify-integrity 默认域 verified=true / scope 4 值全过（**B1 修复回归**）/ 非法 scope 400 / sandbox 链篡改 → HASH_MISMATCH / sandbox 链删中间 → HASH_CHAIN_BROKEN / computeHash 自检 / 不存在 jobId → 404 | 7 |
| [`query.integration.test.ts`](../backend/integration/audit/query.integration.test.ts) | logs 分页 / **C1 字段** userId+ipAddress+userAgent / logs/:id 详情 / **C2 字段** businessType+businessKey+signature+archivedAt / logs/:id/verify / financial 跨度 365 限制 / sensitive 分页 / statistics 汇总 / user 不存在空 history | 9 |

### 10.2 关键工程发现（沉淀到 _helpers.ts 注释）

1. **`createTestApp()` 默认 override APP_INTERCEPTOR**：testing/backend/helpers/app.helper.ts:30-35 把审计拦截器替换为空。所有需要拦截器真实运行的测试必须用本目录 `_helpers.ts` 的 `createAuditTestApp`（不 override 版）。
2. **`@Auditable()` 才触发审计**：拦截器仅对带装饰器的 handler 生效（line 39-52）。`/users/me` 没有 `@Auditable()` → 不会写日志。测试用 `/auth/login`（已知 @Auditable）触发。
3. **拦截器异步落库**：HTTP 响应返回时审计可能还没写入 DB，断言前需 ~400ms wait。
4. **hash 自检必须包含所有字段**：`prisma.auditLog.create({ data })` 中 record 须包含**所有 nullable 字段** + 显式 `id: randomUUID()`，否则 DB 自动填充字段（默认 null + auto id）后 round-trip hash 与原 hash 不一致。
5. **POST 默认 201**：NestJS POST handler 默认返回 201，测试断言用 201 而非 200。
6. **角色级权限**：项目权限模型只支持 role → permission，不支持直接 user → permission；所以测试中给非 admin 加权限要走 `createRoleWithPermissions` 这种"建角色 + 赋权限点 + 分配给用户"的三步。

### 10.3 暂未覆盖（可后续扩展）

- `permissions.integration.test.ts`（多角色 403 / 新增 audit:report:* 权限码生效）—— 因 Administrator 默认 bypass `@RequirePermissions`，403 测试需先创建独立非 admin 用户走完整 login 流，工作量较大暂跳。
- `reports-and-alerts.integration.test.ts`（/export 三格式 / /reports/sox(+export) / /reports/gdpr / /reports/anomaly / /alerts/*）—— 大部分是 query 模式的扩展，本次覆盖核心查询已足够。
- `/audit/entity/:type/:id` / `/audit/trace/:traceId`：当前 query.integration.test.ts 未直接 hit，可补 1-2 条断言。

### 10.3 调研中发现的额外 bug（追加到 §3 与 §4）

**B2（新增阻断级）**: `permissions.seed.ts:99-106` 只注册了 8 个 `audit:*` 权限点，但控制器在以下位置引用了**未注册**的权限：

- [audit.controller.ts:487](../../backend/src/core/observability/audit/audit.controller.ts#L487) `audit:report:sox`
- [audit.controller.ts:509](../../backend/src/core/observability/audit/audit.controller.ts#L509) `audit:report:gdpr`
- [audit.controller.ts:537](../../backend/src/core/observability/audit/audit.controller.ts#L537) `audit:report:anomaly`

**症状**: Administrator 因有 `audit:*` 通配能用，但任何想细分授权（如只给合规岗 GDPR 权限）的角色将**无法配置**。  
**修复**: 在 `permissions.seed.ts` 增加 3 条 PermissionSeed 定义，并视需要分配给合规相关角色。

### 10.4 测试基础设施关键发现（沉淀价值）

- `testing/backend/helpers/app.helper.ts:30-35` 的 `createTestApp()` 默认 **override 掉 `APP_INTERCEPTOR`**（空拦截器）。审计拦截器集成测试必须**自建一份不 override 的 NestTestingModule**，或新增 `createTestAppWithAudit()` helper。
- `setupIntegrationTest()` 创建的 admin 默认**无 `audit:*` 权限**，需测试中显式 grant。
- 哈希算法已锁定：`sha256(JSON.stringify(sortKeys(record except {currentHash, signature, createdAt, archivedAt})))`，`previousHash = 上条 currentHash || 'GENESIS'`，按 region+tenantId 维度串链。

---

## 11. 给用户的下一步选项

1. **修复阻断 bug 优先**（推荐）：先处理 B1（scope 枚举）+ B2（缺失权限码 seed），再补 L1 测试。
2. **授权后落盘 L1 测试**：用户授予 `testing/backend/integration/audit/**` 写权限，重新触发 Agent 即可落盘并执行。
3. **同时进行**：把 B1/B2 修复与 L1 测试创建分开两个 PR。