# §18. 工具链端到端验证测试

> 日期：2026-06-09
> 状态：已完成 ✅
> 目标：用真实 Webhook 触发验证整条 Mail 通知链路

## 前置确认

- Gitea 用户名 ↔ Agent ID 映射：完全一致（admin, guanyu-dev, jiangwei-infra, pangtong-fujunshi, simayi-challenger, zhangfei-dev, zhaoyun-data）
- Gitea 组织级 Webhook（Hook ID=28）：姜维确认最近 5 条投递全部 is_succeed=1
- Daemon 在线：sanguo-moziplus-v2 运行中
- 测试仓库：sanguo/moziplus-v2

## 命名规范

- Issue 标题：`[E2E-TEST] xxx`
- PR 标题：`[E2E-TEST] xxx`
- 分支名：`test/e2e-<timestamp>`

## 验证步骤

| 步骤 | 操作 | 触发事件 | 预期 Mail 通知 | 验证点 |
|------|------|----------|---------------|--------|
| 1 | 创建 Issue `[E2E-TEST] Issue指派测试`，assignee=zhangfei-dev | issues (assigned) | zhangfei-dev 收到 "Issue 指派" Mail | Mail to/模板正确 |
| 2 | 开分支 `test/e2e-<ts>`，创建 PR `[E2E-TEST] Review请求测试` | pull_request (opened) | simayi-challenger 收到 "Review 请求" Mail | Mail to/风险级别/文件列表 |
| 3 | PR Review APPROVED | pull_request_review (approved) | PR 作者(pangtong-fujunshi) 收到 "Review 通过 ✓" Mail | result=通过 ✓ |
| 4 | PR Review REQUEST_CHANGES | pull_request_review (rejected) | PR 作者收到 "Review 驳回 ✗" Mail | result=驳回 ✗ |
| 5 | Issue 上发评论 `[CI] CI 失败 — 分支: test/e2e-xxx, 错误: build timeout` | issue_comment | Issue 作者收到 "CI 失败" Mail | 模板含分支/错误摘要 |
| 6 | 创建标题含"部署失败"的 Issue（无指派） | issues (opened) | jiangwei-infra + pangtong-fujunshi 各收到 "部署失败" Mail | 双收件人 |
| 7 | 关闭步骤 1 的 Issue，再发 CI 失败评论 | issue_comment (closed issue) | 不产生 Mail（负面测试） | handler 跳过 closed |
| 8 | 重发步骤 1 Webhook（相同 delivery ID） | 重复事件 | 不产生新 Mail（幂等测试） | 返回 duplicate |

## 签名校验

已测试（GITEA_WEBHOOK_SECRET 已配置且生效）：
- ✅ 正确签名：请求正常处理
- ✅ 无签名：返回 403 `signature verification failed`

## Review 意见来源

- 姜维（基础设施确认 + 边界验证建议）
- 司马懿（遗漏点补充 + 命名规范 + 风险防范）

---

## 执行记录

> 2026-06-09 00:40~00:50 CST

### 步骤 1：Issue 指派 ✅
- 操作：创建 Issue #22 `[E2E-TEST] Issue指派测试`，assignee=zhangfei-dev
- Mail：`mail-1780936736480`，from=system, to=zhangfei-dev, title=`Issue 指派: [E2E-TEST] Issue指派测试`
- 模板渲染正确（含 Issue 链接、标签、描述、建议分支名）

### 步骤 2：PR Review 请求 ✅
- 操作：创建分支 `test/e2e-1780936838`，创建 PR #23
- Mail：`mail-1780936851715`，from=system, to=simayi-challenger
- 模板含 PR 链接、标题、作者(pangtong-fujunshi)、分支、风险级别(standard)
- 附带：CI 失败通知 `mail-1780936876572`（CI 自动触发，符合预期）

### 步骤 3：Review APPROVED ✅
- 操作：用 simayi-challenger token 提交 APPROVED review
- Mail：`mail-1780936968411`，from=system, to=pangtong-fujunshi, title=`Review 通过 ✓`
- 描述含审查者(simayi-challenger)、review body
- ⚠️ 收到 2 封重复 Mail（org webhook + repo webhook 双触发）

### 步骤 4：Review REQUEST_CHANGES ✅
- 操作：用 simayi-challenger token 提交 REQUEST_CHANGES review
- Mail：`mail-1780936972207`，from=system, to=pangtong-fujunshi, title=`Review 驳回 ✗`
- ⚠️ 同上，收到 2 封重复 Mail

### 步骤 5：CI 失败评论 ✅
- 操作：在 Issue #22 发评论 `[CI] CI 失败 — 分支: test/e2e-1780936838, 错误: build timeout`
- Mail：`mail-1780936994513`，from=system, to=pangtong-fujunshi, title=`CI 失败: sanguo/moziplus-v2#22`
- 模板含分支提取和错误摘要

