Files
cc-web/docs/gitea-workflow/TESTING.md

156 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Gitea Webhook Agent 测试设计
> 本文与 [PRD.md](./PRD.md) 的 FR/AC 以及
> [ARCHITECTURE.md](./ARCHITECTURE.md) 的字段、状态和错误矩阵对齐。独立协议
> 回归入口为:
>
> ```bash
> timeout 60s node scripts/gitea-webhook-regression.js
> ```
>
> 脚本只使用 Node 内置模块和 `fixtures/gitea-workflow/*.json`,不启动
> `server.js`、不访问网络、不写业务 Store适合实现落地前后的离线验收。
## 1. 测试金字塔
| 层级 | 目标 | 本轮入口 |
|---|---|---|
| 契约/纯函数 | HMAC、事件规范化、mention/Bot 过滤、幂等键 | `gitea-webhook-regression.js` |
| 状态模型 | queued/running/retry_wait/终态恢复和有限重试 | 同上 + 实现方状态机单测 |
| 协议集成 | HTTP header、Gitea REST、Codex JSON-RPC、MCP stdio | mock server/adapter 集成测试 |
| 端到端 | 真实 Gitea 测试仓库到最终评论 | staging 手工/CI smoke不纳入离线脚本 |
| 运维验收 | 重启、备份恢复、pause/disable/abort、告警脱敏 | staging runbook 演练 |
离线脚本是最小门禁;当前仓库已提供以下专项门禁:
```bash
node scripts/gitea-webhook-regression.js
node scripts/gitea-webhook-workspace-unit.js
node scripts/gitea-workflow-codex-unit.js
node scripts/gitea-workflow-core-unit.js
node scripts/gitea-workflow-management-unit.js
```
仍需在 staging 用真实 Gitea 验证出站回帖确认;纯函数通过不能替代真实凭据和
网络链路验收。
## 2. Fixtures 约定
所有 fixture 均为 JSON放在 `fixtures/gitea-workflow/`,不含真实 Secret、Token、
源码或生产 URL。字段约定
```json
{
"name": "valid-issue-comment",
"headers": {
"x-gitea-event": "issue_comment",
"x-gitea-delivery": "delivery-001"
},
"payload": {
"repository": {"owner": "acme", "name": "widget"},
"issue": {"number": 42},
"comment": {"id": 101, "body": "@ccweb-bot 请修复"},
"sender": {"login": "alice", "is_bot": false}
},
"expected": {"accepted": true, "resourceKind": "issue"}
}
```
脚本运行时用固定测试 Secret 计算签名;签名不落盘。需要拒绝签名时复制
payload 后篡改 raw body 或签名头。fixture 变更必须同步场景 ID 和预期结果。
## 3. 需求覆盖矩阵
| 场景 ID | 验证内容 | Fixture/断言 | 对应 AC |
|---|---|---|---|
| WH-01 | 合法 Issue 评论 mention 触发,规范化 owner/repo/number/comment | `valid-issue-comment.json` | AC-01 |
| WH-02 | 合法 PR 评论进入 `pull_request` 资源,保留 base/head | `valid-pr-comment.json` | AC-01/03 |
| WH-03 | HMAC 缺失/错误不写业务任务,常量时间比较 | `valid-issue-comment.json` 篡改签名 | AC-02 |
| WH-04 | 相同 `instanceId:deliveryId` 重放返回 duplicate不增 task | valid fixture 两次提交 | AC-02 |
| WH-05 | 无 mention、非评论事件、Bot 自评论、停用仓库均 ignored/rejected | `ignored-events.json``bot-self-comment.json` | AC-01/10 |
| Q-01 | 重启后 queued 保留running 变 `retry_wait(interrupted_by_restart)` | `queue-recovery.json` | AC-09 |
| Q-02 | 已有重试次数达到上限后进入 failedwaiting_user/终态不重跑 | 同上 | AC-09 |
| Q-03 | 同仓库运行锁互斥;跨仓库可以并行,不设置额外全局活动上限 | 脚本内最小状态快照 | AC-04 |
| R-00 | 合法触发仅产生一条 `giteabot: 已收到` 起始回执;成功路径不产生运行中/等待/已完成状态评论 | server.js 回执策略断言 | AC-07 |
| R-01 | MCP 回帖成功但查询缺失只创建一个 reply_retry | `reply-failure.json` | AC-08 |
| R-02 | 补发仍失败才 REST 兜底,结果为 succeeded_with_rest_fallback | 同上 | AC-08 |
| MCP-01 | `thread/start.config.mcp_servers.gitea` 为线程级 stdio 配置 | `mcp-thread-config.json` | AC-06 |
| MCP-02 | collaborationMode 字段放 settings顶层无 model/effort 重复字段 | 同上 | AC-06 |
| MCP-03 | gitea-mcp 命令缺失、进程启动即退、initialize 返回 error、握手超时分别映射稳定错误码 | `gitea-mcp-probe-unit.js` + mock stdio fixture | AC-06/07 |
| MCP-04 | 预检失败后任务只产生一条 `giteabot:` 失败终态回执;线程级注入仍保留且不自动安装二进制 | `gitea-mcp-probe-unit.js` 源码守门 | AC-06/07/11 |
| SEC-01 | Secret/token 不进入日志、评论、clone URL 或 fixture | 全部 fixture + 输出扫描 | AC-02/11 |
| SEC-02 | 路径穿越、任意命令、越权控制参数被拒绝 | 实现方安全单测 | AC-03/10/11 |
## 4. 状态、恢复与并发测试
实现方 Store 测试必须验证以下不变量:
1. `deliveryKey``sessionKey``taskId+turnId``commentKey` 唯一;重复请求
返回原记录且不创建新 turn/comment。
2. 同一 `repoKey` 同时最多一个 `preparing/running/aborting`;不同 `repoKey`
可以并行pause 只阻止领取,不强制终止运行中任务。
3. 重启扫描用 `stateVersion`/`leaseId` CAS旧 worker 不能覆盖新租约;
`running` 最多自动恢复一次,超过上限进入 `failed`
4. `waiting_user` 保留 thread新评论创建新 task 但复用 `sessionKey/threadId`
所有终态不再自动执行。
5. `blocked_workspace` 不执行 reset/clean管理员解除后才可重新入队。
故障注入至少覆盖进程在“intent 已落库、外部调用未完成”和“外部返回成功、
结果未确认”两个窗口崩溃,恢复后必须通过幂等键继续,而非盲目重复修改。
## 5. 回帖重试测试
按以下顺序模拟:
1. MCP comment 返回 accepted但 Gitea 查询无匹配标记:状态进入
`verifying_reply`,只允许一个 `reply_retry` turn。
2. `reply_retry` 仍无评论:调用一次 REST 兜底body 复用 `taskId/turnId/resourceKey`
标记;查询成功为 `succeeded_with_rest_fallback`
3. REST 返回 2xx 但查询仍为空:状态必须是 `failed_reply`,不能虚报成功,也不得
无限重试。
4. 原 MCP 评论已确认后重复 webhook/重启:不得再发送起始、状态、最终或兜底评论。
测试日志只输出 `taskId/turnId/commentId/errorCode`,不得输出正文中的 Secret 或
完整 Authorization。
## 6. MCP/JSON-RPC 协议测试
mock app-server 必须断言初始化顺序为:`initialize``initialized` → best-effort
`experimentalFeature/enablement/set``collaborationMode/list``thread/start`
能力探测失败只记录降级,不阻断启动。
另需运行 `node scripts/gitea-mcp-probe-unit.js`,它会以独立 mock stdio 进程验证
gitea-mcp 的 initialize 预检,不需要真实 Gitea、Token 或 Docker。
`thread/start` 断言:
- `config.mcp_servers.gitea` 每个线程单独携带 host/token 引用和 stdio command
- `CC_WEB_SOURCE_SESSION_ID` 等来源上下文不能只存在进程环境;
- 使用 collaboration mode 时,`model``reasoning_effort`
`developer_instructions` 只在 `collaborationMode.settings`,顶层不存在重复
`model`/`effort`;拒绝 collaborationMode 时可降级普通 turn。
## 7. 安全与运维验收
- 验签使用 raw body + HMAC-SHA256 + 常量时间比较;缺失/错误签名 401 且无业务写入。
- 事件正文、Issue 描述、代码和 PR 内容均视为不可信 prompt不能改变系统约束、
读取 Secret、关闭审计或注入任意命令。
- Bot 自评论、停用仓库、非评论事件和无 mention 事件不启动 Agent每个决定有
审计 action/errorCode。
- clone/fetch 使用不含 Token 的 remote URL工作区路径必须在允许根目录内
路径穿越和脏目录覆盖均拒绝。
- 控制 API 需要管理员身份、reason 和 idempotency key取消/中止幂等。
- 日志和审计脱敏检查覆盖 Secret、Bot Token、Authorization、MCP args/env。
## 8. 执行清单
```bash
node --check scripts/gitea-webhook-regression.js
timeout 60s node scripts/gitea-webhook-regression.js
git diff --check
```
CI 失败时保留 stdout/stderr、fixture 名称和场景 ID不得重试隐藏不稳定失败。
真实 Gitea staging 验收完成后,应将 delivery/task/thread/turn/comment 关联链和
最终状态写入审计报告,作为发布批准依据。