#!/usr/bin/env bash
# Regression tests for bin/_git-guard-shim.sh (installed as bin/git) — the PATH shim
# that gives codex (`cdx exec`) and cursor-agent the same main-checkout protection
# Claude Code gets from hooks/main-checkout-guard.sh. Neither runtime reads Claude's
# PreToolUse hooks; both resolve `git` via PATH, which is how this is enforced for
# every runtime at one choke point instead of duplicating the check per-runtime.
#
# The shim only acts when AGENT_BUILD_SCOPE_ACTIVE=1 (set unconditionally by
# _agent-build-scope for every claude/codex/cursor-agent launch) — a human typing
# `git restore` in their own shell never has it set, so manual use is untouched.
#
# The "real" git in these tests logs every invocation it receives to GIT_CALL_LOG
# before delegating to the actual system git, so a test can assert whether the
# command that was TYPED actually reached git (allowed) or never did (blocked).
# Run: bash git-guard-shim.test.sh (exit 0 = all pass).
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
GIT_SHIM="$ROOT/bin/git"
PASS=0; FAIL=0
ok()  { PASS=$((PASS+1)); printf 'PASS %s\n' "$1"; }
bad() { FAIL=$((FAIL+1)); printf 'FAIL %s\n     %s\n' "$1" "$2"; }

TMP=$(mktemp -d "${TMPDIR:-/tmp}/ggs-test-XXXX")
trap 'rm -rf "$TMP"' EXIT

REALGIT="$(command -v git)"
mkdir -p "$TMP/realbin"
cat > "$TMP/realbin/git" <<EOF
#!/usr/bin/env bash
echo "CALL \$*" >> "\$GIT_CALL_LOG"
exec "$REALGIT" "\$@"
EOF
chmod +x "$TMP/realbin/git"

REPO="$TMP/repo"
mkdir -p "$REPO"
git -C "$REPO" init -q
git -C "$REPO" config user.email t@t; git -C "$REPO" config user.name t; git -C "$REPO" config commit.gpgsign false
echo x > "$REPO/f"; git -C "$REPO" add -A; git -C "$REPO" commit -qm init
WT="$REPO/.worktrees/wt1"; mkdir -p "$WT"

LOG="$TMP/calls.log"

# run <dir> <agent_active:0|1> <git-args...> -> exit code; sets $LAST_LOG (last
# logged invocation) and $LAST_ERR (stderr) as globals.
run() {
  local dir="$1" active="$2"; shift 2
  : > "$LOG"
  if [[ "$active" == 1 ]]; then
    ( cd "$dir" && env PATH="$TMP/realbin:/usr/bin:/bin" GIT_CALL_LOG="$LOG" AGENT_BUILD_SCOPE_ACTIVE=1 bash "$GIT_SHIM" "$@" )
  else
    ( cd "$dir" && env PATH="$TMP/realbin:/usr/bin:/bin" GIT_CALL_LOG="$LOG" bash "$GIT_SHIM" "$@" )
  fi >"$TMP/out" 2>"$TMP/err"
  local rc=$?
  LAST_ERR="$(cat "$TMP/err")"
  LAST_LOG="$(tail -1 "$LOG" 2>/dev/null || true)"
  return $rc
}

# A. Destructive verb inside the main checkout, agent-scoped: BLOCKED — the real
#    git binary is never invoked with the destructive command (only the shim's own
#    internal rev-parse classification call is logged).
run "$REPO" 1 restore .
rc=$?
if [[ $rc -eq 77 && "$LAST_LOG" != *"CALL restore ."* && "$LAST_ERR" == *"git-guard"* ]]; then
  ok "main-checkout restore blocked (agent-scoped)"
else
  bad "main-checkout restore blocked" "rc=$rc log=$LAST_LOG err=$LAST_ERR"
fi

# B. Same command, NOT agent-scoped (simulates a human typing it by hand): ALLOWED.
run "$REPO" 0 status
[[ $? -eq 0 && "$LAST_LOG" == "CALL status" ]] \
  && ok "human-invoked (no AGENT_BUILD_SCOPE_ACTIVE) passes through" \
  || bad "human-invoked passthrough" "rc=$? log=$LAST_LOG"

# C. Destructive verb inside a WORKTREE, agent-scoped: ALLOWED (git-guard only
#    protects the main checkout; per-worktree ownership is worktree-lock-gate's job).
run "$WT" 1 reset --hard
[[ $? -eq 0 && "$LAST_LOG" == "CALL reset --hard" ]] \
  && ok "destructive verb in worktree allowed" \
  || bad "destructive in worktree allowed" "rc=$? log=$LAST_LOG"

# D. Read-only git inside the main checkout, agent-scoped: ALLOWED.
run "$REPO" 1 status
[[ $? -eq 0 && "$LAST_LOG" == "CALL status" ]] \
  && ok "read-only git in main checkout allowed" \
  || bad "read-only allowed" "rc=$? log=$LAST_LOG"

# E. `-C <main_root>` from inside a worktree still resolves to the main checkout —
#    an agent cannot dodge the guard by redirecting via -C instead of `cd`.
run "$WT" 1 -C "$REPO" restore .
rc=$?
[[ $rc -eq 77 && "$LAST_ERR" == *"git-guard"* ]] \
  && ok "-C redirect into main checkout blocked" \
  || bad "-C redirect blocked" "rc=$rc err=$LAST_ERR"

# F. A repo that never opted into worktree isolation (no .worktrees dir) is untouched.
REPO2="$TMP/repo2"; mkdir -p "$REPO2"; git -C "$REPO2" init -q
git -C "$REPO2" config user.email t@t; git -C "$REPO2" config user.name t; git -C "$REPO2" config commit.gpgsign false
echo x > "$REPO2/f"; git -C "$REPO2" add -A; git -C "$REPO2" commit -qm init
run "$REPO2" 1 restore .
[[ $? -eq 0 && "$LAST_LOG" == "CALL restore ." ]] \
  && ok "non-opted-in repo left alone" \
  || bad "non-opted-in repo left alone" "rc=$? log=$LAST_LOG"

echo
echo "PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]]
