Files
buzz/TESTING.md

11 KiB

Sprout — Local Testing Guide

How to run a local Sprout instance and test it with multiple goose agents communicating over the relay.


1. Overview

This guide walks through:

  1. Starting the backing services (MySQL, Redis, Typesense) via Docker Compose
  2. Building and running the relay server
  3. Creating test channels and adding members via SQL
  4. Minting API tokens for each agent via sprout-admin
  5. Launching goose agents with the sprout-mcp extension
  6. Verifying that agents can send and receive messages
  7. Running the automated test suite (unit + integration + e2e)

Outcome: Two or more goose agents connected to a local relay, exchanging messages through a shared channel, with all traffic verifiable in relay logs and the database.


2. Prerequisites

Requirement Version Notes
Docker + Docker Compose 24+ docker compose (v2 plugin)
Rust toolchain 1.88+ via Hermit or rustup
goose CLI latest goose --version
mysql client any for running SQL commands; or use Adminer at http://localhost:8082

Hermit (recommended): If the repo has a .hermit/ directory, activate it with . bin/activate-hermit — this pins the exact Rust version.


3. Start Infrastructure

cd REPOS/sprout

# Copy env config (only needed once)
cp .env.example .env

# Start MySQL, Redis, Typesense, and Adminer
docker compose up -d

# Verify all services are healthy
docker compose ps

Expected output — all services should show healthy:

NAME                STATUS
sprout-mysql        running (healthy)
sprout-redis        running (healthy)
sprout-typesense    running (healthy)
sprout-adminer      running

Tip: If services aren't healthy after ~30 seconds, check logs: docker compose logs mysql or docker compose logs redis

Run migrations:

just migrate

Expected:

Running migrations via sqlx...
Applied 1 migration(s).

Alternative (no sqlx CLI): just migrate falls back to docker exec automatically.


4. Build and Run the Relay

⚠️ Port 3000 conflict: The relay binds to 0.0.0.0:3000 by default. If another process is using port 3000 (e.g., a Node.js dev server), set SPROUT_BIND_ADDR=0.0.0.0:3001 in .env and update RELAY_URL=ws://localhost:3001.

⚠️ .env and cargo run: just relay uses set dotenv-load := true so env vars are loaded automatically. If you run cargo run -p sprout-relay directly, the .env file is not loaded — export vars manually or use just relay.

# Build the workspace first (catches compile errors early)
cargo build --workspace

# Run the relay in a detached screen session
screen -dmS sprout-relay just relay

Verify the relay is listening:

screen -r sprout-relay
# Press Ctrl-A D to detach without stopping

Expected log output:

INFO sprout_relay: listening on 0.0.0.0:3000
WARN sprout_relay: SPROUT_REQUIRE_AUTH_TOKEN is false — relay accepts unauthenticated connections.

The auth warning is expected in local dev. Set SPROUT_REQUIRE_AUTH_TOKEN=true in .env to enforce token auth.


5. Create Test Channels

Connect to MySQL and create a channel, then add members after minting tokens (step 6 gives you pubkeys).

mysql -u sprout -psprout_dev -h 127.0.0.1 sprout
-- Create a test channel (channel ID must be a 16-byte UUID stored as BINARY(16))
INSERT INTO channels (id, name, channel_type, visibility, created_by)
VALUES (
    UNHEX(REPLACE(UUID(), '-', '')),
    'agent-test',
    'stream',
    'open',
    X'0000000000000000000000000000000000000000000000000000000000000001'
);

-- Capture the channel ID for later steps
SELECT HEX(id) AS channel_id, name FROM channels WHERE name = 'agent-test';

Note: channel_members entries require a valid pubkey (32-byte Nostr public key). Add members after minting tokens in step 6.


6. Mint Agent Tokens

sprout-admin creates API tokens and optionally generates a new Nostr keypair per agent. Run once per agent.

⚠️ Save the output immediately — the raw token and private key (nsec) are shown only once.

# Agent 1
cargo run -p sprout-admin -- mint-token \
  --name "agent-alice" \
  --scopes "messages:read,messages:write,channels:read"
# Agent 2
cargo run -p sprout-admin -- mint-token \
  --name "agent-bob" \
  --scopes "messages:read,messages:write,channels:read"

Expected output (per agent):

╔══════════════════════════════════════════════════════════════╗
║  Token minted successfully!                                 ║
╠══════════════════════════════════════════════════════════════╣
║  Token ID:    <uuid>                                        ║
║  Name:        agent-alice                                   ║
║  Scopes:      messages:read,messages:write,channels:read    ║
║  Pubkey:      <first 48 hex chars>...                       ║
╠══════════════════════════════════════════════════════════════╣
║  ⚠️  SAVE THESE — shown only once!                          ║
╠══════════════════════════════════════════════════════════════╣
║  Private key (nsec):                                        ║
║  nsec1...                                                   ║
║                                                              ║
║  API Token:                                                  ║
║  spr_...                                                     ║
╚══════════════════════════════════════════════════════════════╝

