156 lines
8.1 KiB
Markdown
156 lines
8.1 KiB
Markdown
# 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 | 已有重试次数达到上限后进入 failed;waiting_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 关联链和
|
||
最终状态写入审计报告,作为发布批准依据。
|