- No longer unexpected `Unhandled Rejections` during P2P operations (waiting acceptance).
CLI new features
- P2P sync has been implemented.
This commit is contained in:
vorotamoroz
2026-03-14 15:08:31 +09:00
parent bf93bddbdd
commit dfe13b1abd
23 changed files with 1373 additions and 24 deletions

View File

@@ -211,6 +211,57 @@ fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}
cli_test_apply_p2p_settings() {
local settings_file="$1"
local room_id="$2"
local passphrase="$3"
local app_id="${4:-self-hosted-livesync-cli-tests}"
local relays="${5:-ws://localhost:4000/}"
local auto_accept="${6:-~.*}"
SETTINGS_FILE="$settings_file" \
P2P_ROOM_ID="$room_id" \
P2P_PASSPHRASE="$passphrase" \
P2P_APP_ID="$app_id" \
P2P_RELAYS="$relays" \
P2P_AUTO_ACCEPT="$auto_accept" \
node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
data.P2P_Enabled = true;
data.P2P_AutoStart = false;
data.P2P_AutoBroadcast = false;
data.P2P_AppID = process.env.P2P_APP_ID;
data.P2P_roomID = process.env.P2P_ROOM_ID;
data.P2P_passphrase = process.env.P2P_PASSPHRASE;
data.P2P_relays = process.env.P2P_RELAYS;
data.P2P_AutoAcceptingPeers = process.env.P2P_AUTO_ACCEPT;
data.P2P_AutoDenyingPeers = "";
data.P2P_IsHeadless = true;
data.isConfigured = true;
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
}
cli_test_is_local_p2p_relay() {
local relay_url="$1"
[[ "$relay_url" == "ws://localhost:4000" || "$relay_url" == "ws://localhost:4000/" ]]
}
cli_test_stop_p2p_relay() {
bash "$CLI_DIR/util/p2p-stop.sh" >/dev/null 2>&1 || true
}
cli_test_start_p2p_relay() {
echo "[INFO] stopping leftover P2P relay container if present"
cli_test_stop_p2p_relay
echo "[INFO] starting local P2P relay container"
bash "$CLI_DIR/util/p2p-start.sh"
}
cli_test_stop_couchdb() {
bash "$CLI_DIR/util/couchdb-stop.sh" >/dev/null 2>&1 || true
}

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
set -euo pipefail
# This test should be run with P2P client, please refer to the test-p2p-three-nodes-conflict-linux.sh test for more details.
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}"
VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}"
KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}"
RELAY="${RELAY:-ws://localhost:4000/}"
USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}"
ROOM_ID="${ROOM_ID:-1}"
PASSPHRASE="${PASSPHRASE:-test}"
APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}"
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-host.XXXXXX")"
VAULT="$WORK_DIR/vault-host"
SETTINGS="$WORK_DIR/settings-host.json"
mkdir -p "$VAULT"
cleanup() {
local exit_code=$?
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
echo "[INFO] preparing settings"
echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID"
cli_test_init_settings_file "$SETTINGS"
cli_test_apply_p2p_settings "$SETTINGS" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
echo "[CASE] start p2p-host"
echo "[INFO] press Ctrl+C to stop"
run_cli "$VAULT" --settings "$SETTINGS" p2p-host

View File

@@ -0,0 +1,86 @@
#!/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"
RUN_BUILD="${RUN_BUILD:-0}"
KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}"
RELAY="${RELAY:-ws://localhost:7777}"
ROOM_ID="${ROOM_ID:-1}"
PASSPHRASE="${PASSPHRASE:-test}"
TIMEOUT_SECONDS="${TIMEOUT_SECONDS:-8}"
DEBUG_FLAG="${DEBUG_FLAG:--d}"
if [[ "$RUN_BUILD" == "1" ]]; then
echo "[INFO] building CLI"
npm run build
fi
WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-p2p-peers-local-relay.XXXXXX")"
VAULT="$WORK_DIR/vault"
SETTINGS="$WORK_DIR/settings.json"
mkdir -p "$VAULT"
cleanup() {
local exit_code=$?
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
cli_test_init_cli_cmd
echo "[INFO] creating settings at $SETTINGS"
run_cli init-settings --force "$SETTINGS" >/dev/null
SETTINGS_FILE="$SETTINGS" \
P2P_ROOM_ID="$ROOM_ID" \
P2P_PASSPHRASE="$PASSPHRASE" \
P2P_RELAYS="$RELAY" \
node <<'NODE'
const fs = require("node:fs");
const settingsPath = process.env.SETTINGS_FILE;
const data = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
data.P2P_Enabled = true;
data.P2P_AutoStart = false;
data.P2P_AutoBroadcast = false;
data.P2P_roomID = process.env.P2P_ROOM_ID;
data.P2P_passphrase = process.env.P2P_PASSPHRASE;
data.P2P_relays = process.env.P2P_RELAYS;
data.P2P_AutoAcceptingPeers = "~.*";
data.P2P_AutoDenyingPeers = "";
data.P2P_IsHeadless = true;
data.isConfigured = true;
fs.writeFileSync(settingsPath, JSON.stringify(data, null, 2), "utf-8");
NODE
echo "[INFO] relay=$RELAY room=$ROOM_ID timeout=${TIMEOUT_SECONDS}s"
echo "[INFO] running p2p-peers"
set +e
OUTPUT="$(run_cli "$DEBUG_FLAG" "$VAULT" --settings "$SETTINGS" p2p-peers "$TIMEOUT_SECONDS" 2>&1)"
EXIT_CODE=$?
set -e
echo "$OUTPUT"
if [[ "$EXIT_CODE" -ne 0 ]]; then
echo "[FAIL] p2p-peers exited with code $EXIT_CODE" >&2
exit "$EXIT_CODE"
fi
if [[ -z "$OUTPUT" ]]; then
echo "[WARN] command completed but output was empty"
fi
echo "[PASS] p2p-peers finished"

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# This test should be run with P2P client, please refer to the test-p2p-three-nodes-conflict-linux.sh test for more details.
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}"
VERBOSE_TEST_LOGGING="${VERBOSE_TEST_LOGGING:-0}"
KEEP_TEST_DATA="${KEEP_TEST_DATA:-0}"
RELAY="${RELAY:-ws://localhost:4000/}"
USE_INTERNAL_RELAY="${USE_INTERNAL_RELAY:-1}"
ROOM_ID="${ROOM_ID:-1}"
PASSPHRASE="${PASSPHRASE:-test}"
APP_ID="${APP_ID:-self-hosted-livesync-cli-tests}"
PEERS_TIMEOUT="${PEERS_TIMEOUT:-12}"
SYNC_TIMEOUT="${SYNC_TIMEOUT:-15}"
TARGET_PEER="${TARGET_PEER:-}"
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-sync.XXXXXX")"
VAULT="$WORK_DIR/vault-sync"
SETTINGS="$WORK_DIR/settings-sync.json"
mkdir -p "$VAULT"
cleanup() {
local exit_code=$?
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
echo "[INFO] preparing settings"
echo "[INFO] relay=$RELAY room=$ROOM_ID app=$APP_ID"
cli_test_init_settings_file "$SETTINGS"
cli_test_apply_p2p_settings "$SETTINGS" "$ROOM_ID" "$PASSPHRASE" "$APP_ID" "$RELAY"
echo "[CASE] discover peers"
PEER_LINES="$(run_cli "$VAULT" --settings "$SETTINGS" p2p-peers "$PEERS_TIMEOUT")"
if [[ -z "$PEER_LINES" ]]; then
echo "[FAIL] p2p-peers returned empty output" >&2
exit 1
fi
if ! awk -F $'\t' 'NF>=3 && $1=="[peer]" { found=1 } END { exit(found ? 0 : 1) }' <<< "$PEER_LINES"; then
echo "[FAIL] p2p-peers output must include [peer]<TAB><peer-id><TAB><peer-name>" >&2
echo "$PEER_LINES" >&2
exit 1
fi
SELECTED_PEER_ID=""
SELECTED_PEER_NAME=""
if [[ -n "$TARGET_PEER" ]]; then
while IFS=$'\t' read -r marker peer_id peer_name _; do
if [[ "$marker" != "[peer]" ]]; then
continue
fi
if [[ "$peer_id" == "$TARGET_PEER" || "$peer_name" == "$TARGET_PEER" ]]; then
SELECTED_PEER_ID="$peer_id"
SELECTED_PEER_NAME="$peer_name"
break
fi
done <<< "$PEER_LINES"
if [[ -z "$SELECTED_PEER_ID" ]]; then
echo "[FAIL] TARGET_PEER=$TARGET_PEER was not found" >&2
echo "$PEER_LINES" >&2
exit 1
fi
else
SELECTED_PEER_ID="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $2; exit}' <<< "$PEER_LINES")"
SELECTED_PEER_NAME="$(awk -F $'\t' 'NF>=3 && $1=="[peer]" {print $3; exit}' <<< "$PEER_LINES")"
fi
if [[ -z "$SELECTED_PEER_ID" ]]; then
echo "[FAIL] could not extract peer-id from p2p-peers output" >&2
echo "$PEER_LINES" >&2
exit 1
fi
echo "[PASS] selected peer: ${SELECTED_PEER_ID} (${SELECTED_PEER_NAME:-unknown})"
echo "[CASE] run p2p-sync"
run_cli "$VAULT" --settings "$SETTINGS" p2p-sync "$SELECTED_PEER_ID" "$SYNC_TIMEOUT" >/dev/null
echo "[PASS] p2p-sync completed"

View File

@@ -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"