事件总线的测试计划

更新于 · 在 sijie.xyz 查看原条目 ↗

状态: 已在 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 数据库。

flowchart TB
  subgraph ut["UT · real Postgres · seconds"]
    u1[Mechanism: Recorder · trigger · relay · registries · schema parity]
    u2[Delivery: retry classes · backoff · cooldown · signing · SSRF · scope · embed hook upsert]
    u3[Saturation: pool · disk full · 429 · Meili down · backlog · waiters · starvation · timeout]
    u4[Conformance suite: any Jobs / Inspector / Runtime implementation must pass]
  end
  subgraph e2e["New e2e · 11 specs · black-box · real services"]
    e1[P1 index via bus · bulk-import bound · Tasks panel · upgrade]
    e2[P2 webhooks · every event type · fault injection]
    e3[P3 embed update hook]
    e4[P4 durable side effects · build settled after restart]
  end
  subgraph reg["Regression"]
    r1[each phase: the affected existing specs, REPEAT=5]
    r2[end: the full suite, one pass]
  end
  gates[7 gates · each shown red once on a planted sample]
  ut --> e2e --> reg
  gates -.-> reg

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。