Add node sync e2e harness and bump 0.5.0
Some checks failed
CI / Test (OTP 27.2 / Elixir 1.18.2) (push) Failing after 1s
CI / Test (OTP 28.4 / Elixir 1.19.4 + Marmot E2E) (push) Failing after 1s

This commit is contained in:
2026-03-17 02:32:33 +01:00
parent 02f2584757
commit d63b12a4aa
9 changed files with 1316 additions and 4 deletions

227
scripts/run_node_sync_e2e.sh Executable file
View File

@@ -0,0 +1,227 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
RUN_ID="${PARRHESIA_NODE_SYNC_E2E_RUN_ID:-local-$(date +%s)}"
RESOURCE="${PARRHESIA_NODE_SYNC_E2E_RESOURCE:-tribes.accounts.user}"
RUNNER_MIX_ENV="${PARRHESIA_NODE_SYNC_E2E_RUNNER_MIX_ENV:-test}"
TMP_DIR="${PARRHESIA_NODE_SYNC_E2E_TMP_DIR:-$(mktemp -d "${TMPDIR:-/tmp}/parrhesia-node-sync-e2e.XXXXXX")}"
STATE_FILE="$TMP_DIR/state.json"
LOG_DIR="$TMP_DIR/logs"
mkdir -p "$LOG_DIR"
SUFFIX="$(basename "$TMP_DIR" | tr -c 'a-zA-Z0-9' '_')"
DB_NAME_A="${PARRHESIA_NODE_SYNC_E2E_DB_A:-parrhesia_node_sync_a_${SUFFIX}}"
DB_NAME_B="${PARRHESIA_NODE_SYNC_E2E_DB_B:-parrhesia_node_sync_b_${SUFFIX}}"
port_in_use() {
local port="$1"
if command -v ss >/dev/null 2>&1; then
ss -ltn "( sport = :${port} )" | tail -n +2 | grep -q .
return
fi
if command -v lsof >/dev/null 2>&1; then
lsof -nP -iTCP:"${port}" -sTCP:LISTEN >/dev/null 2>&1
return
fi
echo "Neither ss nor lsof is available for checking port usage." >&2
exit 1
}
pick_port() {
local port
while true; do
port="$(( (RANDOM % 10000) + 40000 ))"
if ! port_in_use "$port"; then
printf '%s\n' "$port"
return
fi
done
}
NODE_A_PORT="${PARRHESIA_NODE_A_PORT:-$(pick_port)}"
NODE_B_PORT="${PARRHESIA_NODE_B_PORT:-$(pick_port)}"
if [[ "$NODE_A_PORT" == "$NODE_B_PORT" ]]; then
echo "Node A and Node B ports must differ." >&2
exit 1
fi
database_url_for() {
local database_name="$1"
local pg_user="${PGUSER:-${USER:-agent}}"
local pg_host="${PGHOST:-localhost}"
local pg_port="${PGPORT:-5432}"
if [[ "$pg_host" == /* ]]; then
if [[ -n "${PGPASSWORD:-}" ]]; then
printf 'ecto://%s:%s@localhost/%s?socket_dir=%s&port=%s\n' \
"$pg_user" "$PGPASSWORD" "$database_name" "$pg_host" "$pg_port"
else
printf 'ecto://%s@localhost/%s?socket_dir=%s&port=%s\n' \
"$pg_user" "$database_name" "$pg_host" "$pg_port"
fi
else
if [[ -n "${PGPASSWORD:-}" ]]; then
printf 'ecto://%s:%s@%s:%s/%s\n' \
"$pg_user" "$PGPASSWORD" "$pg_host" "$pg_port" "$database_name"
else
printf 'ecto://%s@%s:%s/%s\n' \
"$pg_user" "$pg_host" "$pg_port" "$database_name"
fi
fi
}
DATABASE_URL_A="$(database_url_for "$DB_NAME_A")"
DATABASE_URL_B="$(database_url_for "$DB_NAME_B")"
printf -v PROTECTED_FILTERS_JSON '[{"kinds":[5000],"#r":["%s"]}]' "$RESOURCE"
cleanup() {
if [[ -n "${NODE_A_PID:-}" ]] && kill -0 "$NODE_A_PID" 2>/dev/null; then
kill "$NODE_A_PID" 2>/dev/null || true
wait "$NODE_A_PID" 2>/dev/null || true
fi
if [[ -n "${NODE_B_PID:-}" ]] && kill -0 "$NODE_B_PID" 2>/dev/null; then
kill "$NODE_B_PID" 2>/dev/null || true
wait "$NODE_B_PID" 2>/dev/null || true
fi
if [[ "${PARRHESIA_NODE_SYNC_E2E_DROP_DB_ON_EXIT:-1}" == "1" ]]; then
DATABASE_URL="$DATABASE_URL_A" MIX_ENV=prod mix ecto.drop --quiet --force || true
DATABASE_URL="$DATABASE_URL_B" MIX_ENV=prod mix ecto.drop --quiet --force || true
fi
if [[ "${PARRHESIA_NODE_SYNC_E2E_KEEP_TMP:-0}" != "1" ]]; then
rm -rf "$TMP_DIR"
fi
}
trap cleanup EXIT INT TERM
wait_for_health() {
local port="$1"
local label="$2"
for _ in {1..150}; do
if curl -fsS "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
return
fi
sleep 0.1
done
echo "${label} did not become healthy on port ${port}" >&2
exit 1
}
setup_database() {
local database_url="$1"
DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.drop --quiet --force || true
DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.create --quiet
DATABASE_URL="$database_url" MIX_ENV=prod mix ecto.migrate --quiet
}
start_node() {
local node_name="$1"
local port="$2"
local database_url="$3"
local relay_url="$4"
local identity_path="$5"
local sync_path="$6"
local log_path="$7"
DATABASE_URL="$database_url" \
PORT="$port" \
PARRHESIA_RELAY_URL="$relay_url" \
PARRHESIA_ACL_PROTECTED_FILTERS="$PROTECTED_FILTERS_JSON" \
PARRHESIA_IDENTITY_PATH="$identity_path" \
PARRHESIA_SYNC_PATH="$sync_path" \
MIX_ENV=prod \
mix run --no-halt >"$log_path" 2>&1 &
if [[ "$node_name" == "a" ]]; then
NODE_A_PID=$!
else
NODE_B_PID=$!
fi
}
run_runner() {
ERL_LIBS="_build/${RUNNER_MIX_ENV}/lib" \
elixir scripts/node_sync_e2e.exs "$@" --state-file "$STATE_FILE"
}
export DATABASE_URL="$DATABASE_URL_A"
MIX_ENV=prod mix compile
MIX_ENV="$RUNNER_MIX_ENV" mix compile >/dev/null
setup_database "$DATABASE_URL_A"
setup_database "$DATABASE_URL_B"
NODE_A_HTTP_URL="http://127.0.0.1:${NODE_A_PORT}"
NODE_B_HTTP_URL="http://127.0.0.1:${NODE_B_PORT}"
NODE_A_WS_URL="ws://127.0.0.1:${NODE_A_PORT}/relay"
NODE_B_WS_URL="ws://127.0.0.1:${NODE_B_PORT}/relay"
start_node \
a \
"$NODE_A_PORT" \
"$DATABASE_URL_A" \
"$NODE_A_WS_URL" \
"$TMP_DIR/node-a-identity.json" \
"$TMP_DIR/node-a-sync.json" \
"$LOG_DIR/node-a.log"
start_node \
b \
"$NODE_B_PORT" \
"$DATABASE_URL_B" \
"$NODE_B_WS_URL" \
"$TMP_DIR/node-b-identity.json" \
"$TMP_DIR/node-b-sync.json" \
"$LOG_DIR/node-b.log"
wait_for_health "$NODE_A_PORT" "Node A"
wait_for_health "$NODE_B_PORT" "Node B"
export PARRHESIA_NODE_SYNC_E2E_RUN_ID="$RUN_ID"
export PARRHESIA_NODE_SYNC_E2E_RESOURCE="$RESOURCE"
export PARRHESIA_NODE_A_HTTP_URL="$NODE_A_HTTP_URL"
export PARRHESIA_NODE_B_HTTP_URL="$NODE_B_HTTP_URL"
export PARRHESIA_NODE_A_WS_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_WS_URL="$NODE_B_WS_URL"
export PARRHESIA_NODE_A_RELAY_AUTH_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_RELAY_AUTH_URL="$NODE_B_WS_URL"
export PARRHESIA_NODE_A_SYNC_URL="$NODE_A_WS_URL"
export PARRHESIA_NODE_B_SYNC_URL="$NODE_B_WS_URL"
run_runner bootstrap
kill "$NODE_B_PID"
wait "$NODE_B_PID" 2>/dev/null || true
unset NODE_B_PID
run_runner publish-resume
start_node \
b \
"$NODE_B_PORT" \
"$DATABASE_URL_B" \
"$NODE_B_WS_URL" \
"$TMP_DIR/node-b-identity.json" \
"$TMP_DIR/node-b-sync.json" \
"$LOG_DIR/node-b.log"
wait_for_health "$NODE_B_PORT" "Node B"
run_runner verify-resume
printf 'node-sync-e2e local run completed\nlogs: %s\n' "$LOG_DIR"