# Shared by tests/cli/test_*.sh - source it first. # # Sourcing gives the test its own sandbox: a HOME, CM_ROOT and CLAUDE_CONFIG_DIR # of its own, the repository's scripts installed into it the way linux/install.sh # lays them out, and the vault forced to the plain-file backend. Nothing on the # machine running the tests is read or written - not the real keyring, not # ~/.claude-mode, not ~/.claude/settings.json - and a switch performed by a test # only rewrites the sandbox's settings.json. # # Assertions count rather than stop, so one run reports every failure; the # file ends with `finish`, whose status is the test's. REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" T_NAME="$(basename "$0" .sh)" T_PASS=0 T_FAIL=0 OUT='' RC=0 FAKE_PID='' SB="$(mktemp -d "${TMPDIR:-/tmp}/cm-test.XXXXXX")" mkdir -p "$SB/root/bin" "$SB/root/presets" "$SB/home/.claude/projects" cp "$REPO/linux/claude-mode" "$REPO/linux/cm-json.py" "$REPO/linux/cm-vault.sh" \ "$REPO/linux/claude-key-helper.sh" "$SB/root/bin/" if [ -d "$REPO/linux/lib" ]; then cp -R "$REPO/linux/lib" "$SB/root/bin/"; fi chmod +x "$SB/root/bin/claude-mode" "$SB/root/bin/claude-key-helper.sh" cp "$REPO"/presets/*.json "$SB/root/presets/" cp "$REPO/providers.json" "$REPO/VERSION" "$SB/root/" export HOME="$SB/home" export CM_ROOT="$SB/root" export CLAUDE_CONFIG_DIR="$SB/home/.claude" export CLAUDE_MODE_VAULT=file unset CM_IGNORE_AGE_DAYS CLAUDE_MODE_THEME CM="$SB/root/bin/claude-mode" J="$SB/root/bin/cm-json.py" P="$SB/root/presets" set_state() { printf '{"mode":"%s","preset":"%s","writtenEnvKeys":[]}\n' "$1" "${2:-}" > "$CM_ROOT/state.json" } set_state anthropic t_cleanup() { if [ -n "$FAKE_PID" ]; then kill "$FAKE_PID" 2>/dev/null; fi rm -rf "$SB" } trap t_cleanup EXIT # Starts tests/fake_server.py and sets OLLAMA_URL, LMSTUDIO_URL, KEYED_URL and # PROXY_URL from the ports it chose. start_fake() { python3 "$REPO/tests/fake_server.py" > "$SB/ports" 2>/dev/null & FAKE_PID=$! local i for i in $(seq 1 50); do [ -s "$SB/ports" ] && break sleep 0.1 done [ -s "$SB/ports" ] || { echo "$T_NAME: the fake server did not start" >&2; exit 1; } eval "$(python3 -c ' import json, sys for kind, port in json.load(open(sys.argv[1])).items(): print("%s_URL=http://127.0.0.1:%d" % (kind.upper(), port)) ' "$SB/ports")" } ESC="$(printf '\033')" # run_cm : the CLI's combined output, colour codes removed, in OUT; # its exit status in RC. run_cm() { OUT="$("$CM" "$@" 2>&1)" RC=$? OUT="$(printf '%s' "$OUT" | sed "s/${ESC}\[[0-9;]*m//g")" } pass() { T_PASS=$((T_PASS + 1)); } fail() { T_FAIL=$((T_FAIL + 1)) printf ' FAIL %s: %s\n' "$T_NAME" "$*" if [ -n "$OUT" ]; then printf '%s\n' "$OUT" | tail -n 8 | sed 's/^/ | /'; fi } expect_rc() { if [ "$RC" -eq "$1" ]; then pass; else fail "$2 (exit $RC, wanted $1)"; fi; } expect_out() { case "$OUT" in *"$1"*) pass ;; *) fail "$2 (output lacks: $1)" ;; esac; } expect_no_out() { case "$OUT" in *"$1"*) fail "$2 (output has: $1)" ;; *) pass ;; esac; } expect_eq() { if [ "$1" = "$2" ]; then pass; else fail "$3 (got '$1', wanted '$2')"; fi; } expect_file() { if [ -e "$1" ]; then pass; else fail "$2 (no $1)"; fi; } expect_no_file(){ if [ -e "$1" ]; then fail "$2 ($1 exists)"; else pass; fi; } jget() { python3 "$J" get "$1" "$2"; } # The preflight verdict for a mode (and preset) as " ". preflight_code() { "$CM" preflight "$@" 2>/dev/null | python3 -c ' import json, sys d = json.load(sys.stdin) print(d["code"], d["remedyKind"])' } # Point a shipped preset at a fake server and mark it set up, so preflight # gets past "not set up yet" to the checks under test. ready_preset() { python3 "$J" set-flag "$P/$1.json" configured true [ -n "${2:-}" ] && python3 "$J" set-url "$P/$1.json" "$2" >/dev/null return 0 } finish() { printf '%-22s %3d passed' "$T_NAME" "$T_PASS" if [ "$T_FAIL" -gt 0 ]; then printf ', %d FAILED' "$T_FAIL"; fi printf '\n' [ "$T_FAIL" -eq 0 ] }