Files
김경종 72dad72703
Tests / Hermetic test suite (push) Has been cancelled
Tests / Skill frontmatter validation (push) Has been cancelled
add claude-obsidian
2026-05-28 10:57:16 +09:00

170 lines
7.1 KiB
Bash

#!/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."