#!/usr/bin/env bash # Behavioural tests for scripts/revision-preflight.sh # # Every case builds a throwaway clone with a real bare remote, so origin/master # is genuine and the guard's fetch path is exercised. Nothing outside mktemp is # touched. # # The pre-fix draft is kept at tests/fixtures/revision-preflight.prefix.sh and # is run against the SAME cases, to prove these tests bite: the pre-fix guard # exits 0 where the fixed guard exits 1. set -uo pipefail HERE="$(cd "$(dirname "$0")" && pwd)" REPO="$(cd "$HERE/.." && pwd)" GUARD="$REPO/scripts/revision-preflight.sh" PREFIX_GUARD="$HERE/fixtures/revision-preflight.prefix.sh" PASS=0 FAIL=0 FAILED_CASES=() pass() { printf ' ✓ %s\n' "$1"; PASS=$((PASS + 1)); } fail() { printf ' ✗ %s\n' "$1"; FAIL=$((FAIL + 1)); FAILED_CASES+=("$1"); } # Build a clone with a real remote; echo the clone path. make_clone() { local tmp tmp="$(mktemp -d)" git init --bare -q "$tmp/remote.git" git init -q "$tmp/clone" ( cd "$tmp/clone" || exit 1 git config user.email test@example.invalid git config user.name test mkdir -p scripts printf '#!/bin/bash\necho hello\n' > scripts/demo.sh chmod +x scripts/demo.sh git add -A git commit -qm init git branch -M master git remote add origin "$tmp/remote.git" git push -q origin master git fetch -q origin ) echo "$tmp/clone" } echo "== revision-preflight behavioural tests ==" # ── 1. match → exit 0 ──────────────────────────────────────────────────────── echo "1. matching copy" C=$(make_clone) if out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); then pass "matching copy exits 0" else fail "matching copy should exit 0 (got $?, output: $out)" fi if [[ -z "$( "$GUARD" --quiet "$C/scripts/demo.sh" "$C" 2>&1 )" ]]; then pass "--quiet prints nothing on a match" else fail "--quiet should print nothing on a match" fi rm -rf "$(dirname "$C")" # ── 2. mismatch → exit 1 and names both hashes ─────────────────────────────── echo "2. mismatched copy" C=$(make_clone) printf '#!/bin/bash\necho TAMPERED\n' > "$C/scripts/demo.sh" out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "mismatch exits 1"; else fail "mismatch should exit 1 (got $rc)"; fi if [[ "$out" == *"MISMATCH"* ]]; then pass "mismatch says MISMATCH"; else fail "mismatch should say MISMATCH"; fi if [[ "$out" == *"executed:"* && "$out" == *"merged:"* ]]; then pass "mismatch prints both revisions" else fail "mismatch should print both revisions" fi rm -rf "$(dirname "$C")" # ── 3. paths under scripts/ resolve (the original basename defect) ─────────── echo "3. repo-relative path resolution" C=$(make_clone) if "$GUARD" --quiet "$C/scripts/demo.sh" "$C" >/dev/null 2>&1; then pass "script under scripts/ resolves against origin/master" else fail "script under scripts/ must resolve (basename defect)" fi rm -rf "$(dirname "$C")" # ── 4. script that exists in NO revision → must fail ───────────────────────── echo "4. untracked script present in no revision" C=$(make_clone) printf '#!/bin/bash\necho never committed\n' > "$C/scripts/ghost.sh" out=$("$GUARD" "$C/scripts/ghost.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "ghost script exits 1"; else fail "ghost script must exit 1 (got $rc)"; fi if [[ "$out" == *"does not exist in"* ]]; then pass "ghost script says it is absent from the ref" else fail "ghost script should say it is absent from the ref" fi rm -rf "$(dirname "$C")" reason_of() { grep -m1 '^REASON=' <<<"$1" | cut -d= -f2-; } # ── 5. unresolvable ref → CANNOT VERIFY (exit 2) ───────────────────────────── echo "5. unresolvable ref" C=$(make_clone) out=$("$GUARD" --no-fetch --ref origin/nope "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 2 ]]; then pass "unresolvable ref exits 2 (cannot verify)"; else fail "unresolvable ref must exit 2 (got $rc)"; fi if [[ "$(reason_of "$out")" == "cannot-verify:ref-unresolvable" ]]; then pass "unresolvable ref names cannot-verify:ref-unresolvable" else fail "unresolvable ref should name its class (got: $(reason_of "$out"))" fi rm -rf "$(dirname "$C")" # ── 5b. fetch failure → CANNOT VERIFY, and it names that ───────────────────── echo "5b. fetch failed" C=$(make_clone) ( cd "$C" && git remote set-url origin /nonexistent/definitely-not-a-repo ) out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 2 ]]; then pass "fetch failure exits 2 (cannot verify)"; else fail "fetch failure must exit 2 (got $rc)"; fi if [[ "$(reason_of "$out")" == "cannot-verify:fetch-failed" ]]; then pass "fetch failure names cannot-verify:fetch-failed" else fail "fetch failure should name its class (got: $(reason_of "$out"))" fi if [[ "$out" == *"bound:"* ]]; then pass "fetch failure reports the bound"; else fail "fetch failure should report the bound"; fi rm -rf "$(dirname "$C")" # ── 5c. fetch timeout → CANNOT VERIFY, bounded (never hangs) ───────────────── echo "5c. fetch timeout is bounded" C=$(make_clone) # a remote that will never answer: a fifo-backed git daemon is overkill, so use # a black-hole address with a 1s bound and assert we return promptly. ( cd "$C" && git remote set-url origin http://10.255.255.1:9/never.git ) start=$(date +%s) out=$("$GUARD" --fetch-timeout 1 "$C/scripts/demo.sh" "$C" 2>&1); rc=$? elapsed=$(( $(date +%s) - start )) if [[ $rc -eq 2 ]]; then pass "timeout exits 2 (cannot verify)"; else fail "timeout must exit 2 (got $rc)"; fi if [[ "$(reason_of "$out")" == "cannot-verify:fetch-failed" ]]; then pass "timeout names cannot-verify:fetch-failed" else fail "timeout should name its class (got: $(reason_of "$out"))" fi if [[ $elapsed -le 10 ]]; then pass "timeout returned promptly (${elapsed}s, bound 1s)"; else fail "timeout did not bound the fetch (${elapsed}s)"; fi rm -rf "$(dirname "$C")" # ── 6. missing script → MISMATCH:path-absent (exit 1) ──────────────────────── echo "6. missing script" C=$(make_clone) out=$("$GUARD" "$C/scripts/nope.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "missing script exits 1"; else fail "missing script must exit 1 (got $rc)"; fi if [[ "$(reason_of "$out")" == "mismatch:path-absent" ]]; then pass "missing script names mismatch:path-absent" else fail "missing script should name its class (got: $(reason_of "$out"))" fi rm -rf "$(dirname "$C")" # ── 7. script outside the clone → MISMATCH (exit 1) ────────────────────────── echo "7. script outside the clone" C=$(make_clone) OUTSIDE=$(mktemp) printf '#!/bin/bash\necho outside\n' > "$OUTSIDE" out=$("$GUARD" "$OUTSIDE" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "outside script exits 1"; else fail "outside script must exit 1 (got $rc)"; fi rm -f "$OUTSIDE"; rm -rf "$(dirname "$C")" # ── 7b. detached HEAD is named, not reported as a raw content mismatch ─────── echo "7b. detached HEAD" C=$(make_clone) ( cd "$C" && printf '#!/bin/bash\necho TAMPERED\n' > scripts/demo.sh \ && git add scripts/demo.sh && git commit -qm tamper && git checkout -q --detach HEAD ) out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "detached HEAD exits 1"; else fail "detached HEAD must exit 1 (got $rc)"; fi if [[ "$(reason_of "$out")" == "mismatch:detached-head" ]]; then pass "detached HEAD names mismatch:detached-head" else fail "detached HEAD should name its class (got: $(reason_of "$out"))" fi rm -rf "$(dirname "$C")" # ── 7c. a branch ahead of the ref is named as such, not as a raw mismatch ──── echo "7c. clone ahead of the ref" C=$(make_clone) ( cd "$C" && git checkout -q -b feature \ && printf '#!/bin/bash\necho FEATURE\n' > scripts/demo.sh \ && git add scripts/demo.sh && git commit -qm feature ) out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 1 ]]; then pass "clone ahead exits 1"; else fail "clone ahead must exit 1 (got $rc)"; fi if [[ "$(reason_of "$out")" == "mismatch:clone-ahead" ]]; then pass "clone ahead names mismatch:clone-ahead" else fail "clone ahead should name its class (got: $(reason_of "$out"))" fi if [[ "$out" == *"mid-review"* ]]; then pass "clone ahead explains it is a legitimate state"; else fail "clone ahead should explain the state"; fi rm -rf "$(dirname "$C")" # ── 7d. a genuine content mismatch is named as content ────────────────────── echo "7d. genuine content mismatch" C=$(make_clone) printf '#!/bin/bash\necho TAMPERED\n' > "$C/scripts/demo.sh" out=$("$GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ "$(reason_of "$out")" == "mismatch:content" ]]; then pass "hand-edit names mismatch:content" else fail "hand-edit should name mismatch:content (got: $(reason_of "$out"))" fi rm -rf "$(dirname "$C")" # ── 8. --no-fetch states the freshness assumption ──────────────────────────── echo "8. --no-fetch states its assumption" C=$(make_clone) out=$("$GUARD" --no-fetch "$C/scripts/demo.sh" "$C" 2>&1) if [[ "$out" == *"freshness is assumed"* ]]; then pass "--no-fetch states the freshness assumption" else fail "--no-fetch should state the freshness assumption" fi rm -rf "$(dirname "$C")" # ── 8b. contract-run.sh defaults to warn, not enforce ─────────────────────── echo "8b. contract-run.sh default mode" DEFAULT=$(grep -m1 'CONTRACT_REVISION_PREFLIGHT:-' "$REPO/scripts/contract-run.sh" | sed 's/.*:-//; s/}.*//') if [[ "$DEFAULT" == "warn" ]]; then pass "contract-run.sh defaults to warn" else fail "contract-run.sh default must be warn (found: '$DEFAULT')" fi if grep -q 'CONTRACT_REVISION_PREFLIGHT=enforce' "$REPO/scripts/contract-run.sh"; then pass "enforce remains available and documented" else fail "enforce must remain documented" fi # ── 9. the pre-fix guard must FAIL these same cases (proves the tests bite) ── echo "9. pre-fix draft fails the same cases (bite proof)" if [[ ! -f "$PREFIX_GUARD" ]]; then fail "pre-fix fixture missing: $PREFIX_GUARD" else # 9a. repo-relative path: pre-fix drops scripts/ and cannot resolve C=$(make_clone) out=$("$PREFIX_GUARD" "$C/scripts/demo.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 0 && "$out" == *"could not resolve"* ]]; then pass "pre-fix: exits 0 and cannot resolve scripts/demo.sh (defect confirmed)" else fail "pre-fix should exit 0 with 'could not resolve' (got rc=$rc)" fi rm -rf "$(dirname "$C")" # 9b. ghost script: pre-fix passes a script that exists in no revision C=$(make_clone) printf '#!/bin/bash\necho never committed\n' > "$C/scripts/ghost.sh" out=$("$PREFIX_GUARD" "$C/scripts/ghost.sh" "$C" 2>&1); rc=$? if [[ $rc -eq 0 ]]; then pass "pre-fix: PASSES a ghost script that exists in no revision (defect confirmed)" else fail "pre-fix was expected to wrongly pass the ghost script (got rc=$rc)" fi rm -rf "$(dirname "$C")" # 9c. a file that DOES exist at the repo root still works pre-fix, showing # the defect is specific to nested paths C=$(make_clone) printf '#!/bin/bash\necho root\n' > "$C/rootlevel.sh" ( cd "$C" && git add rootlevel.sh && git commit -qm root && git push -q origin master && git fetch -q origin ) if "$PREFIX_GUARD" "$C/rootlevel.sh" "$C" >/dev/null 2>&1; then pass "pre-fix: root-level path resolves (so the defect is the basename, not git)" else fail "pre-fix should resolve a root-level tracked file" fi rm -rf "$(dirname "$C")" fi echo echo " passed: $PASS failed: $FAIL" if [[ $FAIL -gt 0 ]]; then printf ' FAILED: %s\n' "${FAILED_CASES[@]}" exit 1 fi echo "All revision-preflight tests passed."