#!/usr/bin/env bash # v0.8 A3 integration test — registry + greetings across 3 local nodes. # # Spec: A3-SPEC §6 multi-node scenario. Run from the repo root after # `cargo build -p itsgoin-cli`. Uses FIFOs to drive the interactive REPL. # # 1. All nodes self-materialize the same registry post ID; node1 also # runs the --publish-registry genesis one-shot (debug gate override). # 2. node2 registers "Alice rust p2p" → node3 `search rust` finds her. # node2 `unregister` → node3 re-search finds nothing. # 3. node2's bio has a greeting slot; node3 greets it; node2 sees the # unsealed text + real sender persona; node2 replies; node3 sees the # reply. node1 (holder) stores only ciphertext. # 5. Newest-wins: node2 re-registers; node3 sees exactly one entry. # 6. (Optional, ITSGOIN_TEST_TTL_SECS) comment expiry sweep. # # Exit code 0 = all checks passed. Logs: /tmp/itsgoin-cli{1,2,3}.log set -u cd "$(dirname "$0")/.." BIN=target/debug/itsgoin [ -x "$BIN" ] || { echo "build first: cargo build -p itsgoin-cli"; exit 2; } PASS=0; FAIL=0 check() { # check local desc="$1"; shift if "$@" >/dev/null 2>&1; then PASS=$((PASS+1)); echo "PASS: $desc"; else FAIL=$((FAIL+1)); echo "FAIL: $desc"; fi } strip_ansi() { sed 's/\x1b\[[0-9;]*m//g' "$1" | grep -v "WARN.*netlink\|WARN.*buffer_tool"; } # Same noise filter for the tail-window checks below. The netlink/mDNS watcher # logs in unpredictable bursts, and a burst landing between a REPL command and # its assertion pushes the answer out of a raw `tail -N` window — a false # failure with nothing wrong in the node. quiet_tail() { grep -av "netlink\|buffer_tool\|iroh_quinn\|swarm_discovery" "$1" | tail -"$2"; } export -f quiet_tail sq() { sqlite3 "/tmp/itsgoin-test$1/itsgoin.db" "$2"; } cleanup() { { exec 13>&- 14>&- 15>&-; } 2>/dev/null || true kill $(pgrep -f 'itsgoin.*itsgoin-test') 2>/dev/null rm -f /tmp/itsgoin-cmd{1,2,3} } trap cleanup EXIT echo "== setup ==" kill $(pgrep -f 'itsgoin.*itsgoin-test') 2>/dev/null; sleep 1 for i in 1 2 3; do rm -rf /tmp/itsgoin-test$i /tmp/itsgoin-cmd$i /tmp/itsgoin-cli$i.log mkdir -p /tmp/itsgoin-test$i mkfifo /tmp/itsgoin-cmd$i done export ITSGOIN_TEST_ALLOW_REGISTRY_GENESIS=1 # Genesis one-shot on node1's data dir (debug-gate override). "$BIN" /tmp/itsgoin-test1 --bind 127.0.0.1:18411 --publish-registry > /tmp/itsgoin-genesis.log 2>&1 check "genesis prints registry post id matching shipped constant" \ bash -c 'g=$(grep registry_post_id /tmp/itsgoin-genesis.log | awk "{print \$2}"); c=$(grep shipped_constant /tmp/itsgoin-genesis.log | awk "{print \$2}"); [ -n "$g" ] && [ "$g" = "$c" ]' # Start the three nodes with FIFO-driven stdin. for i in 1 2 3; do ( exec "$BIN" /tmp/itsgoin-test$i --bind 127.0.0.1:1841$i \ < /tmp/itsgoin-cmd$i > /tmp/itsgoin-cli$i.log 2>&1 ) & done # Keep FIFO write ends open for the whole run. exec 13>/tmp/itsgoin-cmd1 14>/tmp/itsgoin-cmd2 15>/tmp/itsgoin-cmd3 sleep 6 n1_id=$(strip_ansi /tmp/itsgoin-cli1.log | grep -m1 "Node ID:" | awk '{print $3}') n2_id=$(strip_ansi /tmp/itsgoin-cli2.log | grep -m1 "Node ID:" | awk '{print $3}') echo "node1=$n1_id node2=$n2_id" check "all nodes self-materialized the registry post" \ bash -c 'for i in 1 2 3; do sqlite3 /tmp/itsgoin-test$i/itsgoin.db \ "SELECT count(*) FROM posts WHERE hex(id) = upper(\"'"$(grep shipped_constant /tmp/itsgoin-genesis.log | awk '{print $2}')"'\")" \ | grep -q 1 || exit 1; done' echo "== mesh: 2,3 -> 1; 3 -> 2 ==" echo "connect $n1_id@127.0.0.1:18411" >&14 echo "connect $n1_id@127.0.0.1:18411" >&15 sleep 4 echo "connect $n2_id@127.0.0.1:18412" >&15 sleep 4 echo "== step 2: register / search / unregister ==" echo "name Alice" >&14 sleep 3 echo "register Alice rust p2p" >&14 sleep 3 echo "search rust" >&15 sleep 8 check "node3 search finds Alice" grep -q "Alice" /tmp/itsgoin-cli3.log echo "unregister" >&14 sleep 3 echo "search rust" >&15 sleep 8 check "node3 re-search finds nothing after signed delete" \ bash -c 'quiet_tail /tmp/itsgoin-cli3.log 20 | grep -q "no registry matches"' echo "== step 3: greeting roundtrip ==" # node2's bio post id (latest Profile post) from its DB. bio2=$(sq 2 'SELECT lower(hex(id)) FROM posts WHERE visibility_intent = '"'"'"Profile"'"'"' ORDER BY timestamp_ms DESC LIMIT 1') check "node2 has a bio post with a greeting slot" \ bash -c '[ -n "'"$bio2"'" ] && sqlite3 /tmp/itsgoin-test2/itsgoin.db \ "SELECT fof_gating_json FROM posts WHERE lower(hex(id))=\"'"$bio2"'\"" | grep -q Greeting' echo "name Bob" >&15 sleep 3 echo "greet $bio2 hello alice from bob" >&15 sleep 6 echo "greetings" >&14 sleep 4 check "node2 unseals the greeting text" grep -q "hello alice from bob" /tmp/itsgoin-cli2.log echo "reply 0 hello back bob" >&14 sleep 6 echo "greetings" >&15 sleep 4 check "node3 unseals the reply via its stored fresh reply key" \ grep -q "hello back bob" /tmp/itsgoin-cli3.log # Holder-side opacity: node1 never sees plaintext greeting bodies. check "node1 stores only ciphertext (no greeting plaintext in db)" \ bash -c '! sqlite3 /tmp/itsgoin-test1/itsgoin.db \ "SELECT content FROM comments" | grep -q "hello alice"' echo "== step 5: newest-wins ==" echo "register Alice rust p2p" >&14 sleep 2 echo "register AliceV2 rust mesh" >&14 sleep 4 echo "search rust" >&15 sleep 8 check "node3 sees exactly one (newest) entry for node2's persona" \ bash -c 'quiet_tail /tmp/itsgoin-cli3.log 6 | grep -c "rust" | grep -q "^1$" && quiet_tail /tmp/itsgoin-cli3.log 6 | grep -q AliceV2' echo echo "== results: $PASS passed, $FAIL failed ==" [ "$FAIL" -eq 0 ]