# Testing ## Automated Tests ```bash just test-unit # unit tests — no infrastructure needed just test # unit + integration (starts Docker if needed) ``` `just test` runs unit tests plus integration tests against Postgres and Redis (started automatically if not already running). Neither task runs the E2E suites in `buzz-test-client` — those are marked `#[ignore]` and require a running relay: ```bash # Start a relay first (see below), then: cargo test -p buzz-test-client -- --ignored ``` --- ## Live Local Relay The fastest way to exercise the relay end-to-end is to build the release binaries once, run `buzz-relay`, and drive it with the `buzz` CLI. The CLI signs every request with NIP-98, so you don't need `nak` or hand-rolled `curl`. ### 1. Setup ```bash . ./bin/activate-hermit # activate pinned toolchain cp .env.example .env # one-time just setup # start Docker services, run migrations ``` > **Already running Buzz Desktop?** Desktop uses the same Docker container > names (`buzz-postgres`, `buzz-redis`) and the same > default ports (`:5432`, `:6379`). `just setup` will reuse those > services, so **your test relay writes into Desktop's database**. That's > fine for read/write smoke tests, but: `just reset` wipes Desktop's data > along with yours. If you need isolation, stop Desktop first or run the > dev stack on a different Compose project > (`COMPOSE_PROJECT_NAME=buzz-dev docker compose …`). `just reset` wipes all local data and starts over — **including Buzz Desktop's data** if its services are sharing your dev stack (see callout above). > **Heads up — scrub stale env first.** If your shell inherits any of > `BUZZ_AUTH_TAG`, `BUZZ_RELAY_URL`, or `BUZZ_PRIVATE_KEY` from a > prior session (or a staging config), `unset` them before continuing. > A stale `BUZZ_AUTH_TAG` fails the **local dev relay** with > `auth_error: signature verification failed` on the first CLI write — > it is *not* tolerated. > ```bash > unset BUZZ_AUTH_TAG BUZZ_RELAY_URL BUZZ_PRIVATE_KEY > ``` ### 2. Build the binaries ```bash cargo build --release -p buzz-relay -p buzz-cli -p buzz-admin export PATH="$PWD/target/release:$PATH" ``` Rebuild after any code change — the steps below use the release binaries. ### 3. Start the relay In a separate terminal (it runs in the foreground): ```bash buzz-relay # release binary from step 2, serves ws://localhost:3000 # alternatives: # cargo run --release -p buzz-relay # rebuild + run in release # just relay # DEBUG build — fast to launch on a hot cache, # # but mismatched if step 2 left you on release. # # Use `just relay-release` if you want the recipe. ``` Verify it's up (back in your working terminal): ```bash curl -s http://localhost:3000/health # → ok curl -s http://localhost:8080/_readiness # → {"status":"ready"} ``` > Health/readiness/liveness live on a **separate port** (default `8080`, > `BUZZ_HEALTH_PORT`) so K8s probes bypass auth middleware. The main app > port also exposes `/health` for convenience. The relay starts in dev mode (`BUZZ_REQUIRE_AUTH_TOKEN=false`). The startup log emits a WARN about this — that's expected for local testing. See the env vars table at the bottom if you need to lock it down. > **Already running Buzz Desktop (or another relay) on `:3000` / `:8080` / > `:9102`?** Buzz binds three ports — main, health, metrics — and any of > them can collide. Use a separate terminal per role and export the right > vars in each: > > **In the relay terminal** (before launching `buzz-relay`): > ```bash > 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 # advertised in NIP-42 challenges > buzz-relay > ``` > > **In your working / CLI terminal** (for steps 4+ and the ACP harness): > ```bash > export BUZZ_RELAY_URL=http://localhost:3030 # CLI target > # verify the relay on the overridden ports: > curl -s http://localhost:3030/health # → ok > curl -s http://localhost:8088/_readiness # → {"status":"ready"} > ``` > > Every snippet later in this doc shows the defaults. When you see > `localhost:3000` / `:8080` in a code block, mentally substitute your > overrides — or the CLI will end up talking to Buzz Desktop's relay. > **Ignore `just setup`'s "Next steps" banner.** It still prints > `just relay` (a debug build). Use `buzz-relay` from step 2 here — > step 2 already built the release binary. When you're done, stop the relay (Ctrl-C in its terminal). If it's backgrounded or you lost the terminal: `pkill -f buzz-relay`. Leaving it running will collide with the next reviewer who follows this doc on the same machine. ### 4. Smoke test the CLI against the relay End-to-end: generate an identity, create a channel, post a message, read it back. This is the minimum sequence an agent needs to verify a local relay. ```bash # Generate a keypair 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" # Create a channel — the UUID is returned in the response CHANNEL=$(buzz channels create --name "smoke-$$" --type stream --visibility open | jq -r '.channel_id') echo "channel: $CHANNEL" # Send a message and read it back 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 . # Fetch the reply chain for a specific message (empty array on a leaf — that's fine) buzz messages thread --channel "$CHANNEL" --event "$EVENT_ID" | jq . ``` A successful run prints `{"event_id":"…","accepted":true,"message":""}` for the send, and the message body in the `get` output. `thread` returns `[]` for a leaf message — populated only after a reply comes in (see §5). ### 5. Going deeper For full coverage of every CLI command (54 subcommands across 12 groups), follow [`crates/buzz-cli/TESTING.md`](crates/buzz-cli/TESTING.md). The relay's HTTP bridge accepts three endpoints — useful if you're testing a client other than `buzz-cli`: | Endpoint | Purpose | |-----------------|------------------------------------| | `POST /events` | Submit a signed Nostr event | | `POST /query` | NIP-01 filter query (returns events) | | `POST /count` | NIP-45 count query | All three accept NIP-98 auth (recommended) or, in dev mode, an `X-Pubkey` header fallback. There is no REST API for fetching message threads — use `POST /query` with an `#e` filter, or `buzz messages thread`. --- ## ACP Harness (optional, end-to-end with a real agent) `buzz-acp` connects an ACP-speaking agent (goose, codex, claude code, buzz-agent) to the relay. The harness listens for events, drives the agent over stdio, and the agent replies through MCP tools. Minimum recipe — assumes the relay from step 3 is running and the channel `$CHANNEL` from step 4 still exists. The agent identity must be **different** from the sender identity (`BUZZ_ACP_RESPOND_TO=anyone` still skips events the agent signed itself). ```bash cargo build --release -p buzz-acp export PATH="$PWD/target/release:$PATH" # 1. Save your sender identity from step 4 — you'll need it to @mention the agent SENDER_SK="$BUZZ_PRIVATE_KEY" # 2. Mint a fresh agent identity and capture its pubkey 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. Add the agent as a member of $CHANNEL — still using the sender identity. # Skip this and the agent boots to "discovered 0 channel(s) → agent will # sit idle" and silently ignores every mention. buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member # 4. Switch to the agent identity and start it. # buzz-acp wants ws:// (not http://). If you set BUZZ_RELAY_URL to an # http:// URL in step 3, set the ws:// equivalent here — same host/port. export BUZZ_PRIVATE_KEY="$AGENT_SK" export BUZZ_RELAY_URL=ws://localhost:3000 # match step 3 (e.g. ws://localhost:3030 if overridden) export BUZZ_ACP_RESPOND_TO=anyone # default is owner-only; opens the gate for testing # NIP-AE core-memory prompt injection is on by default; set BUZZ_ACP_NO_MEMORY=true to opt out. export GOOSE_MODE=auto # must be 'auto' or goose hangs on prompts buzz-acp # foreground; logs to stdout (run in a separate terminal) # Optional: turn on per-turn tracing if the default log is too quiet. # RUST_LOG=buzz_acp=debug buzz-acp ``` > **Using a different ACP agent?** The default recipe assumes `goose` is on > `$PATH` and configured (`goose --version` should print). For codex / claude > code / buzz-agent, set `BUZZ_ACP_AGENT_COMMAND` and `BUZZ_ACP_AGENT_ARGS` > accordingly — see `crates/buzz-acp/README.md`. Without these, buzz-acp > will fail to spawn the agent subprocess on startup. If you started the agent before adding it to the channel, just run the `add-member` afterwards — it picks up the membership notification live and subscribes without restart (`membership notification: subscribing to new channel …`). The justfile also ships `just goose key="$AGENT_NSEC"` (foreground) and `just goose-bg key="$AGENT_NSEC"` (background screen session) which set the same env. See `crates/buzz-acp/README.md` for parallel agents, heartbeats, respond-to gates, and forum subscriptions. Send the agent a task — switch your shell back to the **sender** identity from step 4 and @mention the agent: ```bash export BUZZ_PRIVATE_KEY=$SENDER_SK # the key from step 4 buzz messages send --channel "$CHANNEL" \ --content "Hey agent, reply PONG only." # Wait 10–90s, then read the channel — the agent's reply is a kind:9 from # AGENT_PUBKEY. The current ACP build is quiet on stdout during a turn, so # `buzz messages get` is how you confirm it ran. buzz messages get --channel "$CHANNEL" --limit 5 | jq '.[] | {pubkey, content}' ``` Replies are kind:9 in the same channel; `buzz messages thread --channel --event ` fetches the reply chain for a specific mention. --- ## Configuration reference The relay reads all configuration from environment variables. Defaults work out of the box with `just setup` or `just relay`. Common overrides: | Variable | Default | Notes | |-----------------------------------|-----------------------------|-------| | `BUZZ_BIND_ADDR` | `0.0.0.0:3000` | Main app port | | `BUZZ_HEALTH_PORT` | `8080` | `/_liveness`, `/_readiness` | | `BUZZ_METRICS_PORT` | `9102` | Prometheus `/metrics` | | `RELAY_URL` | `ws://localhost:3000` | Advertised in NIP-11 / NIP-42 challenges. **Note: no `BUZZ_` prefix.** | | `DATABASE_URL` | `postgres://buzz:buzz_dev@localhost:5432/buzz` | | | `REDIS_URL` | `redis://localhost:6379` | | | `BUZZ_REQUIRE_AUTH_TOKEN` | `false` | When true, REST requires NIP-98 (no `X-Pubkey` fallback) | | `BUZZ_REQUIRE_RELAY_MEMBERSHIP` | `false` | When true, only pubkeys in `relay_members` can connect | | `BUZZ_REQUIRE_MEDIA_GET_AUTH` | `false` | When true, `GET`/`HEAD /media/*` require Blossom kind 24242 `t=get` auth plus relay membership. | | `BUZZ_AUTO_MIGRATE` | `false` | Opt in with `true`/`1`/`yes`/`on` to run embedded SQLx migrations on relay startup | | `RELAY_OWNER_PUBKEY` | unset | Bootstrapped as `owner` in `relay_members` at first start | | `BUZZ_ALLOW_NIP_OA_AUTH` | `false` | Enable NIP-OA owner attestation for membership | | `BUZZ_WEB_DIR` | unset (source), `/srv/buzz/web` (container) | Directory containing the invite landing bundle; the production container enables it so `/invite/{code}` always works | | `BUZZ_SERVE_GIT_WEB_GUI` | `false` | Set to `true` or `1` to expose the bundled Git repository browser at `/` and `/repos/...`; invite routes do not depend on this flag | CLI-side, only two matter for testing: | Variable | Default | Notes | |-------------------------|--------------------------|-------| | `BUZZ_RELAY_URL` | `http://localhost:3000` | CLI relay base; accepts `ws(s)://` and normalises | | `BUZZ_PRIVATE_KEY` | — (**required**) | `nsec1…` or 64-char hex | | `BUZZ_AUTH_TAG` | unset | Optional NIP-OA owner attestation JSON | --- ## Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | `relay error 500` or `400: restricted: not a channel member` after a code change | Stale binary | Rebuild and re-export `PATH`; or `cargo run` directly | | `Address already in use` on relay start (os error 48 on macOS, 98 on Linux) | Another relay (or stale process) holding `:3000` / `:8080` / `:9102` (or your override ports) | The panic line names the failing port — read it first. Then `lsof -iTCP:3000,8080,9102 -sTCP:LISTEN` (or your override equivalents). Kill the offender (`pkill -f buzz-relay`) or use the port-override block in step 3. If you already overrode and *still* collide, a prior reviewer left a relay running on the same alt ports — kill it or pick fresh ports | | `auth_error: BUZZ_PRIVATE_KEY is required` | Env not exported into the CLI's shell | `export BUZZ_PRIVATE_KEY=...` (or pass `--private-key`) | | `auth_error: BUZZ_AUTH_TAG verification failed … signature verification failed` | A stale `BUZZ_AUTH_TAG` inherited from a parent shell. The local dev relay rejects it. | `unset BUZZ_AUTH_TAG` (see the scrub block in step 1) | | `auth-required: verification failed` on a closed relay | NIP-OA attestation needed | Set `BUZZ_AUTH_TAG` to the owner-issued JSON, or relax `BUZZ_REQUIRE_RELAY_MEMBERSHIP` | | `channels list` empty after `channels create` | The CLI doesn't echo the channel UUID; use the filter shown in step 4 | Or `POST /query` with `{"kinds":[39002]}` | | ACP agent ignores all events | `BUZZ_ACP_RESPOND_TO=owner-only` (default) with no owner configured | Set `BUZZ_ACP_RESPOND_TO=anyone` for testing | | ACP logs `discovered 0 channel(s)` / `no channel subscriptions resolved` | Agent identity isn't a member of any channel | `buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member` from another identity | | `GOOSE_MODE` warning, agent hangs | Not set | `export GOOSE_MODE=auto` | | Tests pass locally but CI fails | Forgot to run `just ci` | `just ci` runs the gate (fmt, clippy, unit tests, desktop/web builds) |