test: Profile-Logik und Installer-Test-Suite (#3)
This commit is contained in:
parent
55d9e52b43
commit
17c1c1392e
2 changed files with 225 additions and 0 deletions
3
Makefile
Normal file
3
Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.PHONY: test
|
||||
test:
|
||||
@bash tests/test_profiles.sh
|
||||
222
tests/test_profiles.sh
Executable file
222
tests/test_profiles.sh
Executable file
|
|
@ -0,0 +1,222 @@
|
|||
#!/usr/bin/env bash
|
||||
#
|
||||
# Tests for claude-launcher-profiles (profile logic + installer).
|
||||
#
|
||||
# Design:
|
||||
# - A stub `claude` captures the ANTHROPIC_* env vars the launcher exports,
|
||||
# so we can assert on them without a real Claude Code or network.
|
||||
# - launch_profile calls `exec claude`, so every invocation runs inside a
|
||||
# subshell — the exec then only ends the subshell, not the test runner.
|
||||
# - Installer tests run with HOME pointed at a temp dir and a throwaway repo
|
||||
# clone, so they never touch the real repo working tree.
|
||||
#
|
||||
# Run: bash tests/test_profiles.sh
|
||||
# or: make test
|
||||
|
||||
set -u
|
||||
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
TESTS_RUN=0
|
||||
TESTS_FAIL=0
|
||||
FAILED_CASES=()
|
||||
|
||||
# ---- assert helpers --------------------------------------------------------
|
||||
|
||||
assert_eq() {
|
||||
# assert_eq <label> <expected> <actual>
|
||||
local label="$1" expected="$2" actual="$3"
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
printf ' ok %s\n' "$label"
|
||||
else
|
||||
printf ' FAIL %s\n expected: <%s>\n actual: <%s>\n' \
|
||||
"$label" "$expected" "$actual" >&2
|
||||
TESTS_FAIL=$((TESTS_FAIL + 1))
|
||||
FAILED_CASES+=("$label")
|
||||
fi
|
||||
}
|
||||
|
||||
assert_true() {
|
||||
# assert_true <label> <condition-as-string>; runs the condition in a subshell
|
||||
local label="$1"; shift
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
if ( "$@" ); then
|
||||
printf ' ok %s\n' "$label"
|
||||
else
|
||||
printf ' FAIL %s (condition false)\n' "$label" >&2
|
||||
TESTS_FAIL=$((TESTS_FAIL + 1))
|
||||
FAILED_CASES+=("$label")
|
||||
fi
|
||||
}
|
||||
|
||||
assert_exit() {
|
||||
# assert_exit <label> <expected_code> -- <cmd...>
|
||||
local label="$1" expected="$2"; shift 2
|
||||
[[ "$1" == "--" ]] && shift
|
||||
local actual
|
||||
"$@" >/dev/null 2>&1
|
||||
actual=$?
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
printf ' ok %s (exit %s)\n' "$label" "$actual"
|
||||
else
|
||||
printf ' FAIL %s: expected exit %s, got %s\n' "$label" "$expected" "$actual" >&2
|
||||
TESTS_FAIL=$((TESTS_FAIL + 1))
|
||||
FAILED_CASES+=("$label (exit $expected != $actual)")
|
||||
fi
|
||||
}
|
||||
|
||||
# ---- stub claude on PATH ----------------------------------------------------
|
||||
|
||||
STUB_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$STUB_DIR" "$REAL_CLONE" "$CAPTURE" "$H"' EXIT
|
||||
export PATH="$STUB_DIR:$PATH"
|
||||
|
||||
cat > "$STUB_DIR/claude" <<'STUB'
|
||||
#!/usr/bin/env bash
|
||||
# If CLAUDE_ENV_CAPTURE is set, snapshot the exported ANTHROP_* vars into it.
|
||||
if [[ -n "${CLAUDE_ENV_CAPTURE:-}" ]]; then
|
||||
{
|
||||
printf 'BASE_URL=%s\n' "${ANTHROPIC_BASE_URL:-}"
|
||||
printf 'AUTH_TOKEN=%s\n' "${ANTHROPIC_AUTH_TOKEN:-}"
|
||||
printf 'API_KEY=%s\n' "${ANTHROPIC_API_KEY:-}"
|
||||
printf 'MODEL=%s\n' "${ANTHROPIC_MODEL:-}"
|
||||
} > "$CLAUDE_ENV_CAPTURE"
|
||||
fi
|
||||
echo "stub-claude-invoked"
|
||||
STUB
|
||||
chmod +x "$STUB_DIR/claude"
|
||||
|
||||
# ---- throwaway repo clone with a test config -------------------------------
|
||||
|
||||
REAL_CLONE="$(mktemp -d)"
|
||||
cp -r "$REPO_DIR"/. "$REAL_CLONE"/
|
||||
chmod +x "$REAL_CLONE/bin/"* "$REAL_CLONE/lib/profiles.sh" 2>/dev/null || true
|
||||
|
||||
cat > "$REAL_CLONE/config/profiles.yml" <<'YML'
|
||||
defaults:
|
||||
claude_cmd: "claude"
|
||||
openrouter_base_url: "https://openrouter.ai/api"
|
||||
profiles:
|
||||
pro:
|
||||
mode: "pro"
|
||||
glm:
|
||||
mode: "openrouter"
|
||||
model: "z-ai/glm-5.2"
|
||||
free:
|
||||
mode: "openrouter"
|
||||
model: "openrouter/free"
|
||||
badmode:
|
||||
mode: "bogus"
|
||||
YML
|
||||
|
||||
CAPTURE="$(mktemp)"
|
||||
|
||||
# Run a profile in a subshell (launch_profile execs claude -> ends subshell
|
||||
# only). Env capture written into $CAPTURE.
|
||||
run_profile() {
|
||||
CLAUDE_ENV_CAPTURE="$CAPTURE" bash -c '
|
||||
set -euo pipefail
|
||||
source "$0/lib/profiles.sh"
|
||||
launch_profile "$1"
|
||||
' "$REAL_CLONE" "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
field() { sed -n "s/^$1=//p" "$CAPTURE" 2>/dev/null; }
|
||||
|
||||
# ===========================================================================
|
||||
echo "## Profile logic"
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
echo "-- pro profile clears ANTHROPIC env and launches claude"
|
||||
export ANTHROPIC_BASE_URL="stale" ANTHROPIC_AUTH_TOKEN="stale" ANTHROPIC_MODEL="stale"
|
||||
run_profile pro
|
||||
assert_eq "pro: BASE_URL cleared" "" "$(field BASE_URL)"
|
||||
assert_eq "pro: AUTH_TOKEN cleared" "" "$(field AUTH_TOKEN)"
|
||||
assert_eq "pro: MODEL cleared" "" "$(field MODEL)"
|
||||
|
||||
echo "-- glm profile exports OpenRouter env"
|
||||
export OPENROUTER_API_KEY="sk-or-v1-TEST123"
|
||||
run_profile glm
|
||||
assert_eq "glm: BASE_URL" "https://openrouter.ai/api" "$(field BASE_URL)"
|
||||
assert_eq "glm: AUTH_TOKEN" "sk-or-v1-TEST123" "$(field AUTH_TOKEN)"
|
||||
assert_eq "glm: API_KEY empty" "" "$(field API_KEY)"
|
||||
assert_eq "glm: MODEL" "z-ai/glm-5.2" "$(field MODEL)"
|
||||
|
||||
echo "-- free profile exports configured model slug"
|
||||
run_profile free
|
||||
assert_eq "free: MODEL" "openrouter/free" "$(field MODEL)"
|
||||
|
||||
echo "-- error: missing OPENROUTER_API_KEY"
|
||||
assert_exit "missing key exits 1" 1 -- env -u OPENROUTER_API_KEY bash -c '
|
||||
set -euo pipefail
|
||||
source "$0/lib/profiles.sh"
|
||||
launch_profile glm
|
||||
' "$REAL_CLONE"
|
||||
|
||||
echo "-- error: unknown profile"
|
||||
assert_exit "unknown profile exits 1" 1 -- bash -c '
|
||||
set -euo pipefail
|
||||
source "$0/lib/profiles.sh"
|
||||
launch_profile does-not-exist
|
||||
' "$REAL_CLONE"
|
||||
|
||||
echo "-- error: unsupported mode"
|
||||
assert_exit "bad mode exits 1" 1 -- bash -c '
|
||||
set -euo pipefail
|
||||
source "$0/lib/profiles.sh"
|
||||
launch_profile badmode
|
||||
' "$REAL_CLONE"
|
||||
|
||||
# ===========================================================================
|
||||
echo "## Installer symlink logic"
|
||||
# ---------------------------------------------------------------------------
|
||||
# Run install-claude-profiles with HOME pointed at a temp dir, so the real
|
||||
# repo working tree is never touched.
|
||||
|
||||
target_points_to_clone() {
|
||||
local t="$1/src/claude-launcher-profiles"
|
||||
[[ -L "$t" ]] && [[ "$(readlink "$t")" == "$REAL_CLONE" ]]
|
||||
}
|
||||
|
||||
echo "-- S1: broken (dangling) symlink is repointed, not fatal"
|
||||
H="$(mktemp -d)"; mkdir -p "$H/src"
|
||||
ln -sfn /does/not/exist "$H/src/claude-launcher-profiles"
|
||||
HOME="$H" bash "$REAL_CLONE/bin/install-claude-profiles" >/dev/null 2>&1
|
||||
assert_true "S1 broken link repointed to clone" target_points_to_clone "$H"
|
||||
rm -rf "$H"
|
||||
|
||||
echo "-- S2: real directory at target -> warning, left in place"
|
||||
H="$(mktemp -d)"; mkdir -p "$H/src/claude-launcher-profiles"
|
||||
OUT="$(HOME="$H" bash "$REAL_CLONE/bin/install-claude-profiles" 2>&1 >/dev/null)"
|
||||
if [[ "$OUT" == *"exists and is not a symlink"* ]] && [[ -d "$H/src/claude-launcher-profiles" ]] && [[ ! -L "$H/src/claude-launcher-profiles" ]]; then
|
||||
assert_eq "S2 real dir warned and preserved" "ok" "ok"
|
||||
else
|
||||
assert_eq "S2 real dir warned and preserved" "ok" "FAIL ($OUT)"
|
||||
fi
|
||||
rm -rf "$H"
|
||||
|
||||
echo "-- S3: free path -> new symlink created"
|
||||
H="$(mktemp -d)"; mkdir -p "$H/src"
|
||||
HOME="$H" bash "$REAL_CLONE/bin/install-claude-profiles" >/dev/null 2>&1
|
||||
assert_true "S3 free path got a new symlink" target_points_to_clone "$H"
|
||||
rm -rf "$H"
|
||||
|
||||
echo "-- S4: existing valid symlink -> repointed"
|
||||
H="$(mktemp -d)"; mkdir -p "$H/src"
|
||||
ln -sfn /some/other/old "$H/src/claude-launcher-profiles"
|
||||
HOME="$H" bash "$REAL_CLONE/bin/install-claude-profiles" >/dev/null 2>&1
|
||||
assert_true "S4 existing symlink repointed" target_points_to_clone "$H"
|
||||
rm -rf "$H"
|
||||
|
||||
# ===========================================================================
|
||||
echo
|
||||
echo "------------------------------------------------------------"
|
||||
printf 'tests: %d run, %d failed\n' "$TESTS_RUN" "$TESTS_FAIL"
|
||||
if (( TESTS_FAIL > 0 )); then
|
||||
printf 'FAILED cases:\n'
|
||||
printf ' - %s\n' "${FAILED_CASES[@]}"
|
||||
exit 1
|
||||
fi
|
||||
echo "all tests passed."
|
||||
Loading…
Add table
Add a link
Reference in a new issue