#!/usr/bin/env bash # Test: mirror command — storage <-> local database synchronisation # # Covered cases: # 1. Storage-only file → synced into DB (UPDATE DATABASE) # 2. DB-only file → restored to storage (UPDATE STORAGE) # 3. DB-deleted file → NOT restored to storage (UPDATE STORAGE skip) # 4. Both, storage newer → DB updated (SYNC: STORAGE → DB) # 5. Both, DB newer → storage updated (SYNC: DB → STORAGE) # # Not covered (require precise mtime control or artificial conflict injection): # - Both, equal mtime → no-op (EVEN) # - Conflicted entry → skipped # 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}" cli_test_init_cli_cmd WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/livesync-cli-test.XXXXXX")" trap 'rm -rf "$WORK_DIR"' EXIT SETTINGS_FILE="$WORK_DIR/data.json" VAULT_DIR="$WORK_DIR/vault" DB_DIR="$WORK_DIR/db" mkdir -p "$VAULT_DIR/test" mkdir -p "$DB_DIR" if [[ "$RUN_BUILD" == "1" ]]; then echo "[INFO] building CLI..." npm run build fi echo "[INFO] generating settings -> $SETTINGS_FILE" cli_test_init_settings_file "$SETTINGS_FILE" # isConfigured=true is required for mirror (canProceedScan checks this) cli_test_mark_settings_configured "$SETTINGS_FILE" # Preparation: Sync settings and files logic DB_SETTINGS="$DB_DIR/settings.json" cp "$SETTINGS_FILE" "$DB_SETTINGS" # Helper for standard run (Separated paths) run_mirror_test() { run_cli "$DB_DIR" --settings "$DB_SETTINGS" mirror "$VAULT_DIR" } # Helper for compatibility run (Same path) run_mirror_compat() { run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror } PASS=0 FAIL=0 assert_pass() { echo "[PASS] $1"; PASS=$((PASS + 1)); } assert_fail() { echo "[FAIL] $1" >&2; FAIL=$((FAIL + 1)); } # Return timestamp for touch -t in YYYYMMDDHHMM format. # Accepts offsets such as "+1 hour" or "-1 hour". portable_touch_timestamp() { local offset="$1" if command -v gdate >/dev/null 2>&1; then gdate -d "$offset" +%Y%m%d%H%M return fi if date -d "$offset" +%Y%m%d%H%M >/dev/null 2>&1; then date -d "$offset" +%Y%m%d%H%M return fi case "$offset" in "+1 hour") date -v+1H +%Y%m%d%H%M ;; "-1 hour") date -v-1H +%Y%m%d%H%M ;; *) echo "[FAIL] Unsupported date offset on this platform: $offset" >&2 exit 1 ;; esac } # ───────────────────────────────────────────────────────────────────────────── # Case 1: File exists only in storage → should be synced into DB after mirror # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 1: storage-only → DB (Separated Paths) ===" printf 'storage-only content\n' > "$VAULT_DIR/test/storage-only.md" echo "[DEBUG] DB_DIR: $DB_DIR" echo "[DEBUG] VAULT_DIR: $VAULT_DIR" run_mirror_test RESULT_FILE="$WORK_DIR/case1-cat.txt" # Try 'ls' first to see what's in the DB echo "--- DB contents ---" run_cli "$DB_DIR" --settings "$DB_SETTINGS" ls echo "-------------------" run_cli "$DB_DIR" --settings "$DB_SETTINGS" pull test/storage-only.md "$RESULT_FILE" if cmp -s "$VAULT_DIR/test/storage-only.md" "$RESULT_FILE"; then assert_pass "storage-only file was synced into DB using separated paths" else assert_fail "storage-only file NOT synced into DB with separated paths" echo "--- storage ---" >&2; cat "$VAULT_DIR/test/storage-only.md" >&2 echo "--- cat ---" >&2; cat "$RESULT_FILE" >&2 fi # ───────────────────────────────────────────────────────────────────────────── # Case 2: File exists only in DB → should be restored to storage after mirror # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 2: DB-only → storage (Separated Paths) ===" printf 'db-only content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/db-only.md if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then assert_fail "db-only.md unexpectedly exists in storage before mirror" else echo "[INFO] confirmed: test/db-only.md not in storage before mirror" fi run_mirror_test if [[ -f "$VAULT_DIR/test/db-only.md" ]]; then STORAGE_CONTENT="$(cat "$VAULT_DIR/test/db-only.md")" if [[ "$STORAGE_CONTENT" == "db-only content" ]]; then assert_pass "DB-only file was restored to storage" else assert_fail "DB-only file restored but content mismatch (got: '${STORAGE_CONTENT}')" fi else assert_fail "DB-only file NOT restored to storage after mirror" fi # ───────────────────────────────────────────────────────────────────────────── # Case 3: File deleted in DB → should NOT be created in storage # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 3: DB-deleted → storage untouched (Separated Paths) ===" printf 'to-be-deleted\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/deleted.md run_cli "$DB_DIR" --settings "$DB_SETTINGS" rm test/deleted.md run_mirror_test if [[ ! -f "$VAULT_DIR/test/deleted.md" ]]; then assert_pass "deleted DB entry was not restored to storage" else assert_fail "deleted DB entry was incorrectly restored to storage" fi # ───────────────────────────────────────────────────────────────────────────── # Case 4: Both exist, storage is newer → DB should be updated # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 4: storage newer → DB updated (Separated Paths) ===" # Seed DB with old content (mtime ≈ now) printf 'old content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/sync-storage-newer.md # Write new content to storage with a timestamp 1 hour in the future printf 'new content\n' > "$VAULT_DIR/test/sync-storage-newer.md" touch -t "$(portable_touch_timestamp '+1 hour')" "$VAULT_DIR/test/sync-storage-newer.md" run_mirror_test DB_RESULT_FILE="$WORK_DIR/case4-pull.txt" run_cli "$DB_DIR" --settings "$DB_SETTINGS" pull test/sync-storage-newer.md "$DB_RESULT_FILE" if cmp -s "$VAULT_DIR/test/sync-storage-newer.md" "$DB_RESULT_FILE"; then assert_pass "DB updated to match newer storage file" else assert_fail "DB NOT updated to match newer storage file" echo "--- expected(storage) ---" >&2; cat "$VAULT_DIR/test/sync-storage-newer.md" >&2 echo "--- pulled(from db) ---" >&2; cat "$DB_RESULT_FILE" >&2 fi # ───────────────────────────────────────────────────────────────────────────── # Case 5: Both exist, DB is newer → storage should be updated # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 5: DB newer → storage updated (Separated Paths) ===" # Write old content to storage with a timestamp 1 hour in the past printf 'old storage content\n' > "$VAULT_DIR/test/sync-db-newer.md" touch -t "$(portable_touch_timestamp '-1 hour')" "$VAULT_DIR/test/sync-db-newer.md" # Write new content to DB only (mtime ≈ now, newer than the storage file) printf 'new db content\n' | run_cli "$DB_DIR" --settings "$DB_SETTINGS" put test/sync-db-newer.md run_mirror_test STORAGE_CONTENT="$(cat "$VAULT_DIR/test/sync-db-newer.md")" if [[ "$STORAGE_CONTENT" == "new db content" ]]; then assert_pass "storage updated to match newer DB entry" else assert_fail "storage NOT updated to match newer DB entry (got: '${STORAGE_CONTENT}')" fi # ───────────────────────────────────────────────────────────────────────────── # Case 6: Compatibility test - omitted vault-path # ───────────────────────────────────────────────────────────────────────────── echo "" echo "=== Case 6: omitted vault-path (Compatibility Mode) ===" # We use VAULT_DIR as the "main" database path for this part. printf 'compat-content\n' > "$VAULT_DIR/compat.md" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" mirror # In compat mode, it should find it in the DB at root CAT_RESULT="$WORK_DIR/compat-cat.txt" run_cli "$VAULT_DIR" --settings "$SETTINGS_FILE" pull compat.md "$CAT_RESULT" if [[ "$(cat "$CAT_RESULT")" == "compat-content" ]]; then assert_pass "Compatibility mode works (omitted vault-path)" else assert_fail "Compatibility mode failed to sync file into DB" fi # ───────────────────────────────────────────────────────────────────────────── # Summary # ───────────────────────────────────────────────────────────────────────────── echo "" echo "Results: PASS=$PASS FAIL=$FAIL" if [[ "$FAIL" -gt 0 ]]; then exit 1 fi