| name | backend-testing |
| description | Test backend services end-to-end. Use when the user wants to drive HTTP/gRPC/GraphQL/WebSocket endpoints against a real server, correlate requests with server logs (file/Docker/Kubernetes), verify database side-effects with snapshot/diff or watch-changes, replay captured curl/HAR requests, or seed and roll back fixtures inside a writable transaction. |
| allowed-tools | Bash(ironbee-backend-devtools-cli:*) |
Backend Testing Skill
End-to-end backend verification with the IronBee Backend DevTools CLI. Drive requests, watch logs, and assert on database state — all in one session.
See the full CLI reference at ironbee-backend-devtools-cli. Domain-level docs: request, log, db, o11y.
When to Use
This skill activates when:
- User wants to test backend APIs over HTTP/1.1, HTTP/2, gRPC, GraphQL, or WebSocket
- User needs to assert on log lines that a request produced (file, Docker, or Kubernetes logs)
- User needs to verify that a request changed exactly the right database rows
- User wants to replay a captured
curl command or HAR entry
- User needs to seed test fixtures and roll them back automatically
- User wants to correlate a request chain with a single W3C trace id
Core principle: correlate by trace id
Pin a trace id once on the session and every request automatically carries it. Then read your server logs filtered by that trace id — that's the cleanest agent-driven verification loop.
SESSION="--session-id e2e"
TRACE=$(ironbee-backend-devtools-cli $SESSION --json o11y new-trace-id | jq -r .traceId)
ironbee-backend-devtools-cli $SESSION request http --url "https://api.example.com/orders" --method POST --body '{"kind":"json","value":{"sku":"ABC"}}'
ironbee-backend-devtools-cli $SESSION --json log read --source app --pattern "$TRACE" --parse-json --select '["timestamp","level","msg","span_id"]'
Capabilities
HTTP / GraphQL / gRPC / WebSocket requests
ironbee-backend-devtools-cli --json request http \
--url "https://api.example.com/orders" \
--method POST \
--body '{"kind":"json","value":{"sku":"ABC","qty":2}}'
ironbee-backend-devtools-cli --json request graphql \
--url "https://api.example.com/graphql" \
--query 'mutation($s:String!){createOrder(sku:$s){id}}' \
--variables '{"s":"ABC"}'
ironbee-backend-devtools-cli --json request grpc \
--target "api.example.com:443" \
--service "orders.v1.OrderService" \
--method "GetOrder" \
--proto-source '{"kind":"protoFile","path":"./protos/orders.proto"}' \
--request '{"orderId":"abc-123"}'
CONN=$(ironbee-backend-devtools-cli --json request websocket-open --url "wss://api.example.com/stream" | jq -r .connectionId)
ironbee-backend-devtools-cli request websocket-send --connection-id "$CONN" --data '{"kind":"json","value":{"subscribe":"orders"}}'
ironbee-backend-devtools-cli --json request websocket-receive --connection-id "$CONN" --max-count 50 --timeout-ms 5000
ironbee-backend-devtools-cli request websocket-close --connection-id "$CONN"
Session state: cookies, default headers, default gRPC metadata
ironbee-backend-devtools-cli request set-default-headers \
--host "api.example.com" \
--headers '{"Authorization":"Bearer $TOKEN"}'
ironbee-backend-devtools-cli request set-default-metadata \
--target "api.example.com:443" \
--metadata '{"x-tenant-id":"acme"}'
ironbee-backend-devtools-cli request list-default-headers
ironbee-backend-devtools-cli request list-cookies
ironbee-backend-devtools-cli request clear-cookies --domain "api.example.com"
Replay captured calls (curl or HAR)
ironbee-backend-devtools-cli --json request replay \
--source '{"kind":"curl","value":"curl -X POST https://api.example.com/orders -d ..."}' \
--overrides '{"headers":{"Authorization":"Bearer NEW"}}' \
--new-trace-id
Log capture: register → read or follow
ironbee-backend-devtools-cli log register-source --name app --type file --path /var/log/app.log
ironbee-backend-devtools-cli --json log read --source app --tail 200 \
--level ERROR --parse-json --json-filter '{"span.kind":"server"}'
ironbee-backend-devtools-cli --json log read --source app --pattern "TypeError" --coalesce true \
--context-before 2 --context-after 10
FOLLOW=$(ironbee-backend-devtools-cli --json log follow --source app --max-buffer-lines 1000 | jq -r .followId)
ironbee-backend-devtools-cli request http --url "https://api.example.com/orders" --method POST --body '{"kind":"json","value":{}}'
ironbee-backend-devtools-cli --json log get-followed --follow-id "$FOLLOW" --drain --pattern "trace_id="
ironbee-backend-devtools-cli log stop-follow --follow-id "$FOLLOW"
ironbee-backend-devtools-cli log register-source --name api --type docker --container my-api
ironbee-backend-devtools-cli log register-source --name worker --type kubernetes --pod worker-0 --namespace prod
ironbee-backend-devtools-cli --json log read-multi --sources '["app","api","worker"]' --tail 500 --pattern "$TRACE"
Database verification: snapshot → request → snapshot → diff
ironbee-backend-devtools-cli db connect --name main --type postgres --connection-string-env DATABASE_URL
BEFORE=$(ironbee-backend-devtools-cli --json db snapshot \
--connection main --table orders --where "customer_id = $1" --params '["cust-1"]' | jq -r .snapshotId)
ironbee-backend-devtools-cli request http --url "https://api.example.com/orders" --method POST --body '{"kind":"json","value":{"customer_id":"cust-1"}}'
AFTER=$(ironbee-backend-devtools-cli --json db snapshot \
--connection main --table orders --where "customer_id = $1" --params '["cust-1"]' | jq -r .snapshotId)
ironbee-backend-devtools-cli --json db diff --from "$BEFORE" --to "$AFTER"
Database watch-changes (async writes)
WATCH=$(ironbee-backend-devtools-cli --json db watch-changes \
--connection main --table outbox --poll-interval-ms 500 | jq -r .watchId)
ironbee-backend-devtools-cli request http --url "https://api.example.com/orders" --method POST --body '{"kind":"json","value":{}}'
ironbee-backend-devtools-cli --json db get-changes --watch-id "$WATCH" --drain
Writable transactions: fixtures with auto-rollback
ironbee-backend-devtools-cli db connect --name main --type postgres --connection-string-env TEST_DATABASE_URL --allow-writes
ironbee-backend-devtools-cli db transaction-begin --connection main --writable
ironbee-backend-devtools-cli db seed --connection main --table users \
--rows '[{"id":"u1","email":"a@example.com"},{"id":"u2","email":"b@example.com"}]'
ironbee-backend-devtools-cli db transaction-rollback --connection main
Readonly probes: list-tables / describe-table / query
ironbee-backend-devtools-cli --json db list-tables --connection main
ironbee-backend-devtools-cli --json db describe-table --connection main --table orders
ironbee-backend-devtools-cli --json db query --connection main \
--sql "SELECT id, status FROM orders WHERE customer_id = \$1" --params '["cust-1"]'
Canonical flows
A. Log-correlated request → assertion
SESSION="--session-id flow-a"
TRACE=$(ironbee-backend-devtools-cli $SESSION --json o11y new-trace-id | jq -r .traceId)
ironbee-backend-devtools-cli $SESSION log register-source --name app --type file --path /var/log/app.log
FOLLOW=$(ironbee-backend-devtools-cli $SESSION --json log follow --source app --max-buffer-lines 2000 | jq -r .followId)
ironbee-backend-devtools-cli $SESSION request http \
--url "https://api.example.com/orders" --method POST \
--body '{"kind":"json","value":{"sku":"ABC"}}'
ironbee-backend-devtools-cli $SESSION --json log get-followed \
--follow-id "$FOLLOW" --drain --pattern "$TRACE" \
--parse-json --select '["timestamp","level","msg","error"]'
ironbee-backend-devtools-cli $SESSION log stop-follow --follow-id "$FOLLOW"
B. Database-correlated request → diff
SESSION="--session-id flow-b"
ironbee-backend-devtools-cli $SESSION db connect --name main --type postgres --connection-string-env DATABASE_URL
BEFORE=$(ironbee-backend-devtools-cli $SESSION --json db snapshot --connection main --table orders | jq -r .snapshotId)
ironbee-backend-devtools-cli $SESSION request http \
--url "https://api.example.com/orders" --method POST \
--body '{"kind":"json","value":{"sku":"ABC"}}'
AFTER=$(ironbee-backend-devtools-cli $SESSION --json db snapshot --connection main --table orders | jq -r .snapshotId)
ironbee-backend-devtools-cli $SESSION --json db diff --from "$BEFORE" --to "$AFTER"
C. Sandboxed fixture write inside a transaction
SESSION="--session-id flow-c"
ironbee-backend-devtools-cli $SESSION db connect --name test --type postgres \
--connection-string-env TEST_DATABASE_URL --allow-writes
ironbee-backend-devtools-cli $SESSION db transaction-begin --connection test --writable
ironbee-backend-devtools-cli $SESSION db seed --connection test --table customers \
--rows '[{"id":"cust-1","email":"a@example.com"}]'
ironbee-backend-devtools-cli $SESSION db transaction-rollback --connection test
Best Practices
- Pin a trace id at the start of a flow —
o11y new-trace-id once, then correlate logs / spans / db audit rows by it.
- Treat 4xx / 5xx and non-OK gRPC as data, not errors. Only transport-level failures (DNS, TLS, timeout, abort) populate the
error field.
- Prefer
--connection-string-env over inline credentials — the value stays in process.env and never enters the agent's context.
- Default to readonly db access. Open with
--allow-writes only when you need fixtures, and always wrap writes in a transaction you roll back.
- Use the cookie jar for login flows. It's on by default — set credentials once and every subsequent request inherits the session.
- Pin host-scoped default headers (
request set-default-headers) for auth tokens instead of passing --headers on every call.
- Set up log followers BEFORE the request so the buffer captures lines emitted during the call.
log get-followed and db get-changes drain by default; passing --drain is a no-op but harmless. To peek without clearing, call via MCP / run execute with drain: false.
- Snapshot just the affected rows with a tight
--where clause — full-table snapshots are slow and noisy in the diff.
- Compose flows in a scenario when you'll reuse them — see scenario reference. Scenarios are platform-agnostic and work on the backend CLI too.