From e01f7f4d92647b6af20083255786eb0d5c9ef02f Mon Sep 17 00:00:00 2001 From: "Shibata, Tats" <868951+rewse@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:58:51 +0900 Subject: [PATCH] test(cli): add TODO comment and locked-remote-DB test script - Add inline TODO comment in runCommand.ts about standardising replication failure cause identification logic. - Add test-sync-locked-remote-linux.sh that verifies: 1. sync succeeds when the remote milestone is not locked. 2. sync fails with an actionable error when the remote milestone has locked=true and accepted_nodes is empty. --- src/apps/cli/commands/runCommand.ts | 3 + .../cli/test/test-sync-locked-remote-linux.sh | 136 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100755 src/apps/cli/test/test-sync-locked-remote-linux.sh diff --git a/src/apps/cli/commands/runCommand.ts b/src/apps/cli/commands/runCommand.ts index fc5d482..7672d50 100644 --- a/src/apps/cli/commands/runCommand.ts +++ b/src/apps/cli/commands/runCommand.ts @@ -22,6 +22,9 @@ export async function runCommand(options: CLIOptions, context: CLICommandContext console.log("[Command] sync"); const result = await core.services.replication.replicate(true); if (!result) { + // TODO: Standardise the logic for identifying the cause of replication + // failure so that every reason (locked DB, version mismatch, network + // error, etc.) is surfaced with a CLI-specific actionable message. const replicator = core.services.replicator.getActiveReplicator(); if (replicator?.remoteLockedAndDeviceNotAccepted) { console.error( diff --git a/src/apps/cli/test/test-sync-locked-remote-linux.sh b/src/apps/cli/test/test-sync-locked-remote-linux.sh new file mode 100755 index 0000000..1d331e7 --- /dev/null +++ b/src/apps/cli/test/test-sync-locked-remote-linux.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash +# Test: CLI sync behaviour against a locked remote database. +# +# Scenario: +# 1. Start CouchDB, create a test database, and perform an initial sync so that +# the milestone document is created on the remote. +# 2. Unlock the milestone (locked=false, accepted_nodes=[]) and verify sync +# succeeds without the locked error message. +# 3. Lock the milestone (locked=true, accepted_nodes=[]) and verify sync fails +# with an actionable error message. +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}" +TEST_ENV_FILE="${TEST_ENV_FILE:-$CLI_DIR/.test.env}" +cli_test_init_cli_cmd + +if [[ ! -f "$TEST_ENV_FILE" ]]; then + echo "[ERROR] test env file not found: $TEST_ENV_FILE" >&2 + exit 1 +fi + +set -a +source "$TEST_ENV_FILE" +set +a + +DB_SUFFIX="$(date +%s)-$RANDOM" + +COUCHDB_URI="${hostname%/}" +COUCHDB_DBNAME="${dbname}-locked-${DB_SUFFIX}" +COUCHDB_USER="${username:-}" +COUCHDB_PASSWORD="${password:-}" + +if [[ -z "$COUCHDB_URI" || -z "$COUCHDB_USER" || -z "$COUCHDB_PASSWORD" ]]; then + echo "[ERROR] COUCHDB_URI, COUCHDB_USER, COUCHDB_PASSWORD are required" >&2 + exit 1 +fi + +WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-locked-test.XXXXXX")" +VAULT_DIR="$WORK_DIR/vault" +SETTINGS_FILE="$WORK_DIR/settings.json" +mkdir -p "$VAULT_DIR" + +cleanup() { + local exit_code=$? + cli_test_stop_couchdb + rm -rf "$WORK_DIR" + exit "$exit_code" +} +trap cleanup EXIT + +if [[ "$RUN_BUILD" == "1" ]]; then + echo "[INFO] building CLI" + npm run build +fi + +echo "[INFO] starting CouchDB and creating test database: $COUCHDB_DBNAME" +cli_test_start_couchdb "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" + +echo "[INFO] preparing settings" +cli_test_init_settings_file "$SETTINGS_FILE" +cli_test_apply_couchdb_settings "$SETTINGS_FILE" "$COUCHDB_URI" "$COUCHDB_USER" "$COUCHDB_PASSWORD" "$COUCHDB_DBNAME" 1 + +echo "[INFO] initial sync to create milestone document" +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >/dev/null + +MILESTONE_ID="_local/obsydian_livesync_milestone" +MILESTONE_URL="${COUCHDB_URI}/${COUCHDB_DBNAME}/${MILESTONE_ID}" + +update_milestone() { + local locked="$1" + local accepted_nodes="$2" + local current + current="$(cli_test_curl_json --user "${COUCHDB_USER}:${COUCHDB_PASSWORD}" "$MILESTONE_URL")" + local updated + updated="$(node -e ' +const doc = JSON.parse(process.argv[1]); +doc.locked = process.argv[2] === "true"; +doc.accepted_nodes = JSON.parse(process.argv[3]); +process.stdout.write(JSON.stringify(doc)); +' "$current" "$locked" "$accepted_nodes")" + cli_test_curl_json -X PUT \ + --user "${COUCHDB_USER}:${COUCHDB_PASSWORD}" \ + -H "Content-Type: application/json" \ + -d "$updated" \ + "$MILESTONE_URL" >/dev/null +} + +SYNC_LOG="$WORK_DIR/sync.log" + +echo "[CASE] sync should succeed when remote is not locked" +update_milestone "false" "[]" + +set +e +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >"$SYNC_LOG" 2>&1 +SYNC_EXIT=$? +set -e + +if [[ "$SYNC_EXIT" -ne 0 ]]; then + echo "[FAIL] sync should succeed when remote is not locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +if grep -Fq "The remote database is locked" "$SYNC_LOG"; then + echo "[FAIL] locked error should not appear when remote is not locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +echo "[PASS] unlocked remote DB syncs successfully" + +echo "[CASE] sync should fail with actionable error when remote is locked" +update_milestone "true" "[]" + +set +e +run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" sync >"$SYNC_LOG" 2>&1 +SYNC_EXIT=$? +set -e + +if [[ "$SYNC_EXIT" -eq 0 ]]; then + echo "[FAIL] sync should have exited with non-zero when remote is locked" >&2 + cat "$SYNC_LOG" >&2 + exit 1 +fi + +cli_test_assert_contains "$(cat "$SYNC_LOG")" \ + "The remote database is locked and this device is not yet accepted" \ + "sync output should contain the locked-remote error message" + +echo "[PASS] locked remote DB produces actionable CLI error"