e2e.sh raw

   1  #!/usr/bin/env bash
   2  # End-to-end check of a TinyGo-built torly binary against a real Nostr client.
   3  #
   4  # Requires: curl, jq and nak (the "nostr army knife"). Build first with
   5  # `make build`, then run `make e2e`.
   6  set -euo pipefail
   7  
   8  BIN=${BIN:-$(pwd)/torly}
   9  PORT=${PORT:-34587}
  10  BASE="http://127.0.0.1:${PORT}"
  11  RELAY="ws://127.0.0.1:${PORT}/"
  12  DATA=$(mktemp -d /tmp/torly-e2e.XXXXXX)
  13  PID=""
  14  SK=${SK:-$(nak key generate)}
  15  PUB=$(nak key public "$SK")
  16  
  17  step() { printf '\n== %s\n' "$*"; }
  18  fail() { printf 'FAIL: %s\n' "$*" >&2; exit 1; }
  19  cleanup() {
  20  	if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
  21  		kill -9 "$PID" 2>/dev/null || true
  22  	fi
  23  	if [ "${KEEP:-0}" = "1" ]; then
  24  		printf 'kept data directory: %s\n' "$DATA" >&2
  25  	else
  26  		rm -rf "$DATA"
  27  	fi
  28  }
  29  trap cleanup EXIT
  30  
  31  start() {
  32  	TORLY_DATA_DIR="$DATA" TORLY_PORT="$PORT" TORLY_LISTEN=127.0.0.1 \
  33  		TORLY_APP_NAME=torly-e2e "$BIN" serve >>"$DATA/server.log" 2>&1 &
  34  	PID=$!
  35  	for _ in $(seq 1 100); do
  36  		if curl -fsS "$BASE/health" >/dev/null 2>&1; then return 0; fi
  37  		sleep 0.1
  38  	done
  39  	cat "$DATA/server.log" >&2
  40  	fail "relay did not start"
  41  }
  42  
  43  stop() {
  44  	kill -TERM "$PID"
  45  	for _ in $(seq 1 60); do
  46  		if ! kill -0 "$PID" 2>/dev/null; then
  47  			wait "$PID" 2>/dev/null || true
  48  			PID=""
  49  			return 0
  50  		fi
  51  		sleep 0.1
  52  	done
  53  	fail "relay did not shut down on SIGTERM"
  54  }
  55  
  56  publish() { # publish <kind> <content> [tag...]
  57  	local kind=$1 content=$2
  58  	shift 2
  59  	nak event -k "$kind" -c "$content" "$@" --sec "$SK" "$RELAY" 2>/dev/null
  60  }
  61  
  62  event_id() {
  63  	grep -o '"id":"[0-9a-f]*"' | head -1 | cut -d'"' -f4
  64  }
  65  
  66  step "start"
  67  start
  68  curl -fsS "$BASE/health" | grep -q '"status":"ok"' || fail "health endpoint"
  69  
  70  step "NIP-11"
  71  curl -fsS -H 'Accept: application/nostr+json' "$BASE/" |
  72  	jq -e '.name == "torly-e2e" and (.supported_nips | length) > 0' >/dev/null || fail "NIP-11 document"
  73  
  74  step "publish and query"
  75  OUT=$(publish 1 "e2e hello from the shell" -t t=e2e)
  76  E2E_ID=$(printf '%s' "$OUT" | event_id)
  77  [ -n "$E2E_ID" ] || fail "publish did not return an event id"
  78  nak req -k 1 -t t=e2e "$RELAY" 2>/dev/null | grep -q "$E2E_ID" || fail "event was not queryable"
  79  
  80  step "count"
  81  nak count -k 1 -t t=e2e "$RELAY" 2>&1 | grep -Eq ': 1$' || fail "count mismatch"
  82  
  83  step "search"
  84  publish 1 "the quick brown fox jumps over torly" -t t=search >/dev/null
  85  nak req -k 1 --search "quick brown" -t t=search "$RELAY" 2>/dev/null |
  86  	grep -q "quick brown fox" || fail "search did not find the event"
  87  
  88  step "replaceable event"
  89  publish 0 '{"name":"first"}' >/dev/null
  90  sleep 1
  91  publish 0 '{"name":"second"}' >/dev/null
  92  nak req -k 0 -a "$PUB" "$RELAY" 2>/dev/null | grep -q 'name.*second' ||
  93  	fail "newest replaceable event was not returned"
  94  
  95  step "deletion"
  96  OUT=$(publish 1 "delete this event")
  97  ID=$(printf '%s' "$OUT" | event_id)
  98  OUT=$(publish 5 "" -t "e=$ID")
  99  sleep 0.3
 100  if nak req -i "$ID" "$RELAY" 2>/dev/null | grep -q "$ID"; then
 101  	fail "deleted event is still served"
 102  fi
 103  
 104  step "blossom upload"
 105  printf 'torly e2e blob payload\n' >"$DATA/blob.txt"
 106  DESC=$(nak blossom -s "$BASE" upload "$DATA/blob.txt" --sec "$SK" 2>/dev/null)
 107  HASH=$(printf '%s' "$DESC" | jq -r '.sha256')
 108  [ -n "$HASH" ] && [ "$HASH" != "null" ] || fail "blossom upload failed: $DESC"
 109  curl -fsS "$BASE/$HASH" | grep -q "torly e2e blob payload" || fail "blob download"
 110  nak blossom -s "$BASE" list "$PUB" --sec "$SK" 2>/dev/null | grep -q "$HASH" || fail "blob list"
 111  nak blossom -s "$BASE" delete "$HASH" --sec "$SK" >/dev/null 2>&1 || fail "blob delete"
 112  CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/$HASH")
 113  [ "$CODE" = "404" ] || fail "blob still served after delete ($CODE)"
 114  
 115  step "graceful shutdown, restart, persistence"
 116  stop
 117  start
 118  RESULT=$(nak req -k 1 -t t=e2e "$RELAY" 2>/dev/null || true)
 119  printf '%s' "$RESULT" | grep -q "$E2E_ID" ||
 120  	fail "event did not survive a graceful restart (wanted $E2E_ID, got: $RESULT)"
 121  
 122  step "crash recovery from the write-ahead log"
 123  OUT=$(publish 1 "written just before the crash" -t t=crash)
 124  CRASH_ID=$(printf '%s' "$OUT" | event_id)
 125  kill -9 "$PID"
 126  wait "$PID" 2>/dev/null || true
 127  PID=""
 128  start
 129  nak req -k 1 -t t=crash "$RELAY" 2>/dev/null | grep -q "$CRASH_ID" ||
 130  	fail "event written before SIGKILL was not recovered from the WAL"
 131  
 132  step "shutdown"
 133  stop
 134  printf '\nPASS: torly end-to-end checks\n'
 135