事件总线的测试计划
上级:events
状态: 已在 v0.1.76 发布(2026-09-27)—— 设计与落地记录见 StandMeet 仓库的 docs/design/event-bus-outbox-webhooks.md。下面的全套验收已于 2026-09-27 通过。
内部机制靠真 Postgres 上的 UT 证明,对外行为靠黑盒 e2e 证明,每一期收尾前都要回归受影响的现有 spec。UT 用真 Postgres、httptest 端点,不 mock 数据库。
UT:已有的
| 套件 | 覆盖 |
|---|---|
| River 一致性(约 27) | Jobs / Inspector / Runtime 的契约:生命周期、尝试次数、snooze、discard、按参数唯一、查看、等待;现在对 River 跑,将来换实现原样跑 |
| events 的 relay / 触发器 / recorder(约 26) | 按行领取;交错提交不丢;批大小;poison 标记;多个 relay 并发不重复扇出;不合并的订阅方收到同一主体的每条事件;每个被监视列都出事件、空更新不出;未声明类型报错 |
| 饱和套件 | 连接池耗尽;磁盘满且 relay 退避;429 冷却;Meili 挂掉后积压排空;积压暴增告警;等待者上限;队列饥饿;任务超时(saturation-degrades-gracefully) |
注册表驱动的幂等性(cmd/server/wire) | 每个已注册订阅方收同一事件两次,效果只发生一次;遇到不知道怎么驱动的订阅方就失败 |
| 事件类型注册表 | 每个类型只声明一次、名词加动词命名、有描述和主体模式、Exposure 没被无意停在零值 |
| 每个用例的事件耦合 | 每个产生事件的用例都在写入的同一事务里记下事件 |
| webhook 签名、分类、范围 | Standard Webhooks 测试向量;每种状态码和传输错误 → retryable / snoozed / discarded;raw 永不出、已发布切片、码的 deny、embed 范围 |
| embed hook upsert | embed 的 update_hook_url 创建或更新它挂的端点 |
| schema 一致性 | TestMigrationsAddNothingToASchemaSQLDatabase;TestSQLPathSegmentMatchesGo |
“交错提交不丢”来自 message-loss-guarantees;注册表驱动的幂等性测试来自 no-bypass-by-structure。
新增 e2e:已有的
| 期 | spec | 证明什么 |
|---|---|---|
| P1 | events-index-via-bus | 用 MCP corpus.create 创建的条目立即可搜;更新和删除立即反映,且回执如实说明;Meili 故障期间的写入返回回执,Meili 恢复后被索引,中间重启 backend 也不丢 |
| P1 | events-bulk-import-bound | 导入的笔记全部可搜,索引任务不超过 N + 余量,且没有 discarded |
| P1 | tasks-panel、tasks-panel-more | tasks-panel 里的 12 条,走真实 UI |
| P1 | upgrade-events-outbox | 总线出现之前的实例重启即升级:启动时跑迁移和 River 的迁移器,之后编辑的旧笔记被索引,周期任务跑在 River 上 |
| P2 | webhooks | 签名事件送达且可验;只发已发布切片(哨兵式);raw 永不出;500×2 后成功且只收一次;429 带 Retry-After 不计次数;410 立即 discarded,之后可重投;send_test;轮换密钥;停用的端点收不到;指向内网地址的端点被拒 |
| P2 | webhook-event-types | 每种开放类型由真实动作产生,都到达 * 端点 |
| P2 | events-fault-injection | 比超时还慢的端点被重试,之后送达;连接被拒立即 retryable 并进入冷却;Postgres 重启后,编辑仍到达接收方和搜索 |
| P3 | embed-update-hook | embed 表单的 hook 挂上端点,编辑带签名到达;范围就是码的范围(哨兵式);再存同一个 URL 保留原端点且不给新密钥;删除带最后已知的主体;cards 带 updated_at |
| P4 | events-side-effects-durable | 失败过一次的申请通知仍送达;批准从发送中变已发送,邮件发出后才标记已回复;永久失败时显示失败且不标记已回复;重启前排队的通知重启后送达;重启中的预约仍通知 owner |
| P4 | events-build-settled | backend 重启后才完成的构建仍能到达打开着的预览 |
- webhook sink 是外部 mock 上的一组路由(
mock-stack/job-board/webhook_sink.go),/__mock/set_delay用来模拟慢接收方。 - 现有 spec 只改“什么时候看”,不改“看到什么”(completion-hooks)。
norm-outward-toolset的 golden 加上了tasks.*、events.*、webhooks.*和jobs.fetch_result工具。 - 门禁不是 e2e,也不留自测脚本:7 条各在临时副本里植入的样本上证红过一次(no-bypass-by-structure)。
- 从计划里去掉的:招聘源定时抓取(按决定不做)、webhook 防抖(webhook 从不合并)。
回归
- 每期:跑受影响的现有 spec(embed、corpus、access、booking、microsite、obsidian、jobs、monitor、mail、search、supplier、calendar、upgrade…),
REPEAT=5排除闪烁。 - 顺序:每个组件先写 UT,每个验收项先写 e2e 并在原代码上证红(
make test-asis),再写实现。
最终验收:这个仓的所有测试全绿(2026-09-27 已通过)
| 命令 | 覆盖 |
|---|---|
make lint | secrets、env、backend(含全部 check-*.sh 门禁,含本计划的 7 条)、no-mock、app、sdk、e2e、im-bridge、verify-items |
make backend-test | Go 测试,含新增 UT |
make test-unit | 前端 / SDK 单测 |
make im-bridge-test | im-bridge |
make stack-test | 整栈测试 |
make dsh-plugin-test | 每个 block 装进真 dsh 跑通 |
make test-fresh | 全套 e2e,从空卷起,验证 schema.sql 与迁移一致 |
make eval-* | 真 agent 循环的评测(blocks、owner-mcp、ghost、summary 等) |
一次过,不靠重跑刷绿。 以上全部一次性全绿;失败先读日志定位机制,不靠重跑刷绿。然后才合并 main、发版、升级 sijie.xyz、部署 standmeet.com,最后做 P3 的真环境验收并归档到 docs/real-env-verification/。分期状态见 events-roadmap。