mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-05-28 01:52:57 +00:00
Fixed:
- No longer unexpected `Unhandled Rejections` during P2P operations (waiting acceptance). CLI new features - P2P sync has been implemented.
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
CLI_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$CLI_DIR"
|
||||
source "$SCRIPT_DIR/test-helpers.sh"
|
||||
display_test_info
|
||||
|
||||
RUN_BUILD="${RUN_BUILD:-1}"
|
||||
KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}"
|
||||
VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}"
|
||||
|
||||
RELAY="${RELAY:-ws://localhost:4000/}"
|
||||
USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}"
|
||||
ROOM_ID_PREFIX="${ROOM_ID_PREFIX:-p2p-room}"
|
||||
PASSPHRASE_PREFIX="${PASSPHRASE_PREFIX:-p2p-pass}"
|
||||
APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}"
|
||||
PEERS_TIMEOUT="${PEERS_TIMEOUT:-10}"
|
||||
SYNC_TIMEOUT="${SYNC_TIMEOUT:-15}"
|
||||
|
||||
ROOM_ID="${ROOM_ID_PREFIX}-$(date +%s)-$RANDOM-$RANDOM"
|
||||
PASSPHRASE="${PASSPHRASE_PREFIX}-$(date +%s)-$RANDOM-$RANDOM"
|
||||
|
||||
cli_test_init_cli_cmd
|
||||
|
||||
if [[ "$RUN_BUILD" == "1" ]]; then
|
||||
echo "[INFO] building CLI"
|
||||
npm run build
|
||||
fi
|
||||
|
||||
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-3nodes.XXXXXX")"
|
||||
VAULT_A="$WORK_DIR/vault-a"
|
||||
VAULT_B="$WORK_DIR/vault-b"
|
||||
VAULT_C="$WORK_DIR/vault-c"
|
||||
SETTINGS_A="$WORK_DIR/settings-a.json"
|
||||
SETTINGS_B="$WORK_DIR/settings-b.json"
|
||||
SETTINGS_C="$WORK_DIR/settings-c.json"
|
||||
HOST_LOG="$WORK_DIR/p2p-host.log"
|
||||
|
||||
mkdir -p "$VAULT_A" "$VAULT_B" "$VAULT_C"
|
||||
|
||||
cleanup() {
|
||||
local exit_code=$?
|
||||
if [[ -n "${HOST_PID:-}" ]] && kill -0 "$HOST_PID" >/dev/null 2>&1; then
|
||||
kill -TERM "$HOST_PID" >/dev/null 2>&1 || true
|
||||
wait "$HOST_PID" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [[ "${P2P_RELAY_STARTED:-0}" == "1" ]]; then
|
||||
cli_test_stop_p2p_relay
|
||||
fi
|
||||
|
||||
if [[ "$KEEP_TEST_DATA" != "1" ]]; then
|
||||
rm -rf "$WORK_DIR"
|
||||
else
|
||||
echo "[INFO] KEEP_TEST_DATA=1, preserving artefacts at $WORK_DIR"
|
||||
fi
|
||||
|
||||
exit "$exit_code"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ "$USE_INTERNAL_RELAY" == "1" ]]; then
|
||||
if cli_test_is_local_p2p_relay "$RELAY"; then
|
||||
cli_test_start_p2p_relay
|
||||
P2P_RELAY_STARTED=1
|
||||
else
|
||||
echo "[INFO] USE_INTERNAL_RELAY=1 but RELAY is not local ($RELAY), skipping local relay startup"
|
||||
fi
|
||||
fi
|
||||
|
||||
run_cli_a() {
|
||||
run_cli "$VAULT_A" --settings "$SETTINGS_A" "$@"
|
||||
}
|
||||
|
||||
run_cli_b() {
|
||||
run_cli "$VAULT_B" --settings "$SETTINGS_B" "$@"
|
||||
}
|
||||
|
||||
run_cli_c() {
|
||||
run_cli "$VAULT_C" --settings "$SETTINGS_C" "$@"
|
||||
}
|
||||
|
||||
echo "[INFO] preparing settings"
|
||||
echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID"
|
||||
cli_test_init_settings_file "$SETTINGS_A"
|
||||
cli_test_init_settings_file "$SETTINGS_B"
|
||||
cli_test_init_settings_file "$SETTINGS_C"
|
||||
cli_test_apply_p2p_settings "$SETTINGS_A" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
|
||||
cli_test_apply_p2p_settings "$SETTINGS_B" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
|
||||
cli_test_apply_p2p_settings "$SETTINGS_C" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
|
||||
|
||||
echo "[CASE] start p2p-host on A"
|
||||
run_cli_a p2p-host >"$HOST_LOG" 2>&1 &
|
||||
HOST_PID=$!
|
||||
|
||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||
echo "[INFO] waiting for p2p-host to start..."
|
||||
if grep -Fq "P2P host is running" "$HOST_LOG"; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if ! grep -Fq "P2P host is running" "$HOST_LOG"; then
|
||||
echo "[FAIL] p2p-host did not become ready" >&2
|
||||
cat "$HOST_LOG" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[PASS] p2p-host started"
|
||||
|
||||
echo "[CASE] discover host peer from B"
|
||||
PEERS_FROM_B="$(run_cli_b p2p-peers "$PEERS_TIMEOUT")"
|
||||
HOST_PEER_ID="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_B")"
|
||||
if [[ -z "$HOST_PEER_ID" ]]; then
|
||||
echo "[FAIL] B could not find host peer" >&2
|
||||
echo "$PEERS_FROM_B" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[PASS] B discovered host peer: $HOST_PEER_ID"
|
||||
|
||||
echo "[CASE] discover host peer from C"
|
||||
PEERS_FROM_C="$(run_cli_c p2p-peers "$PEERS_TIMEOUT")"
|
||||
HOST_PEER_ID_FROM_C="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEERS_FROM_C")"
|
||||
if [[ -z "$HOST_PEER_ID_FROM_C" ]]; then
|
||||
echo "[FAIL] C could not find host peer" >&2
|
||||
echo "$PEERS_FROM_C" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[PASS] C discovered host peer: $HOST_PEER_ID_FROM_C"
|
||||
|
||||
TARGET_PATH="p2p/conflicted-from-two-clients.txt"
|
||||
|
||||
echo "[CASE] B creates file and syncs"
|
||||
printf 'from-client-b-v1\n' | run_cli_b put "$TARGET_PATH" >/dev/null
|
||||
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null
|
||||
|
||||
echo "[CASE] C syncs and can see B file"
|
||||
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
|
||||
VISIBLE_ON_C=""
|
||||
for _ in 1 2 3 4 5; do
|
||||
if VISIBLE_ON_C="$(run_cli_c cat "$TARGET_PATH" 2>/dev/null | cli_test_sanitise_cat_stdout)"; then
|
||||
if [[ "$VISIBLE_ON_C" == "from-client-b-v1" ]]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
|
||||
sleep 1
|
||||
done
|
||||
cli_test_assert_equal "from-client-b-v1" "$VISIBLE_ON_C" "C should see file created by B"
|
||||
|
||||
echo "[CASE] B and C modify file independently"
|
||||
printf 'from-client-b-v2\n' | run_cli_b put "$TARGET_PATH" >/dev/null
|
||||
printf 'from-client-c-v2\n' | run_cli_c put "$TARGET_PATH" >/dev/null
|
||||
|
||||
echo "[CASE] B and C sync to host concurrently"
|
||||
set +e
|
||||
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null &
|
||||
SYNC_B_PID=$!
|
||||
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null &
|
||||
SYNC_C_PID=$!
|
||||
wait "$SYNC_B_PID"
|
||||
SYNC_B_EXIT=$?
|
||||
wait "$SYNC_C_PID"
|
||||
SYNC_C_EXIT=$?
|
||||
set -e
|
||||
if [[ "$SYNC_B_EXIT" -ne 0 || "$SYNC_C_EXIT" -ne 0 ]]; then
|
||||
echo "[FAIL] concurrent sync failed: B=$SYNC_B_EXIT C=$SYNC_C_EXIT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[CASE] sync back to clients"
|
||||
run_cli_b p2p-sync "$HOST_PEER_ID" "$SYNC_TIMEOUT" >/dev/null
|
||||
run_cli_c p2p-sync "$HOST_PEER_ID_FROM_C" "$SYNC_TIMEOUT" >/dev/null
|
||||
|
||||
echo "[CASE] B info shows conflict"
|
||||
INFO_JSON_B_BEFORE="$(run_cli_b info "$TARGET_PATH")"
|
||||
CONFLICTS_B_BEFORE="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin conflicts)"
|
||||
KEEP_REV_B="$(printf '%s' "$INFO_JSON_B_BEFORE" | cli_test_json_string_field_from_stdin revision)"
|
||||
if [[ "$CONFLICTS_B_BEFORE" == "N/A" || -z "$CONFLICTS_B_BEFORE" ]]; then
|
||||
echo "[FAIL] expected conflicts on B after two-client sync" >&2
|
||||
echo "$INFO_JSON_B_BEFORE" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$KEEP_REV_B" ]]; then
|
||||
echo "[FAIL] could not read current revision on B for resolve" >&2
|
||||
echo "$INFO_JSON_B_BEFORE" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[PASS] conflict detected on B"
|
||||
|
||||
echo "[CASE] C info shows conflict"
|
||||
INFO_JSON_C_BEFORE="$(run_cli_c info "$TARGET_PATH")"
|
||||
CONFLICTS_C_BEFORE="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin conflicts)"
|
||||
KEEP_REV_C="$(printf '%s' "$INFO_JSON_C_BEFORE" | cli_test_json_string_field_from_stdin revision)"
|
||||
if [[ "$CONFLICTS_C_BEFORE" == "N/A" || -z "$CONFLICTS_C_BEFORE" ]]; then
|
||||
echo "[FAIL] expected conflicts on C after two-client sync" >&2
|
||||
echo "$INFO_JSON_C_BEFORE" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$KEEP_REV_C" ]]; then
|
||||
echo "[FAIL] could not read current revision on C for resolve" >&2
|
||||
echo "$INFO_JSON_C_BEFORE" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[PASS] conflict detected on C"
|
||||
|
||||
echo "[CASE] resolve conflict on B and C"
|
||||
run_cli_b resolve "$TARGET_PATH" "$KEEP_REV_B" >/dev/null
|
||||
run_cli_c resolve "$TARGET_PATH" "$KEEP_REV_C" >/dev/null
|
||||
|
||||
INFO_JSON_B_AFTER="$(run_cli_b info "$TARGET_PATH")"
|
||||
CONFLICTS_B_AFTER="$(printf '%s' "$INFO_JSON_B_AFTER" | cli_test_json_string_field_from_stdin conflicts)"
|
||||
if [[ "$CONFLICTS_B_AFTER" != "N/A" ]]; then
|
||||
echo "[FAIL] conflict still remains on B after resolve" >&2
|
||||
echo "$INFO_JSON_B_AFTER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
INFO_JSON_C_AFTER="$(run_cli_c info "$TARGET_PATH")"
|
||||
CONFLICTS_C_AFTER="$(printf '%s' "$INFO_JSON_C_AFTER" | cli_test_json_string_field_from_stdin conflicts)"
|
||||
if [[ "$CONFLICTS_C_AFTER" != "N/A" ]]; then
|
||||
echo "[FAIL] conflict still remains on C after resolve" >&2
|
||||
echo "$INFO_JSON_C_AFTER" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
FINAL_CONTENT_B="$(run_cli_b cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)"
|
||||
FINAL_CONTENT_C="$(run_cli_c cat "$TARGET_PATH" | cli_test_sanitise_cat_stdout)"
|
||||
if [[ "$FINAL_CONTENT_B" != "from-client-b-v2" && "$FINAL_CONTENT_B" != "from-client-c-v2" ]]; then
|
||||
echo "[FAIL] unexpected final content on B after resolve" >&2
|
||||
echo "[FAIL] final content on B: $FINAL_CONTENT_B" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$FINAL_CONTENT_C" != "from-client-b-v2" && "$FINAL_CONTENT_C" != "from-client-c-v2" ]]; then
|
||||
echo "[FAIL] unexpected final content on C after resolve" >&2
|
||||
echo "[FAIL] final content on C: $FINAL_CONTENT_C" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[PASS] conflicts resolved on B and C"
|
||||
echo "[PASS] all 3-node P2P conflict scenarios passed"
|
||||
Reference in New Issue
Block a user