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