#!/usr/bin/env bash
# Unit coverage for the image's single git deny-gate implementation.
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SHIM="$ROOT/bin/git"
ENTRY="$ROOT/bin/sandbox-egress-init"
TMP="$(mktemp -d "${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/deny-gate.XXXXXX")"
WORK="/sandbox/workspaces/deny-gate-test-$$"
PASS=0 FAIL=0
trap 'rm -rf "$TMP" "$WORK"' EXIT
ok() { PASS=$((PASS + 1)); echo "PASS $1"; }
bad() { FAIL=$((FAIL + 1)); echo "FAIL $1: $2"; }

run_gate() {
  local gap="$TMP/gaps.jsonl"
  SANDBOX_TOOLGAP_FILE="$gap" "${GATE:-$SHIM}" "$@" >"$TMP/gate-out" 2>"$TMP/err"
}
# For denials whose first argument is a global option, not the verb the
# message names.
expect_deny_naming() {
  local name=$1 expected=$2; shift 2
  rm -f "$TMP/gaps.jsonl"
  run_gate "$@"; local rc=$?
  [[ $rc -eq 86 && $(<"$TMP/err") == *"$expected"* && $(<"$TMP/err") == *2026-08-16-remote-deny-gate-design.md* ]] \
    && ok "$name" || bad "$name" "rc=$rc $(<"$TMP/err")"
}
expect_deny() {
  local name=$1; shift
  rm -f "$TMP/gaps.jsonl"
  run_gate "$@"; local rc=$?
  [[ $rc -eq 86 && $(wc -l < "$TMP/err") -eq 1 && $(<"$TMP/err") == *"$1"* && $(<"$TMP/err") == *2026-08-16-remote-deny-gate-design.md* ]] \
    && jq -e '.kind == "git-deny" and (.argv | length > 0) and (.cwd | type == "string") and (.ts | type == "string")' "$TMP/gaps.jsonl" >/dev/null \
    && ok "$name" || bad "$name" "rc=$rc $(<"$TMP/err")"
}
expect_allow() {
  local name=$1; shift
  run_gate "$@"; local rc=$?
  [[ $rc -ne 86 ]] && ok "$name" || bad "$name" "unexpected deny $(<"$TMP/err")"
}

"$SHIM" --deny-gate-selftest >/dev/null 2>&1
[[ $? -eq 0 ]] && ok "self-test exercises deny and allow" || bad "self-test exercises deny and allow" "failed"

expect_deny "push --force is denied" push --force origin HEAD:refs/cdx/x/out
expect_deny "push -f is denied" push -f origin HEAD:refs/cdx/x/out
expect_deny "push --no-verify is denied" push --no-verify origin HEAD:refs/cdx/x/out
expect_deny "any --no-verify is denied" commit --no-verify -m x
expect_deny "main on any remote is denied" push origin HEAD:refs/heads/main
expect_deny "master on any remote is denied" push mirror HEAD:refs/heads/master
expect_deny "non-result push is denied" push origin HEAD:refs/heads/topic
expect_allow "cdx result lane is allowed" push mirror sha:refs/cdx/x/out
expect_allow "seat result lane is allowed" push origin HEAD:refs/harness-seat/x
expect_allow "factory result lane is allowed" push origin HEAD:refs/heads/factory-result/x
expect_allow "wip lane is allowed" push origin HEAD:refs/heads/wip/x
# A shim that only reads $1 is bypassed by any global option before the verb.
expect_deny_naming "git-dir option cannot smuggle a push past the gate" "denied push" --git-dir=/tmp/x.git push origin HEAD:refs/heads/main
expect_deny_naming "-c option cannot smuggle a push past the gate" "denied push" -c user.email=x push origin HEAD:refs/heads/main

# /sandbox/workspaces exists only inside the image; a sed'd copy pins the
# workspace root into the temp dir so this file is hermetic on any host.
WORK="$TMP/ws"
sed "s|^WORKSPACE_ROOT=.*|WORKSPACE_ROOT=$WORK|" "$SHIM" > "$TMP/shim-ws"
chmod +x "$TMP/shim-ws"
GATE="$TMP/shim-ws"
mkdir -p "$WORK/in" "$TMP/repo-out"
/usr/bin/git -C "$WORK/in" init -q
/usr/bin/git -C "$TMP/repo-out" init -q
pushd "$WORK/in" >/dev/null
expect_allow "reset --hard inside workspace is allowed" reset --hard
popd >/dev/null
pushd "$TMP/repo-out" >/dev/null
expect_deny "reset --hard outside workspace is denied" reset --hard
expect_deny "clean -f outside workspace is denied" clean -fd
expect_deny "clean --force outside workspace is denied" clean --force
expect_deny_naming "git-dir override cannot relocate a destructive verb" "denied reset" --git-dir "$TMP/repo-out/.git" reset --hard
expect_deny "checkout -- outside workspace is denied" checkout --
expect_deny "checkout . outside workspace is denied" checkout .
expect_deny "restore outside workspace is denied" restore file
expect_deny "stash drop outside workspace is denied" stash drop
expect_deny "stash clear outside workspace is denied" stash clear
expect_deny "gc --prune=now outside workspace is denied" gc --prune=now
expect_deny "update-ref -d outside workspace is denied" update-ref -d refs/x
expect_deny "branch -D outside workspace is denied" branch -D x
popd >/dev/null
GATE=""

# Exercise the mandatory entrypoint without nft privileges. This scratch copy
# replaces only nft with a deterministic successful test double, then masks the
# shim through PATH; the entrypoint must refuse with the egress exit-12 shape.
cat > "$TMP/nft" <<'SH'
#!/bin/sh
case "$*" in *"list chain"*) printf 'type filter hook output priority filter; policy drop;\n' ;; esac
SH
chmod +x "$TMP/nft"
mkdir -p "$TMP/rules" "$TMP/bin"
: > "$TMP/rules/rules.nft"
sed "s|/run/overdeck-egress/rules.nft|$TMP/rules/rules.nft|; s|/usr/sbin/nft|$TMP/nft|g" "$ENTRY" > "$TMP/egress-init"
chmod +x "$TMP/egress-init"
PATH="$TMP/bin:/usr/bin:/bin" "$TMP/egress-init" true >"$TMP/entry-out" 2>"$TMP/entry-err"
rc=$?
[[ $rc -eq 12 && $(<"$TMP/entry-err") == *'git deny gate is unavailable; refusing launch'* ]] \
  && ok "egress entrypoint refuses a masked shim" || bad "egress entrypoint refuses a masked shim" "rc=$rc $(<"$TMP/entry-err")"

cat > "$TMP/bin/git" <<'SH'
#!/bin/sh
exit 1
SH
chmod +x "$TMP/bin/git"
sed "s|/run/overdeck-egress/rules.nft|$TMP/rules/rules.nft|; s|/usr/sbin/nft|$TMP/nft|g; s|/usr/local/bin/git|$TMP/bin/git|g" "$ENTRY" > "$TMP/egress-init-selftest"
chmod +x "$TMP/egress-init-selftest"
PATH="$TMP/bin:/usr/bin:/bin" "$TMP/egress-init-selftest" true >"$TMP/selftest-out" 2>"$TMP/selftest-err"
rc=$?
[[ $rc -eq 12 && $(<"$TMP/selftest-err") == *'git deny gate is unavailable; refusing launch'* ]] \
  && ok "egress entrypoint refuses a failed shim self-test" || bad "egress entrypoint refuses a failed shim self-test" "rc=$rc $(<"$TMP/selftest-err")"

echo "deny-gate: $PASS passed, $FAIL failed"
[[ $FAIL -eq 0 ]]
