#!/usr/bin/env bash
# tests/g3-domain-manager-probe/run-tests.sh -- offline harness for scripts/g3-domain-manager-probe.sh.
# Fakes `openstack` (via $OPENSTACK) and admin-openrc; NO cloud access, mutates nothing outside
# mktemp. Proves each exit path can FIRE (repo rule: a new assertion is decoration until it has
# a failing-direction fixture):
#   happy         -> 0   (all three groups hold; teardown log shows a domain delete)
#   pass-miss     -> 11  (manager cannot create a user -> override not effective)
#   escalation    -> 12  (manager grants an unmanaged role -> anti-escalation LEAK)
#   xdomain       -> 12  (manager reads another domain -> cross-domain LEAK)
#   no-openrc     -> 14  (precondition: admin-openrc absent)
#   auth-fail     -> 14  (precondition: admin-openrc does not authenticate)
#   missing-role  -> 14  (precondition: load-balancer_member role absent)
#   teardown-runs -> the EXIT trap tears the throwaway domain down even when the probe FAILS
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
SUT="$REPO/scripts/g3-domain-manager-probe.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
BIN="$TMP/bin"; mkdir -p "$BIN"

# ---- fake admin-openrc -----------------------------------------------------
RC="$TMP/admin-openrc"
cat > "$RC" <<'RCEOF'
export OS_AUTH_URL=https://10.12.4.50:5000/v3
export OS_CACERT=/dev/null
export OS_USERNAME=admin
export OS_PASSWORD=fake
export OS_PROJECT_NAME=admin
export OS_USER_DOMAIN_NAME=admin_domain
export OS_PROJECT_DOMAIN_NAME=admin_domain
export OS_IDENTITY_API_VERSION=3
RCEOF

# ---- fake openstack --------------------------------------------------------
# Behaviour keyed by $G3_FAKE_MODE. Logs mutating teardown calls to $G3_FAKE_LOG.
cat > "$BIN/openstack" <<'FB'
#!/usr/bin/env bash
mode="${G3_FAKE_MODE:-happy}"; log="${G3_FAKE_LOG:-/dev/null}"
args="$*"
has(){ case " $args " in *" $1 "*) return 0;; *) return 1;; esac; }
sub="$1 $2"
case "$sub" in
  "token issue")
     [ "$mode" = auth-fail ] && exit 1
     echo "2026-08-07T00:00:00+0000"; exit 0;;
  "role list")
     echo Admin; echo manager; echo member; echo reader; echo service
     [ "$mode" = missing-role ] || echo load-balancer_member
     echo load-balancer_admin; exit 0;;
  "domain create") echo "aaaa0000000000000000000000000001"; exit 0;;
  "user create")
     # admin creates the manager (has --password); manager creates g3-user (no --password)
     if has --password; then echo "bbbb0000000000000000000000000001"; exit 0; fi
     [ "$mode" = pass-miss ] && { echo "HTTP 403 Forbidden: create_user"; exit 1; }
     echo "cccc0000000000000000000000000001"; exit 0;;
  "project create") echo "dddd0000000000000000000000000001"; exit 0;;
  "role add")
     # setup grant (domain-scoped, no --project) always succeeds
     has --project || exit 0
     # manager granting member/load-balancer_member on a project: allowed
     if has member || has load-balancer_member; then exit 0; fi
     # manager granting the unmanaged role (manager): refused UNLESS we inject the leak
     [ "$mode" = escalation ] && exit 0
     exit 1;;
  "user list")
     # manager reading another domain: refused UNLESS we inject the leak
     if has admin_domain; then [ "$mode" = xdomain ] && { echo "eeee0000000000000000000000000001"; exit 0; }; exit 1; fi
     exit 0;;
  "domain list")
     echo "1111000000000000000000000000000a"; echo "2222000000000000000000000000000b"
     echo "3333000000000000000000000000000c"; echo "4444000000000000000000000000000d"; exit 0;;
  "project delete"|"user delete"|"domain set"|"domain delete")
     echo "$sub $args" >> "$log"; exit 0;;
  *) exit 0;;
esac
FB
chmod +x "$BIN/openstack"

# ---- runner ----------------------------------------------------------------
run(){ # <name> <expected-exit> <mode> [openrc-override]  -- accounts in the PARENT shell (no $(...))
  local name="$1" exp="$2" mode="$3" rc="${4:-$RC}" log="$TMP/td.$1.log" outf="$TMP/out.$1" code
  : > "$log"
  G3_FAKE_MODE="$mode" G3_FAKE_LOG="$log" OPENSTACK="$BIN/openstack" G3_OPENRC="$rc" \
    bash "$SUT" > "$outf" 2>&1; code=$?
  if [ "$code" -eq "$exp" ]; then PASS=$((PASS+1)); echo "PASS: $name (exit $code)"
  else FAIL=$((FAIL+1)); echo "FAIL: $name (got $code want $exp)"; sed 's/^/    /' "$outf"; fi
}

echo "=== g3-domain-manager-probe harness ==="
run happy        0  happy
run pass-miss    11 pass-miss
run escalation   12 escalation
run xdomain      12 xdomain
run auth-fail    14 auth-fail
run missing-role 14 missing-role
run no-openrc    14 happy "$TMP/does-not-exist"
HAPPY_OUT="$(cat "$TMP/out.happy")"

# teardown must have run in the happy path (domain delete logged)
if grep -q "domain delete" "$TMP/td.happy.log"; then PASS=$((PASS+1)); echo "PASS: teardown ran (domain delete logged)"
else FAIL=$((FAIL+1)); echo "FAIL: teardown did not log a domain delete"; fi
# teardown must ALSO run when the probe fails mid-way (escalation leak case)
if grep -q "domain delete" "$TMP/td.escalation.log"; then PASS=$((PASS+1)); echo "PASS: teardown ran on failure path"
else FAIL=$((FAIL+1)); echo "FAIL: teardown did not run on failure path"; fi
# happy output must actually show the three group headers (guards a silently-skipped group)
for grp in "PASS group" "DENY group" "UNAFF group"; do
  if printf '%s' "$HAPPY_OUT" | grep -q "$grp"; then PASS=$((PASS+1)); echo "PASS: happy ran $grp"
  else FAIL=$((FAIL+1)); echo "FAIL: happy missing $grp"; fi
done

echo "=== $PASS pass / $FAIL fail ==="
[ "$FAIL" -eq 0 ]
