Buzz 项目测试指南:从单元测试到本地中继端到端验证的完整实践
【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz
本文是 Buzz(基于 Nostr 协议的 hive mind 通信平台)仓库中 TESTING.md 的深度实践指南。它系统梳理了该项目的自动化测试分层(单元测试与集成测试)、Review-Proven 回归测试标准,以及最核心的实战部分——如何在本机构建buzz-relay并用buzzCLI 完成从发消息、验证千人大名单(roster)到 ACP Agent 端到端联调的全流程。读完本文,你将能复现一条完整的本地中继验证链路,并理解每一个测试步骤背后的源码依据与环境变量语义。
一、自动化测试:两层命令与一个明确边界
Buzz 将测试收敛为两条just命令(由仓库根目录的 Justfile 定义):
just test-unit # 单元测试 —— 无需任何基础设施 just test # 单元 + 集成测试(必要时自动启动 Docker)just test-unit:纯内存、无外部依赖的单元测试。在 Justfile 中它调用 scripts/run-tests.sh,覆盖buzz-core、buzz-auth、buzz-voice、buzz-cli、buzz-acp(relay 与 agent 之间的信任边界)、buzz-db的迁移器与 lint、buzz-conformance的多租户回放校验、buzz-backend-kubernetes的决策层、buzz-agent的模型能力语料,以及buzz-relay的handlers::channel_authz/handlers::moderation_authz/handlers::side_effects纯函数授权网格。若本机装有cargo-nextest,同一批目标会以 nextest 表达式运行;否则回退到 run-tests.sh 的cargo test清单。just test:先跑单元测试,再跑集成测试。集成部分依赖 Postgres 与 Redis,Justfile 中的_ensure-services会检查buzz-postgres、buzz-redis两个容器的健康状态,未就绪时自动执行docker compose up -d并轮询等待,随后_ensure-migrations运行迁移并播种本地社区。
关键边界:两条任务都不跑
buzz-test-client的 E2E 套件。该 crate 的 E2E 测试全部标记为#[ignore],需要一台真实运行的中继。手动触发方式:# 先启动中继(见下文),然后: cargo test -p buzz-test-client -- --ignored
另外,本地测试通过但 CI 失败时,通常是因为遗漏了整门(gate):just ci会执行 fmt、clippy、单元测试以及 desktop/web 的构建检查(见 Justfile 中ci任务与本文末尾故障排查表)。
Review-Proven 测试标准:回归测试必须绑定生产接缝且可证伪
TESTING.md 从最近 25 个 PR 的评审讨论中提炼出一条被评审者反复计较的测试质量标准:
回归测试必须绑定生产代码路径(production seam)并且是可证伪的。
其依据全部来自真实评审事故:一个守卫被移除后没有任何测试变红,说明它没有保护任何东西——这类"幸存突变"曾在移动端测试套件中连续两次漏网(PR #6996、#7013);回归测试绑定在测试专用 helper 而非生产代码路径上同样无效(PR #7013)。对应的可操作规则包括:
- 纯谓词应使用表驱动测试(table test)覆盖完整输入组合空间(PR #6807);
- Playwright 定位器必须限定作用域——必需 smoke 测试中不加作用域的
getByText是严格模式下的典型 flake 源(PR #6980)。
这条标准的推论是:每条回归测试都应当能通过"删除被保护代码后测试失败"的方式自证价值,而不是作为通过即可的形式化存在。
二、本地实时中继(Live Local Relay):最快的中继端到端演练
文档推荐的最快路径是:一次性构建 release 二进制,运行buzz-relay,再用buzzCLI 驱动它。CLI 会对每个请求做 NIP-98 签名,因此无需nak或手写curl。
第 1 步:环境准备
. ./bin/activate-hermit # 激活钉住的工具链(Hermit) just bootstrap # 创建 .env 并一次性生成稳定的 relay key just setup # 启动 Docker 服务,执行迁移其中just bootstrap(见 Justfile)会从.env.example复制生成.env,并调用scripts/ensure-local-relay-key.sh注入稳定的中继身份密钥;just setup调用scripts/dev-setup.sh启动服务并迁移。
两个必须提前知道的坑:
与 Buzz Desktop 共享容器与端口:Desktop 使用相同的容器名(
buzz-postgres、buzz-redis)和默认端口(:5432、:6379)。just setup会复用这些服务,意味着测试中继会写入 Desktop 的数据库。做读写 smoke 测试没问题,但just reset会连同 Desktop 的数据一起清空。需要隔离时,先停止 Desktop,或用独立 Compose 项目名:COMPOSE_PROJECT_NAME=buzz-dev docker compose …just reset会清空所有本地数据并重来——包括共享同一开发栈的 Buzz Desktop 数据。先清理陈旧的 env:如果 shell 从先前会话或 staging 配置继承了
BUZZ_AUTH_TAG、BUZZ_RELAY_URL、BUZZ_PRIVATE_KEY中的任何一个,先unset掉。陈旧的BUZZ_AUTH_TAG会让本地开发中继在第一次 CLI 写入时直接失败:unset BUZZ_AUTH_TAG BUZZ_RELAY_URL BUZZ_PRIVATE_KEY错误形态是
auth_error: signature verification failed——本地开发中继对此零容忍。
第 2 步:构建二进制
cargo build --release -p buzz-relay -p buzz-cli -p buzz-admin export PATH="$PWD/target/release:$PATH"后续所有步骤都使用 release 二进制,因此任何代码改动后都必须重新构建并重新导出 PATH。
第 3 步:启动中继
在独立终端(前台运行):
set -o allexport source .env # 包含 just bootstrap 生成的密钥 set +o allexport buzz-relay # 第 2 步的 release 二进制,监听 ws://localhost:3000 # 备选: # cargo run --release -p buzz-relay # 重新构建并以 release 运行 # just relay # DEBUG 构建 —— 热缓存下启动快, # # 但与第 2 步的 release 版本不一致 # # 需要 release 时用 `just relay-release`回到工作终端验证:
curl -s http://localhost:3000/health # → ok curl -s http://localhost:3000/_readiness # → {"status":"ready"}健康/就绪/存活探针在独立端口上(默认
8080,可用BUZZ_HEALTH_PORT覆盖),这样 K8s 探针可以绕过认证中间件;主应用端口也暴露/health以方便使用。这一点在源码中同样成立:crates/buzz-relay/src/readiness.rs与crates/buzz-relay/src/router.rs分别承载_readiness路由与健康端点,crates/buzz-relay/src/config.rs中BUZZ_HEALTH_PORT与BUZZ_METRICS_PORT的默认值即8080与9102。
中继以开发模式启动(BUZZ_REQUIRE_AUTH_TOKEN=false),携带.env中生成的稳定中继身份。
端口冲突处理:Buzz 同时绑定三个端口——主端口、健康端口、指标端口,任何一个都可能冲突。分别在不同终端导出对应变量:
中继终端(启动buzz-relay前):
export BUZZ_BIND_ADDR=0.0.0.0:3030 export BUZZ_HEALTH_PORT=8088 export BUZZ_METRICS_PORT=9202 export RELAY_URL=ws://localhost:3030 # 在 NIP-42 challenge 中公布 buzz-relay工作/CLI 终端(用于第 4 步及 ACP harness):
export BUZZ_RELAY_URL=http://localhost:3030 # CLI 目标 curl -s http://localhost:3030/health # → ok curl -s http://localhost:8088/_readiness # → {"status":"ready"}本文后续代码块均展示默认端口;见到localhost:3000/:8080时,请在心里替换为你的覆盖值——否则 CLI 会连到 Buzz Desktop 的中继上。
另外:忽略just setup的 "Next steps" 横幅,它仍打印just relay(debug 构建),请使用第 2 步构建的buzz-relayrelease 二进制。
用完记得停止中继(在其终端 Ctrl-C)。若被后台化或丢失终端,用pkill -f buzz-relay——留着它会与下一位在同一台机器上照此文档操作的开发者冲突。
第 4 步:CLI 对中继的 Smoke 测试
端到端最小序列:生成身份 → 建频道 → 发消息 → 读回。这也是 Agent 验证本地中继所需的最小流程。
# 生成密钥对 GEN=$(buzz-admin generate-key) export BUZZ_PRIVATE_KEY=$(echo "$GEN" | awk '/Secret key:/ {print $3}') PUBKEY=$(echo "$GEN" | awk '/Public key:/ {print $3}') echo "pubkey: $PUBKEY" # 创建频道 —— UUID 在响应中返回 CHANNEL=$(buzz channels create --name "smoke-$$" --type stream --visibility open | jq -r '.channel_id') echo "channel: $CHANNEL" # 发消息并读回 SEND=$(buzz messages send --channel "$CHANNEL" --content "hello from smoke test") EVENT_ID=$(echo "$SEND" | jq -r '.event_id') buzz messages get --channel "$CHANNEL" --limit 5 | jq . # 取某条消息的回复链(叶子消息返回空数组 —— 正常) buzz messages thread --channel "$CHANNEL" --event "$EVENT_ID" | jq .成功时,send 输出{"event_id":"…","accepted":true,"message":""},get输出消息体;thread对叶子消息返回[],只有出现回复后才会有内容(见第 6 步)。
第 5 步:验证超过 1000 人的频道名单
修改频道成员、发现(discovery)或对账(reconciliation)逻辑时,应使用聚焦的实时中继脚本 scripts/e2e-large-channel-roster.sh。它能证明纯 DB 测试证明不了的三条边界:
- 中继提供的 kind 39002 中,名单位置 1501 的成员被包含;
- 该身份可以发布频道消息;
- 定向对账(targeted reconciliation)之后该成员仍可被发现。
只对隔离的本地数据库运行。脚本直接插入 fixture 成员,然后通过 release CLI 与中继驱动发现与消息流程。保持第 3 步的 release 中继运行,并使用其配置的中继密钥进行权威替换:
export PATH="$PWD/target/release:$PATH" export DATABASE_URL="postgres://buzz:buzz_dev@localhost:5432/buzz_roster_e2e" export BUZZ_RELAY_URL="http://localhost:3030" # 与第 3 步中继保持一致 export RELAY_URL="ws://localhost:3030" export BUZZ_RELAY_PRIVATE_KEY="<与 buzz-relay 使用的相同密钥>" scripts/e2e-large-channel-roster.sh成功可直接观察为四行PASS。第一、四行包含大于 1000 的成员数与同一个"晚加入成员"公钥;第二行包含被接受的 kind 9 事件 ID;第三行证明定向修复后 kind 39000/39001 的 ID 与 tags 保持不变:
PASS discovery-before-republish channel=<uuid> members=1502 late_pubkey=<hex> PASS late-member-action event_id=<hex> PASS targeted-repair-preserves-metadata-and-admin-events channel=<uuid> PASS discovery-after-republish channel=<uuid> members=1502 late_pubkey=<hex>从脚本源码看,它有以下硬性约束:拒绝 debug 二进制;拒绝解析到本仓库target/release之外的buzz/buzz-admin;要求权威替换操作必须使用BUZZ_RELAY_PRIVATE_KEY,绝不能用临时签名者替代。其实现方式是:通过psql直接向channel_members插入 1499 个 fixture + 1 个真实晚加入身份(位于第 1501 位),再用一次 API 添加成员强制中继经正常成员变更路径发出新的发现快照,随后依次断言发现、写入、对账保真与再发现。
第 6 步:继续深入
- 覆盖全部 CLI 命令(12 组、54+ 子命令)的完整清单,见 crates/buzz-cli/TESTING.md(从
channels、canvas、messages、diff 消息、reactions、DMs、users、频道成员、workflows、feed、论坛投票到 NIP-23 notes,含每个命令的预期输出与错误路径退出码)。 - 中继的 HTTP 桥接提供三个端点,便于测试
buzz-cli之外的其他客户端:
| Endpoint | 用途 |
|---|---|
POST /events | 提交一个已签名的 Nostr 事件 |
POST /query | NIP-01 过滤器查询(返回事件) |
POST /count | NIP-45 计数查询 |
三者都接受 NIP-98 认证(推荐),开发模式下也接受X-Pubkey头回退。消息线程没有 REST API——用带#e过滤器的POST /query,或buzz messages thread。
三、ACP Harness:与真实 Agent 的端到端联调(可选)
buzz-acp将支持 ACP 协议的 Agent(goose、codex、claude code、buzz-agent)接入中继。harness 监听事件、通过 stdio 驱动 Agent,Agent 再经由 MCP 工具回复。
最小配方——假设第 3 步中继在运行、第 4 步的$CHANNEL仍存在。Agent 身份必须与发送者身份不同(BUZZ_ACP_RESPOND_TO=anyone时仍会跳过 Agent 自己签名的事件):
cargo build --release -p buzz-acp export PATH="$PWD/target/release:$PATH" # 1. 保存第 4 步的发送者身份 —— 之后 @提及 Agent 要用 SENDER_SK="$BUZZ_PRIVATE_KEY" # 2. 铸造全新 Agent 身份并记录其公钥 AGENT_GEN=$(buzz-admin generate-key) AGENT_SK=$(echo "$AGENT_GEN" | awk '/Secret key:/ {print $3}') AGENT_PUBKEY=$(echo "$AGENT_GEN" | awk '/Public key:/ {print $3}') # 3. 将 Agent 加为 $CHANNEL 成员 —— 仍用发送者身份。 # 跳过这一步,Agent 启动时只会 "discovered 0 channel(s) → agent will # sit idle",并静默忽略所有提及。 buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member # 4. 切换到 Agent 身份并启动它。 # buzz-acp 需要 ws://(不是 http://)。若第 3 步把 BUZZ_RELAY_URL 设成了 # http:// 地址,这里请设对应的 ws:// 等价地址(同主机同端口)。 export BUZZ_PRIVATE_KEY="$AGENT_SK" export BUZZ_RELAY_URL=ws://localhost:3000 # 与第 3 步一致(覆盖过则如 ws://localhost:3030) export BUZZ_ACP_RESPOND_TO=anyone # 默认是 owner-only;测试时放开闸门 # NIP-AE 核心记忆提示注入默认开启;设 BUZZ_ACP_NO_MEMORY=true 可退出 export GOOSE_MODE=auto # 必须是 'auto',否则 goose 会在提示处挂起 buzz-acp # 前台;日志输出到 stdout(放独立终端) # 可选:默认日志太安静时开启逐轮追踪。 # RUST_LOG=buzz_acp=debug buzz-acp使用其他 ACP Agent?默认配方假定
goose在$PATH上且已配置(goose --version应能打印)。对 codex / claude code / buzz-agent,请相应设置BUZZ_ACP_AGENT_COMMAND与BUZZ_ACP_AGENT_ARGS——参见crates/buzz-acp/README.md。缺少这些时 buzz-acp 会在启动时无法生成 Agent 子进程而失败。
如果你在把 Agent 加进频道之前就启动了它,之后补跑add-member即可——它会实时收到成员变更通知并订阅,无需重启(日志中出现membership notification: subscribing to new channel …)。
Justfile 还提供just goose key="$AGENT_NSEC"(前台)与just goose-bg key="$AGENT_NSEC"(后台 screen 会话),两者设置相同的环境。并行 Agent、心跳、respond-to 闸门与论坛订阅见crates/buzz-acp/README.md。
测试延迟启动(deferred startup):启动buzz-acp前加上BUZZ_ACP_LAZY_POOL=true。harness 应能完成连接、认证、订阅并发布在线状态,而不启动配置的 ACP 子进程;第一条被接受、可排队的提及应恰好启动一个子进程并分发队列中的消息。自动化覆盖在pool_lifecycle_state(crates/buzz-acp/tests/pool_lifecycle_state.rs)中钉住了单次唤醒、重试/退避与过期结果行为;它不能替代这种真实中继/进程的 smoke 测试。
给 Agent 派活——把 shell 切回第 4 步的发送者身份并 @提及 Agent:
export BUZZ_PRIVATE_KEY=$SENDER_SK # 第 4 步的密钥 buzz messages send --channel "$CHANNEL" \ --content "Hey agent, reply PONG only." # 等待 10–90s,然后读频道 —— Agent 的回复是来自 AGENT_PUBKEY 的 kind:9。 # 当前 ACP 构建在一轮对话中 stdout 很安静, # `buzz messages get` 是确认其确实运行的方式。 buzz messages get --channel "$CHANNEL" --limit 5 | jq '.[] | {pubkey, content}'回复是同一频道内的 kind:9;buzz messages thread --channel <id> --event <event_id>可拉取某条提及的回复链。
四、配置参考:中继与 CLI 的环境变量
中继的所有配置都来自环境变量。配合just setup或just relay的默认值即可开箱即用。常见覆盖项:
| 变量 | 默认值 | 说明 |
|---|---|---|
BUZZ_BIND_ADDR | 0.0.0.0:3000 | 主应用端口 |
BUZZ_HEALTH_PORT | 8080 | /_liveness、/_readiness |
BUZZ_METRICS_PORT | 9102 | Prometheus/metrics |
RELAY_URL | ws://localhost:3000 | 在 NIP-11 / NIP-42 challenge 中公布。注意:没有BUZZ_前缀。 |
DATABASE_URL | postgres://buzz:buzz_dev@localhost:5432/buzz | |
REDIS_URL | redis://localhost:6379 | |
BUZZ_REQUIRE_AUTH_TOKEN | false | 为 true 时 REST 强制 NIP-98(无X-Pubkey回退) |
BUZZ_REQUIRE_RELAY_MEMBERSHIP | false | 为 true 时仅relay_members中的公钥可连接 |
BUZZ_DRAIN_JITTER_MS | 0(关闭) | 优雅停机时每个活跃 WebSocket 收到1012 Service Restart关闭前的随机延迟上限(毫秒)。0表示所有 socket 同时关闭(旧行为);正值将关闭在[1, value]ms 内均匀铺开,避免滚动部署时的重连惊群。大于20000的值被截断为20000(MAX_DRAIN_JITTER_MS),为中继 30s 硬排空超时下的关闭帧投递留出余量。空或纯空白视为未设置(关闭);非整数会在启动时大声失败。 |
BUZZ_AUDIT_ENABLED | true | 防篡改的事件/媒体审计日志。设为false/0/off可跳过其 DB 连接池与写入。不会关闭独立的审核(moderation)审计线索。 |
BUZZ_AUTO_MIGRATE | false | 以true/1/yes/on显式开启:启动时运行内置 SQLx 迁移 |
RELAY_OWNER_PUBKEY | 未设置 | 首次启动时在relay_members中引导为owner |
BUZZ_ALLOW_NIP_OA_AUTH | false | 启用 NIP-OA 所有者认证(owner attestation)用于成员资格 |
BUZZ_WEB_DIR | 未设置(源码态)//srv/buzz/web(容器态) | 存放邀请落地页 bundle 的目录;生产容器启用它,使/invite/{code}始终可用 |
BUZZ_SERVE_GIT_WEB_GUI | false | 设为true或1暴露随附的 Git 仓库浏览器(/与/repos/...);邀请路由不依赖此开关 |
以上大多数变量在 crates/buzz-relay/src/config.rs 中有对应解析实现,例如BUZZ_DRAIN_JITTER_MS的解析、截断到MAX_DRAIN_JITTER_MS = 20_000以及非整数启动失败的路径,均有单元测试覆盖(如60000被截断、空字符串视为未设置等断言)。
CLI 侧,测试只关心两个变量:
| 变量 | 默认值 | 说明 |
|---|---|---|
BUZZ_RELAY_URL | http://localhost:3000 | CLI 中继地址;接受ws(s)://并规范化 |
BUZZ_PRIVATE_KEY | —(必需) | nsec1…或 64 位 hex |
BUZZ_AUTH_TAG | 未设置 | 可选的 NIP-OA 所有者认证 JSON |
五、故障排查速查表
| 症状 | 原因 | 修复 |
|---|---|---|
代码改动后出现relay error 500或400: restricted: not a channel member | 二进制陈旧 | 重新构建并重新导出PATH;或直接cargo run |
中继启动时Address already in use(macOS 为 os error 48,Linux 为 98) | 另一个中继(或陈旧进程)占用了:3000/:8080/:9102(或你的覆盖端口) | 指标监听失败会以metrics_bind生命周期终态(reasonbind)报出。用lsof -iTCP:3000,8080,9102 -sTCP:LISTEN(或覆盖后的端口)检查。杀掉占用者(pkill -f buzz-relay)或用第 3 步的端口覆盖块。若已覆盖仍冲突,说明之前的开发者留了一个运行在相同替代端口上的中继——杀掉它或换新端口 |
auth_error: BUZZ_PRIVATE_KEY is required | 环境变量未导出到 CLI 的 shell | export BUZZ_PRIVATE_KEY=...(或传--private-key) |
auth_error: BUZZ_AUTH_TAG verification failed … signature verification failed | 从父 shell 继承了陈旧的BUZZ_AUTH_TAG,本地开发中继拒绝它 | unset BUZZ_AUTH_TAG(见第 1 步的清理块) |
关闭的中继上auth-required: verification failed | 需要 NIP-OA 认证 | 把BUZZ_AUTH_TAG设为主人签发的 JSON,或放宽BUZZ_REQUIRE_RELAY_MEMBERSHIP |
channels create后channels list为空 | CLI 不回显频道 UUID | 用第 4 步的过滤器;或POST /query携带{"kinds":[39002]} |
| ACP Agent 忽略所有事件 | 默认BUZZ_ACP_RESPOND_TO=owner-only且未配置 owner | 测试时设BUZZ_ACP_RESPOND_TO=anyone |
ACP 日志discovered 0 channel(s)/no channel subscriptions resolved | Agent 身份不是任何频道的成员 | 用另一个身份执行buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member |
GOOSE_MODE警告、Agent 挂起 | 未设置 | export GOOSE_MODE=auto |
| 本地测试通过但 CI 失败 | 忘了跑just ci | just ci执行整门检查(fmt、clippy、单元测试、desktop/web 构建) |
六、结语:一条可复现的验证流水线
把整份 TESTING.md 串联起来,就得到一条从代码到真实 Agent 行为的完整验证流水线:
- 静态与单元层:
just test-unit(零基础设施)覆盖核心、认证、CLI、ACP 信任边界、数据库迁移器、多租户回放与授权决策网格; - 集成层:
just test自动拉起 Postgres/Redis 后跑 DB 与集成套件; - 实时中继层:
just bootstrap+just setup准备环境,release 构建buzz-relay后,用 NIP-98 签名的buzzCLI 完成建频道、发消息、读回的最小 smoke; - 边界压力层:scripts/e2e-large-channel-roster.sh 以四行 PASS 证明千人以上名单的发现、写入与定向对账保真;
- 真实 Agent 层:
buzz-acp+BUZZ_ACP_RESPOND_TO=anyone驱动 goose/codex/claude code/buzz-agent 完成带成员资格的 @提及回复闭环,pool_lifecycle_state测试再钉住延迟启动的生命周期行为。
每一步都有对应的源码与脚本可作为证据:环境变量语义见 crates/buzz-relay/src/config.rs,任务编排见 Justfile,CLI 全量命令清单见 crates/buzz-cli/TESTING.md。照此流程,无论是开发者、测试人员还是 Agent 本身,都能在数分钟内获得一台可信的本地中继并完成任意一次端到端验证。
【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考