diff options
Diffstat (limited to 'languages/bash/tests/validate-bash.bats')
| -rw-r--r-- | languages/bash/tests/validate-bash.bats | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/languages/bash/tests/validate-bash.bats b/languages/bash/tests/validate-bash.bats new file mode 100644 index 0000000..9f268a1 --- /dev/null +++ b/languages/bash/tests/validate-bash.bats @@ -0,0 +1,96 @@ +#!/usr/bin/env bats +# +# Tests for languages/bash/claude/hooks/validate-bash.sh — the PostToolUse hook +# that runs shellcheck on edited shell files and blocks on a violation. +# +# The hook reads tool-call JSON on stdin and extracts the file path, so each +# test pipes a JSON payload naming a real file it wrote into a temp dir. The +# shellcheck dependency is real (integration): clean files pass, genuinely +# broken ones fail. Tests needing shellcheck skip when it's absent so the suite +# stays portable. + +HOOK="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)/claude/hooks/validate-bash.sh" + +setup() { + TEST_DIR="$(mktemp -d -t validate-bash-bats.XXXXXX)" +} + +teardown() { + rm -rf "$TEST_DIR" +} + +# Build a tool-call JSON payload naming a file_path. +payload() { + printf '{"tool_input": {"file_path": "%s"}}' "$1" +} + +# ---- Normal ---------------------------------------------------------- + +@test "validate-bash: a clean .sh file passes silently (exit 0)" { + command -v shellcheck >/dev/null 2>&1 || skip "shellcheck not installed" + printf '#!/usr/bin/env bash\nset -euo pipefail\necho "ok"\n' > "$TEST_DIR/clean.sh" + run bash "$HOOK" <<< "$(payload "$TEST_DIR/clean.sh")" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +# ---- Error ----------------------------------------------------------- + +@test "validate-bash: a shellcheck violation blocks (exit 2, names shellcheck)" { + command -v shellcheck >/dev/null 2>&1 || skip "shellcheck not installed" + # SC2086: unquoted expansion that word-splits — a real shellcheck warning. + printf '#!/usr/bin/env bash\nf=$1\nrm $f\n' > "$TEST_DIR/bad.sh" + run bash "$HOOK" <<< "$(payload "$TEST_DIR/bad.sh")" + [ "$status" -eq 2 ] + [[ "$output" == *"SHELLCHECK"* ]] +} + +# ---- Boundary -------------------------------------------------------- + +@test "validate-bash: a non-shell file is ignored (exit 0)" { + printf 'print("hello")\n' > "$TEST_DIR/script.py" + run bash "$HOOK" <<< "$(payload "$TEST_DIR/script.py")" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "validate-bash: an extensionless file with a bash shebang is validated" { + command -v shellcheck >/dev/null 2>&1 || skip "shellcheck not installed" + printf '#!/usr/bin/env bash\nf=$1\nrm $f\n' > "$TEST_DIR/cli-tool" + run bash "$HOOK" <<< "$(payload "$TEST_DIR/cli-tool")" + [ "$status" -eq 2 ] + [[ "$output" == *"SHELLCHECK"* ]] +} + +@test "validate-bash: an extensionless non-shell file is ignored (exit 0)" { + printf 'just some text\nno shebang here\n' > "$TEST_DIR/notes" + run bash "$HOOK" <<< "$(payload "$TEST_DIR/notes")" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "validate-bash: empty file_path is a no-op (exit 0)" { + run bash "$HOOK" <<< '{"tool_input": {}}' + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "validate-bash: a missing file is a no-op (exit 0)" { + run bash "$HOOK" <<< "$(payload "$TEST_DIR/does-not-exist.sh")" + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "validate-bash: shellcheck absent does not block the edit (exit 0)" { + # PATH with jq + coreutils symlinked but no shellcheck → hook can't validate, + # must not block. + STUB="$TEST_DIR/bin" + mkdir -p "$STUB" + for b in bash jq head cat printf grep sed; do + src="$(command -v "$b" 2>/dev/null)" && ln -sf "$src" "$STUB/$b" + done + printf '#!/usr/bin/env bash\nf=$1\nrm $f\n' > "$TEST_DIR/bad.sh" + run env PATH="$STUB" bash "$HOOK" <<< "$(payload "$TEST_DIR/bad.sh")" + [ "$status" -eq 0 ] + [ -z "$output" ] +} |
