#!/usr/bin/env bash # tests/test-sh-formatter.sh — assert formatters/sh runs SETE-1/2 and LEAK-1 # # House convention: bash, set -uo pipefail, SKIP-and-exit-0 when a tool is # missing, one PASS:/FAIL: line per assertion, FAIL=1 aggregate, ALL_OK on # full pass. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" FORMATTER="$SCRIPT_DIR/../sh" # --- SKIP gates: missing tool → exit 0 silently --- if ! command -v python3 >/dev/null 2>&1; then echo "SKIP: python3 not on PATH — formatters/sh custom checks need it" exit 0 fi if [ ! -x "$FORMATTER" ]; then echo "SKIP: formatters/sh not found or not executable at $FORMATTER" exit 0 fi PASS=0 FAIL=0 pass() { printf "PASS: %s\n" "$1" PASS=$((PASS + 1)) } fail() { printf "FAIL: %s\n" "$1" [[ -n "${2:-}" ]] && printf " %s\n" "$2" FAIL=$((FAIL + 1)) } TMPDIR="$(mktemp -d)" trap 'rm -rf "$TMPDIR"' EXIT # Write a fixture and run the formatter. Assert: # - exit code matches expected_exit # - stderr contains the pattern (when expect_pattern is set) assert_flagged() { local label="$1" local expected_exit="$2" local pattern="$3" local content="$4" local f="$TMPDIR/$(printf '%s' "$label" | tr ' /' '__').sh" printf '%s\n' "$content" > "$f" local stderr_file="$TMPDIR/$(basename "$f").stderr" "$FORMATTER" "$f" >/dev/null 2>"$stderr_file" local actual_exit=$? local stderr_content stderr_content="$(cat "$stderr_file")" if [ "$actual_exit" -ne "$expected_exit" ]; then fail "$label (exit)" "expected $expected_exit, got $actual_exit. stderr: $stderr_content" return fi if [ -n "$pattern" ] && ! echo "$stderr_content" | grep -qE "$pattern"; then fail "$label (pattern)" "expected pattern '$pattern', got: $stderr_content" return fi pass "$label" } # Write a fixture and run the formatter. Assert: # - exit code is 0 # - stderr does NOT contain the forbidden pattern assert_clean() { local label="$1" local forbidden="$2" local content="$3" local f="$TMPDIR/$(printf '%s' "$label" | tr ' /' '__').sh" printf '%s\n' "$content" > "$f" local stderr_file="$TMPDIR/$(basename "$f").stderr" "$FORMATTER" "$f" >/dev/null 2>"$stderr_file" local actual_exit=$? local stderr_content stderr_content="$(cat "$stderr_file")" if [ "$actual_exit" -ne 0 ]; then fail "$label (exit)" "expected 0, got $actual_exit. stderr: $stderr_content" return fi if [ -n "$forbidden" ] && echo "$stderr_content" | grep -qE "$forbidden"; then fail "$label (forbidden pattern)" "should NOT match '$forbidden', got: $stderr_content" return fi pass "$label" } # =========================================================================== echo "=== SETE-1: shebanged script without set -e ===" # =========================================================================== # Positive: shebang, no set -e anywhere → must flag SETE-1, exit 1 assert_flagged "SETE-1 positive" 1 "SETE-1" \ "#!/usr/bin/env bash echo hello ls /nonexistent echo world" # Negative: shebang + set -euo pipefail → no SETE-1 finding, exit 0 assert_clean "SETE-1 negative (has set -euo pipefail)" "SETE-1" \ "#!/usr/bin/env bash set -euo pipefail echo hello ls /nonexistent || true" # Negative: shebang + set -e (short form) → no SETE-1 finding, exit 0 assert_clean "SETE-1 negative (has set -e)" "SETE-1" \ "#!/usr/bin/env bash set -e echo hello" # Negative: sourced fragment (no shebang) without set -e → no SETE-1 finding # `shell=bash` directive satisfies shellcheck's SC2148 ("add a shebang or shell directive") assert_clean "SETE-1 negative (sourced fragment, no shebang)" "SETE-1" \ "# shellcheck shell=bash echo hello ls /nonexistent || true echo world" # =========================================================================== echo "" echo "=== SETE-2: sh/bash -c with multi-command string and no set -e ===" # =========================================================================== # Positive: sh -c with two ;-separated commands and no set -e → flag SETE-2 assert_flagged "SETE-2 positive (hugo mod get; mod tidy)" 1 "SETE-2" \ "#!/usr/bin/env bash set -euo pipefail sh -c \"hugo mod get github.com/foo/bar; hugo mod tidy\"" # Positive: bash -c with multiple ; commands, no set -e assert_flagged "SETE-2 positive (bash -c cmd1; cmd2)" 1 "SETE-2" \ "#!/usr/bin/env bash set -euo pipefail bash -c \"echo a; echo b; echo c\"" # Negative: sh -c with single command → no SETE-2 finding assert_clean "SETE-2 negative (single command)" "SETE-2" \ "#!/usr/bin/env bash set -euo pipefail sh -c \"hugo mod tidy\"" # Negative: sh -c with multi commands BUT set -e inside → no SETE-2 finding assert_clean "SETE-2 negative (has set -e inside)" "SETE-2" \ "#!/usr/bin/env bash set -euo pipefail sh -c \"set -e; hugo mod get; hugo mod tidy\"" # =========================================================================== echo "" echo "=== LEAK-1: secret-named file read into echo/printf/die/error/fail ===" # =========================================================================== # Positive: die "$(cat "$KV_JSON_FILE")" — the canonical real case from the brief assert_flagged "LEAK-1 positive (die with cat \$KV_JSON_FILE)" 1 "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail die \"kv put failed: \$(cat \"\$KV_JSON_FILE\")\"" # Positive: echo with TOKEN-named file read assert_flagged "LEAK-1 positive (echo with \$GITHUB_TOKEN_FILE)" 1 "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail echo \"got: \$(cat \"\$GITHUB_TOKEN_FILE\")\"" # Positive: printf with PASSWORD-named file read assert_flagged "LEAK-1 positive (printf with \$DB_PASSWORD_FILE)" 1 "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail printf 'db password was: %s\n' \"\$(< \"\$DB_PASSWORD_FILE\")\"" # Negative: die with no secret-named variable assert_clean "LEAK-1 negative (regular error)" "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail die \"kv put failed: connection refused\"" # Negative: die with secret-named var but NOT read via cat/$() — just interpolated assert_clean "LEAK-1 negative (var interpolated, not cat-substituted)" "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail RESULT_VAR=foo die \"error code: \$RESULT_VAR\"" # Negative: secret-named var read via cat but NOT in echo/die/etc. # Mark vars as used via export so shellcheck doesn't flag SC2034. assert_clean "LEAK-1 negative (secret file read, not in echo)" "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail contents=\$(cat \"\$API_TOKEN_FILE\") export contents" # Negative: echo with non-secret variable name assert_clean "LEAK-1 negative (echo with non-secret var)" "LEAK-1" \ "#!/usr/bin/env bash set -euo pipefail echo \"result file content: \$(cat \"\$RESULT_FILE\")\"" # =========================================================================== echo "" echo "=== Missing-tool warning (formatters/sh must NOT silently pass) ===" # =========================================================================== # When neither tool is on PATH, formatter should emit ONE warning line. # Build a PATH that has bash + python3 but no shfmt or shellcheck, then run. PATH_CLEAN="$(mktemp -d)" ln -sf /usr/bin/bash "$PATH_CLEAN/bash" ln -sf /usr/bin/python3 "$PATH_CLEAN/python3" stderr_file="$TMPDIR/no-tools.stderr" # Use a clean fixture — must not flag any SETE/LEAK. printf '#!/usr/bin/env bash\nset -euo pipefail\necho hello\n' > "$TMPDIR/clean-fixture.sh" PATH="$PATH_CLEAN" bash "$FORMATTER" "$TMPDIR/clean-fixture.sh" >/dev/null 2>"$stderr_file" rc=$? stderr_content="$(cat "$stderr_file")" rm -rf "$PATH_CLEAN" if [ "$rc" -eq 0 ] && echo "$stderr_content" | grep -qE 'formatters/sh: shfmt and shellcheck not installed'; then pass "missing-tool warning emitted, exit 0" else fail "missing-tool warning" "expected exit 0 + warning line, got exit=$rc stderr=$stderr_content" fi # =========================================================================== echo "" echo "Results: $PASS passed, $FAIL failed" if [ "$FAIL" -eq 0 ]; then echo "ALL_OK" exit 0 fi exit 1