Add agents as channel members (using the full pubkey hex from the output):

-- In mysql client — replace <PUBKEY_HEX> with each agent's full 64-char hex pubkey
INSERT INTO channel_members (channel_id, pubkey, role)
SELECT id, UNHEX('<ALICE_PUBKEY_HEX>'), 'member'
FROM channels WHERE name = 'agent-test';

INSERT INTO channel_members (channel_id, pubkey, role)
SELECT id, UNHEX('<BOB_PUBKEY_HEX>'), 'member'
FROM channels WHERE name = 'agent-test';

List all tokens to verify:

cargo run -p sprout-admin -- list-tokens

7. Launch Agents

Each agent runs in its own terminal with its own token and private key. The sprout-mcp extension connects to the relay via stdio transport.

Environment variables for sprout-mcp:

Variable Description Default
SPROUT_RELAY_URL WebSocket URL of the relay ws://localhost:3000
SPROUT_API_TOKEN API token from step 6 (none — unauthenticated)
SPROUT_PRIVATE_KEY Nostr private key (nsec1...) generates ephemeral key

Terminal 1 — Agent Alice:

SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_API_TOKEN=spr_<alice-token> \
SPROUT_PRIVATE_KEY=nsec1<alice-key> \
goose run --no-profile \
  --with-extension "cargo run -p sprout-mcp" \
  --instructions "You are Alice. Join the agent-test channel and say hello."

Terminal 2 — Agent Bob:

SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_API_TOKEN=spr_<bob-token> \
SPROUT_PRIVATE_KEY=nsec1<bob-key> \
goose run --no-profile \
  --with-extension "cargo run -p sprout-mcp" \
  --instructions "You are Bob. Join the agent-test channel and respond to Alice."

Note: cargo run -p sprout-mcp builds and runs the MCP server inline. For faster startup after the first build, use the compiled binary: ./target/debug/sprout-mcp-server.


8. Verify Conversations

Check relay logs (in the screen session):

screen -r sprout-relay

Look for lines like:

DEBUG sprout_relay: authenticated pubkey=<hex>
DEBUG sprout_relay: EVENT accepted kind=40001 channel=<id>
DEBUG sprout_relay: delivered to 2 subscriber(s)

Query the database for messages:

SELECT
    HEX(channel_id) AS channel,
    content,
    created_at
FROM events
WHERE channel_id = (SELECT id FROM channels WHERE name = 'agent-test')
ORDER BY created_at DESC
LIMIT 20;

Read channel history via MCP (from within a goose session with sprout-mcp loaded):

Use the sprout MCP tool to list messages in the agent-test channel.

9. Running the Test Suite

Unit tests (no infrastructure required)

just test-unit
# or equivalently:
./scripts/run-tests.sh unit

Runs sprout-core and sprout-auth unit tests. No Docker needed.

Integration tests (requires running services)

just test-integration
# or equivalently:
./scripts/run-tests.sh integration

Starts services if not running, applies migrations, then tests sprout-db and sprout-auth integration.

All tests

just test

E2E relay tests (requires running relay)

The e2e tests in crates/sprout-test-client/tests/e2e_relay.rs are marked #[ignore] by default. Run them explicitly with a live relay:

# Relay must be running (step 4)
cargo test --test e2e_relay -- --ignored --nocapture

# Override relay URL if not on default port:
RELAY_URL=ws://localhost:3001 cargo test --test e2e_relay -- --ignored --nocapture

Key e2e tests:

  • test_connect_and_authenticate — NIP-42 auth handshake
  • test_send_event_and_receive_via_subscription — pub/sub round-trip
  • test_multiple_concurrent_clients — 3 clients, 1 sender, all receive
  • test_unauthenticated_rejected — auth enforcement
  • test_pubkey_mismatch_rejected — impersonation prevention

10. Troubleshooting

Symptom Likely Cause Fix
Connection refused on port 3000 Relay not running screen -r sprout-relay to check; restart with screen -dmS sprout-relay just relay
Port 3000 already in use Another process (Node, etc.) Set SPROUT_BIND_ADDR=0.0.0.0:3001 and RELAY_URL=ws://localhost:3001 in .env
auth: invalid token Wrong or missing SPROUT_API_TOKEN Re-run mint-token; verify token in SPROUT_API_TOKEN env var
Agent connects but can't post Not a channel member Run the INSERT INTO channel_members SQL from step 6
DATABASE_URL errors in cargo run .env not loaded Use just relay instead of cargo run directly, or `export $(cat .env
MySQL unhealthy after docker compose up Slow start Wait 30s; check docker compose logs mysql for errors
sprout-mcp generates ephemeral key SPROUT_PRIVATE_KEY not set Set SPROUT_PRIVATE_KEY=nsec1... so the agent's identity persists across restarts
E2e tests time out Relay not running or wrong URL Check RELAY_URL env var; confirm relay is listening with curl http://localhost:3000/info
SQLX_OFFLINE errors in CI Missing .sqlx/ query cache Run cargo sqlx prepare --workspace locally and commit the .sqlx/ directory