### 步骤 6：部署失败 Issue ✅
- 操作：创建 Issue #24 `[E2E-TEST] 部署失败: test deploy`（无指派）
- Mail：`mail-1780936999660` to=jiangwei-infra, `mail-1780936999684` to=pangtong-fujunshi
- 双收件人验证通过 ✅

### 步骤 7：已关闭 Issue 负面测试 ✅
- 操作：关闭 Issue #22 后发 `[CI] CI 失败 — 应被过滤`
- 结果：未产生新 Mail ✅（只有步骤 5 的 1 封 CI Mail，步骤 7 的评论被正确过滤）

### 步骤 8：幂等测试 ✅
- 操作：构造带正确 HMAC-SHA256 签名的 Webhook，用同一 delivery ID `test-idempotency-002` 发两次
- 第一次：返回 `ok`，产生 Mail ✅
- 第二次：返回 `duplicate`，无新 Mail ✅
- 额外验证：不带签名的请求返回 403 `signature verification failed`（签名校验正常工作）

---

## 汇总

| 步骤 | 状态 | 备注 |
|------|------|------|
| 1. Issue 指派 | ✅ 通过 | Mail to/模板正确 |
| 2. PR Review 请求 | ✅ 通过 | Mail to/风险级别/文件列表正确 |
| 3. Review APPROVED | ✅ 通过 | E2E 测试中产生 2 封 Mail（根因已查明，非平台问题） |
| 4. Review REQUEST_CHANGES | ✅ 通过 | 同上 |
| 5. CI 失败评论 | ✅ 通过 | 分支提取正确 |
| 6. 部署失败 Issue | ✅ 通过 | 双收件人验证通过 |
| 7. 已关闭 Issue 过滤 | ✅ 通过 | 负面测试通过，无新 Mail |
| 8. 幂等测试 | ✅ 通过 | 第二次返回 duplicate，无新 Mail；签名校验正常拦截无签名请求 |

## 发现的问题

### Review 事件双 Mail（已修复）
- **现象**：E2E 测试步骤 3/4 中 Review 事件产生 2 封 Mail
- **根因**（姜维深入调查确认）：E2E 测试中庞统手动用 simayi token 提交了 Review，同时 simayi agent 收到 Review 请求 Mail 后也自主提交了 Review。是两次独立的 API 调用，**不是 Gitea bug 或平台配置问题**
  - 姜维控制实验：一次 review API 调用只产生 1 个 hook_task
  - Gitea 路由日志确认两次 POST 间隔 7 秒，payload 有差异（review_comments、updated_at 不同）
  - 之前的错误分析（"Gitea webhookNotifier + actionsNotifier 双投递"）已被推翻：actionsNotifier 走 handleWorkflows() 不创建 hook_task
- **修复**：payload 内容去重作为防御性编程保留（`_is_duplicate` 新增内容去重 key = event + pr_num + sender + sha256(body_or_content)），司马懿 APPROVED
- **验证**：PR #27 实测只产生 1 封 Mail ✅

### 根因分析教训
- 姜维第一次分析给出了错误根因（Gitea 双 notifier），第二次深入调查后自我纠正
- 庞统把姜维的第一次结论当事实汇报给主公，没有标注"这是姜维的调查结论，尚未独立验证"
- **改进**：SOUL.md 新增规则——推测 vs 事实显式标注、引用他人结论时标注来源、结论被推翻时及时更正

---

## PR #38 新增场景（synchronize fallback + merge 通知）

> 2026-06-12 新增，对应 PR #38 的设计变更

### 步骤 9：synchronize fallback ✅
- 操作：创建 PR（无 review 历史）→ push 新 commit 到 PR 分支
- 触发事件：`pull_request` (synchronize)
- 预期：`simayi-challenger`（默认 reviewer）收到"请重新 review" Mail
- 验证点：
  - PR 无 review 历史时，`_fetch_latest_reviewer()` 返回 None → fallback 到 `simayi-challenger`
  - Mail to 正确（默认 reviewer 而非跳过通知）
  - 模板使用 `review_updated.md`

### 步骤 10：merge 通知 ✅
- 操作：PR 通过 Review 后 merge
- 触发事件：`pull_request` (closed) + `merged=true`
- 预期：PR 作者收到"PR 已合并" Mail
- 验证点：
  - Mail to 正确（PR 作者）
  - `merged_by` 字段正确提取（payload `merged_by` → fallback `sender`）
  - 模板使用 `review_merged.md`
  - 纯 closed（非 merged）不触发通知
