add claude-obsidian
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
#!/usr/bin/env bash
|
||||
# test_wiki_lock.sh — unit tests for scripts/wiki-lock.sh.
|
||||
#
|
||||
# Hermetic: creates a throwaway vault under mktemp, no network, no external
|
||||
# deps beyond bash + standard POSIX utilities. Covers:
|
||||
# - acquire returns 0 on first call, 75 on second call from a holding context
|
||||
# - release frees the lock and re-acquire works
|
||||
# - list shows held locks; reflects releases
|
||||
# - clear-stale removes locks for dead PIDs
|
||||
# - peek is read-only and reports unheld/held correctly
|
||||
# - path validation rejects absolute paths and traversal
|
||||
#
|
||||
# Usage: bash tests/test_wiki_lock.sh
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
LOCK_SH="$ROOT/scripts/wiki-lock.sh"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
|
||||
assert_eq() {
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
if [ "$expected" = "$actual" ]; then
|
||||
echo "OK $label"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL $label: expected '$expected', got '$actual'"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
assert_true() {
|
||||
local label="$1"
|
||||
shift
|
||||
if "$@"; then
|
||||
echo "OK $label"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "FAIL $label"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# Set up a sandbox vault for the duration of this run
|
||||
SANDBOX=$(mktemp -d /tmp/wiki-lock-test-XXXXXX)
|
||||
trap 'rm -rf "$SANDBOX"' EXIT
|
||||
mkdir -p "$SANDBOX/.vault-meta/locks"
|
||||
export WIKI_LOCK_VAULT="$SANDBOX"
|
||||
|
||||
# Helper: run wiki-lock.sh against the sandbox; return rc
|
||||
wl() {
|
||||
bash "$LOCK_SH" "$@"
|
||||
}
|
||||
|
||||
echo "=== test_wiki_lock.sh ==="
|
||||
echo "sandbox: $SANDBOX"
|
||||
echo ""
|
||||
|
||||
# ── acquire on a fresh path returns 0 ────────────────────────────────────────
|
||||
wl acquire wiki/concepts/Foo.md >/dev/null
|
||||
assert_eq "first acquire rc" "0" "$?"
|
||||
|
||||
# ── second acquire while the lock is fresh returns 75 ────────────────────────
|
||||
# With age-based staleness (STALE_AFTER_SEC=60 default), the lock is held until
|
||||
# either an explicit release OR 60 seconds elapse. A second acquire immediately
|
||||
# after the first should refuse.
|
||||
RC2=$( (wl acquire wiki/concepts/Foo.md >/dev/null); echo $? )
|
||||
assert_eq "second acquire while fresh rc" "75" "$RC2"
|
||||
|
||||
# ── peek shows the lock ──────────────────────────────────────────────────────
|
||||
PEEK_OUT=$(wl peek wiki/concepts/Foo.md)
|
||||
case "$PEEK_OUT" in
|
||||
*"wiki/concepts/Foo.md"*) assert_eq "peek includes path" "yes" "yes" ;;
|
||||
*) assert_eq "peek includes path" "yes" "no($PEEK_OUT)" ;;
|
||||
esac
|
||||
|
||||
# ── list shows the held lock ─────────────────────────────────────────────────
|
||||
LIST_OUT=$(wl list)
|
||||
case "$LIST_OUT" in
|
||||
*"wiki/concepts/Foo.md"*) assert_eq "list shows held lock" "yes" "yes" ;;
|
||||
*) assert_eq "list shows held lock" "yes" "no" ;;
|
||||
esac
|
||||
|
||||
# ── release frees the lock (cross-process release is allowed by design) ─────
|
||||
wl release wiki/concepts/Foo.md
|
||||
LIST_AFTER_RELEASE=$(wl list)
|
||||
assert_eq "list empty after release" "" "$LIST_AFTER_RELEASE"
|
||||
|
||||
# ── re-acquire after release succeeds ───────────────────────────────────────
|
||||
wl acquire wiki/concepts/Foo.md >/dev/null
|
||||
assert_eq "re-acquire after release rc" "0" "$?"
|
||||
wl release wiki/concepts/Foo.md
|
||||
|
||||
# ── short --stale-after-sec lets us test age-based reap quickly ─────────────
|
||||
# Acquire with a 1-second stale window, sleep 2s, second acquire should succeed
|
||||
wl --stale-after-sec 1 acquire wiki/concepts/Aged.md >/dev/null 2>&1 || \
|
||||
bash "$LOCK_SH" acquire --stale-after-sec 1 wiki/concepts/Aged.md >/dev/null 2>&1
|
||||
# (flag order tolerance) — make sure the lock exists
|
||||
PEEK_AGED=$(wl peek wiki/concepts/Aged.md)
|
||||
case "$PEEK_AGED" in
|
||||
*Aged.md*) : ;;
|
||||
*) echo "DEBUG: aged peek was: $PEEK_AGED" ;;
|
||||
esac
|
||||
sleep 2
|
||||
RC_AGED=$( (bash "$LOCK_SH" --stale-after-sec 1 acquire wiki/concepts/Aged.md >/dev/null 2>&1); echo $? )
|
||||
assert_eq "age-based stale reap allows re-acquire" "0" "$RC_AGED"
|
||||
wl release wiki/concepts/Aged.md
|
||||
|
||||
# ── clear-stale with max-age=0 reaps everything ──────────────────────────────
|
||||
# First seed a lock to reap
|
||||
wl acquire wiki/concepts/Reap.md >/dev/null
|
||||
REMOVED=$(wl clear-stale --max-age 0)
|
||||
# Should have removed 1 (the Reap.md lock)
|
||||
case "$REMOVED" in
|
||||
[1-9]*) assert_eq "clear-stale removed count >=1" "yes" "yes" ;;
|
||||
*) assert_eq "clear-stale removed count >=1" "yes" "no($REMOVED)" ;;
|
||||
esac
|
||||
LIST_AFTER_CLEAR=$(wl list)
|
||||
assert_eq "list empty after clear-stale" "" "$LIST_AFTER_CLEAR"
|
||||
|
||||
# ── peek on unheld path ──────────────────────────────────────────────────────
|
||||
PEEK_UNHELD=$(wl peek wiki/concepts/Never.md)
|
||||
assert_eq "peek unheld" "unheld" "$PEEK_UNHELD"
|
||||
|
||||
# ── path validation: absolute path rejected ──────────────────────────────────
|
||||
RC_ABS=$( (wl acquire /etc/passwd >/dev/null 2>&1); echo $? )
|
||||
assert_eq "acquire absolute path rejected" "4" "$RC_ABS"
|
||||
|
||||
# ── path validation: traversal rejected ──────────────────────────────────────
|
||||
RC_DOTDOT=$( (wl acquire ../escape.md >/dev/null 2>&1); echo $? )
|
||||
assert_eq "acquire ../ rejected" "4" "$RC_DOTDOT"
|
||||
|
||||
# ── path validation: empty rejected ──────────────────────────────────────────
|
||||
RC_EMPTY=$( (wl acquire "" >/dev/null 2>&1); echo $? )
|
||||
assert_eq "acquire empty path rejected" "4" "$RC_EMPTY"
|
||||
|
||||
# ── path validation: newline rejected (v1.7.2; closes audit M4) ──────────────
|
||||
# Newlines in lock paths would break the meta-lock line format (key=value lines
|
||||
# separated by literal \n). Must be rejected at validate_path() time.
|
||||
RC_NL=$( (wl acquire $'wiki/concepts/Foo\nbar.md' >/dev/null 2>&1); echo $? )
|
||||
assert_eq "acquire newline path rejected" "4" "$RC_NL"
|
||||
|
||||
# ── path validation: carriage return rejected (v1.7.2; closes audit M4) ──────
|
||||
RC_CR=$( (wl acquire $'wiki/concepts/Foo\rbar.md' >/dev/null 2>&1); echo $? )
|
||||
assert_eq "acquire carriage-return path rejected" "4" "$RC_CR"
|
||||
|
||||
# ── stress: 10 unique paths all acquire cleanly ──────────────────────────────
|
||||
for i in $(seq 1 10); do
|
||||
wl acquire "wiki/stress/page-$i.md" >/dev/null
|
||||
rc=$?
|
||||
if [ $rc -ne 0 ]; then
|
||||
echo "FAIL stress acquire $i: rc=$rc"
|
||||
FAIL=$((FAIL + 1))
|
||||
break
|
||||
fi
|
||||
done
|
||||
LIST_COUNT=$(wl list | wc -l)
|
||||
assert_eq "10 unique paths all acquired" "10" "$LIST_COUNT"
|
||||
wl clear-stale --max-age 0 >/dev/null
|
||||
|
||||
# ── summary ──────────────────────────────────────────────────────────────────
|
||||
echo ""
|
||||
echo "Pass: $PASS Fail: $FAIL"
|
||||
if [ $FAIL -gt 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "All wiki-lock tests passed."
|
||||
Reference in New Issue
Block a user