#!/usr/bin/env bash # End-to-end check of a TinyGo-built torly binary against a real Nostr client. # # Requires: curl, jq and nak (the "nostr army knife"). Build first with # `make build`, then run `make e2e`. set -euo pipefail BIN=${BIN:-$(pwd)/torly} PORT=${PORT:-34587} BASE="http://127.0.0.1:${PORT}" RELAY="ws://127.0.0.1:${PORT}/" DATA=$(mktemp -d /tmp/torly-e2e.XXXXXX) PID="" SK=${SK:-$(nak key generate)} PUB=$(nak key public "$SK") step() { printf '\n== %s\n' "$*"; } fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } cleanup() { if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then kill -9 "$PID" 2>/dev/null || true fi if [ "${KEEP:-0}" = "1" ]; then printf 'kept data directory: %s\n' "$DATA" >&2 else rm -rf "$DATA" fi } trap cleanup EXIT start() { TORLY_DATA_DIR="$DATA" TORLY_PORT="$PORT" TORLY_LISTEN=127.0.0.1 \ TORLY_APP_NAME=torly-e2e "$BIN" serve >>"$DATA/server.log" 2>&1 & PID=$! for _ in $(seq 1 100); do if curl -fsS "$BASE/health" >/dev/null 2>&1; then return 0; fi sleep 0.1 done cat "$DATA/server.log" >&2 fail "relay did not start" } stop() { kill -TERM "$PID" for _ in $(seq 1 60); do if ! kill -0 "$PID" 2>/dev/null; then wait "$PID" 2>/dev/null || true PID="" return 0 fi sleep 0.1 done fail "relay did not shut down on SIGTERM" } publish() { # publish [tag...] local kind=$1 content=$2 shift 2 nak event -k "$kind" -c "$content" "$@" --sec "$SK" "$RELAY" 2>/dev/null } event_id() { grep -o '"id":"[0-9a-f]*"' | head -1 | cut -d'"' -f4 } step "start" start curl -fsS "$BASE/health" | grep -q '"status":"ok"' || fail "health endpoint" step "NIP-11" curl -fsS -H 'Accept: application/nostr+json' "$BASE/" | jq -e '.name == "torly-e2e" and (.supported_nips | length) > 0' >/dev/null || fail "NIP-11 document" step "publish and query" OUT=$(publish 1 "e2e hello from the shell" -t t=e2e) E2E_ID=$(printf '%s' "$OUT" | event_id) [ -n "$E2E_ID" ] || fail "publish did not return an event id" nak req -k 1 -t t=e2e "$RELAY" 2>/dev/null | grep -q "$E2E_ID" || fail "event was not queryable" step "count" nak count -k 1 -t t=e2e "$RELAY" 2>&1 | grep -Eq ': 1$' || fail "count mismatch" step "search" publish 1 "the quick brown fox jumps over torly" -t t=search >/dev/null nak req -k 1 --search "quick brown" -t t=search "$RELAY" 2>/dev/null | grep -q "quick brown fox" || fail "search did not find the event" step "replaceable event" publish 0 '{"name":"first"}' >/dev/null sleep 1 publish 0 '{"name":"second"}' >/dev/null nak req -k 0 -a "$PUB" "$RELAY" 2>/dev/null | grep -q 'name.*second' || fail "newest replaceable event was not returned" step "deletion" OUT=$(publish 1 "delete this event") ID=$(printf '%s' "$OUT" | event_id) OUT=$(publish 5 "" -t "e=$ID") sleep 0.3 if nak req -i "$ID" "$RELAY" 2>/dev/null | grep -q "$ID"; then fail "deleted event is still served" fi step "blossom upload" printf 'torly e2e blob payload\n' >"$DATA/blob.txt" DESC=$(nak blossom -s "$BASE" upload "$DATA/blob.txt" --sec "$SK" 2>/dev/null) HASH=$(printf '%s' "$DESC" | jq -r '.sha256') [ -n "$HASH" ] && [ "$HASH" != "null" ] || fail "blossom upload failed: $DESC" curl -fsS "$BASE/$HASH" | grep -q "torly e2e blob payload" || fail "blob download" nak blossom -s "$BASE" list "$PUB" --sec "$SK" 2>/dev/null | grep -q "$HASH" || fail "blob list" nak blossom -s "$BASE" delete "$HASH" --sec "$SK" >/dev/null 2>&1 || fail "blob delete" CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/$HASH") [ "$CODE" = "404" ] || fail "blob still served after delete ($CODE)" step "graceful shutdown, restart, persistence" stop start RESULT=$(nak req -k 1 -t t=e2e "$RELAY" 2>/dev/null || true) printf '%s' "$RESULT" | grep -q "$E2E_ID" || fail "event did not survive a graceful restart (wanted $E2E_ID, got: $RESULT)" step "crash recovery from the write-ahead log" OUT=$(publish 1 "written just before the crash" -t t=crash) CRASH_ID=$(printf '%s' "$OUT" | event_id) kill -9 "$PID" wait "$PID" 2>/dev/null || true PID="" start nak req -k 1 -t t=crash "$RELAY" 2>/dev/null | grep -q "$CRASH_ID" || fail "event written before SIGKILL was not recovered from the WAL" step "shutdown" stop printf '\nPASS: torly end-to-end checks\n'