#!/usr/bin/env bash
# tests/tenant-assert/run-tests.sh -- offline harness for scripts/tenant-assert.sh (H1).
# Fake openstack replays per-case fixtures keyed by FIXDIR; fake HOME provides a stub
# admin-openrc and (per case) a handover dir. The app-cred list fake emits a realistic
# deprecation notice ON STDERR to exercise the stdout-only JSON capture rule, and
# answers ONLY under owner context (OS_AUTH_TYPE=v3applicationcredential + non-empty
# cred id) -- the admin-side form is always 403 on the real cloud (measured 2026-07-06).
# Cases: happy PASS (incl. owner-context probe); escalation HOLD; keypair-trap HOLD;
# missing-user HOLD; bad handover mode HOLD; no-args precondition (exit 2); absent
# handover dir/appcred double-skip PASS; probe auth-fail HOLD; wrong cred name HOLD.
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0
BIN="$TMP/bin"; mkdir -p "$BIN"

# ---- stable fake ids (32-hex keystone shape) ----
ID_DOM="d0000000000000000000000000000001"
ID_MGR="a0000000000000000000000000000001"
ID_CLU="a0000000000000000000000000000002"
ID_SVC="a0000000000000000000000000000003"
ID_PRJ="b0000000000000000000000000000001"
R_MANAGER="c0000000000000000000000000000001"
R_MEMBER="c0000000000000000000000000000002"
R_LB="c0000000000000000000000000000003"
R_ADMIN="c0000000000000000000000000000004"

cat > "$BIN/openstack" <<FB
#!/usr/bin/env bash
case "\$*" in
  "role show manager -c id -f value")               echo "$R_MANAGER" ;;
  "role show member -c id -f value")                echo "$R_MEMBER" ;;
  "role show load-balancer_member -c id -f value")  echo "$R_LB" ;;
  "role show admin -c id -f value")                 echo "$R_ADMIN" ;;
  "domain show testco -c id -f value")              cat "\$FIXDIR/domain.txt" ;;
  "user show testco-domain-admin --domain $ID_DOM -c id -f value") echo "$ID_MGR" ;;
  "user show testco-cluster --domain $ID_DOM -c id -f value")      cat "\$FIXDIR/user-cluster.txt" ;;
  "user show testco-svc --domain $ID_DOM -c id -f value")          echo "$ID_SVC" ;;
  "project show testco-prod --domain $ID_DOM -c id -f value")      echo "$ID_PRJ" ;;
  "role assignment list --user $ID_MGR -f json")    cat "\$FIXDIR/assign-mgr.json" ;;
  "role assignment list --user $ID_CLU -f json")    cat "\$FIXDIR/assign-cluster.json" ;;
  "role assignment list --user $ID_SVC -f json")    cat "\$FIXDIR/assign-svc.json" ;;
  "keypair list --user $ID_CLU -f json")            cat "\$FIXDIR/keypairs.json" ;;
  "endpoint list --service identity --interface public -c URL -f value")
      echo "https://fake:5000/v3" ;;
  "application credential list -f json")
      # truthful mock: owner-context list only answers when the app-cred env
      # is actually set (the admin-side form is 403 on the real cloud)
      [ "\${OS_AUTH_TYPE:-}" = "v3applicationcredential" ] || { echo "fake: appcred list without owner context" >&2; exit 1; }
      [ -n "\${OS_APPLICATION_CREDENTIAL_ID:-}" ] || { echo "fake: empty OS_APPLICATION_CREDENTIAL_ID" >&2; exit 1; }
      echo "WARNING: this fake emits a deprecation-style notice on stderr" >&2
      cat "\$FIXDIR/appcreds.json" ;;
  *) echo "fake-openstack: unmatched: \$*" >&2; exit 1 ;;
esac
FB
chmod +x "$BIN/openstack"

mk_home(){ # mk_home <dir> [handover-mode: none|good|badmode]
  local h="$1" hv="${2:-good}"
  mkdir -p "$h"
  printf 'export OS_AUTH_URL=https://fake:5000/v3\n' > "$h/admin-openrc"
  if [ "$hv" != "none" ]; then
    local d="$h/tenant-testco"; mkdir -p "$d"
    for f in testco-domain-admin-cred.txt testco-cluster-cred.txt \
             testco-svc-cred.txt testco-key.pem; do
      printf 'x\n' > "$d/$f"; chmod 600 "$d/$f"
    done
    printf 'id="e0000000000000000000000000000001"\nname="testco-svc-cred"\nsecret="fake-secret-not-real"\n' \
      > "$d/testco-svc-appcred.txt"; chmod 600 "$d/testco-svc-appcred.txt"
    [ "$hv" = "badmode" ] && chmod 644 "$d/testco-svc-cred.txt"
  fi
}

mk_fix(){ # mk_fix <dir> <variant: happy|escalated|nokey|nouser>
  local d="$1" v="$2"; mkdir -p "$d"
  echo "$ID_DOM" > "$d/domain.txt"
  if [ "$v" = "nouser" ]; then echo "" > "$d/user-cluster.txt"; else echo "$ID_CLU" > "$d/user-cluster.txt"; fi
  cat > "$d/assign-mgr.json" <<J
[{"Role":"$R_MANAGER","User":"$ID_MGR","Group":"","Project":"","Domain":"$ID_DOM","System":"","Inherited":false}]
J
  cat > "$d/assign-cluster.json" <<J
[{"Role":"$R_MEMBER","User":"$ID_CLU","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false},
 {"Role":"$R_LB","User":"$ID_CLU","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false}]
J
  if [ "$v" = "escalated" ]; then
    cat > "$d/assign-svc.json" <<J
[{"Role":"$R_MEMBER","User":"$ID_SVC","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false},
 {"Role":"$R_LB","User":"$ID_SVC","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false},
 {"Role":"$R_ADMIN","User":"$ID_SVC","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false}]
J
  else
    cat > "$d/assign-svc.json" <<J
[{"Role":"$R_MEMBER","User":"$ID_SVC","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false},
 {"Role":"$R_LB","User":"$ID_SVC","Group":"","Project":"$ID_PRJ","Domain":"","System":"","Inherited":false}]
J
  fi
  if [ "$v" = "nokey" ]; then
    echo '[{"Name":"someone-elses-key","Fingerprint":"aa:bb","Type":"ssh"}]' > "$d/keypairs.json"
  else
    echo '[{"Name":"testco-key","Fingerprint":"aa:bb","Type":"ssh"}]' > "$d/keypairs.json"
  fi
  echo '[{"ID":"e0000000000000000000000000000001","Name":"testco-svc-cred","Expires At":null}]' > "$d/appcreds.json"
}

run_case(){ # run_case <home> <fixdir> [args...] -> OUT/RC globals
  OUT=$(env HOME="$1" FIXDIR="$2" PATH="$BIN:$PATH" \
        bash "$REPO/scripts/tenant-assert.sh" "${@:3}" 2>&1); RC=$?
}
check(){ # check <want_rc> <regex> <label>
  if [ "$RC" = "$1" ] && grep -qE "$2" <<<"$OUT"; then echo "  PASS  $3"; PASS=$((PASS+1))
  else echo "  FAIL  $3 (rc=$RC want=$1)"; echo "$OUT" | tail -6 | sed 's/^/        /'; FAIL=$((FAIL+1)); fi
}

# T1 happy path -> PASS, exit 0 (also proves stderr noise on appcred list is not
# merged, and that the owner-context probe path is the one exercised)
mk_home "$TMP/h1" good; mk_fix "$TMP/f1" happy
run_case "$TMP/h1" "$TMP/f1" testco
check 0 'TENANT-ASSERT testco: PASS' "T1 happy topology passes"
check 0 'owner-context auth OK' "T1b app cred verified via owner-context probe"

# T2 escalation: svc holds admin -> HOLD, incident wording
mk_home "$TMP/h2" good; mk_fix "$TMP/f2" escalated
run_case "$TMP/h2" "$TMP/f2" testco
check 1 'HOLDS ADMIN -- escalation' "T2 admin-on-svc detected as escalation"

# T3 keypair trap: key not owned by -cluster -> HOLD, trap wording
mk_home "$TMP/h3" good; mk_fix "$TMP/f3" nokey
run_case "$TMP/h3" "$TMP/f3" testco
check 1 'keypair trap' "T3 wrong keypair owner detected"

# T4 missing -cluster user -> HOLD
mk_home "$TMP/h4" good; mk_fix "$TMP/f4" nouser
run_case "$TMP/h4" "$TMP/f4" testco
check 1 'user testco-cluster missing' "T4 missing user detected"

# T5 no args -> precondition exit 2
mk_home "$TMP/h5" good; mk_fix "$TMP/f5" happy
run_case "$TMP/h5" "$TMP/f5"
check 2 'usage:' "T5 missing client arg is a precondition (2)"

# T6 handover file with mode 644 -> HOLD
mk_home "$TMP/h6" badmode; mk_fix "$TMP/f6" happy
run_case "$TMP/h6" "$TMP/f6" testco
check 1 'mode 644 \(want 600\)' "T6 bad handover mode detected"

# T7 no handover dir -> both skips (files + owner-context probe), still PASS
mk_home "$TMP/h7" none; mk_fix "$TMP/f7" happy
run_case "$TMP/h7" "$TMP/f7" testco
check 0 'skip: no local handover dir' "T7 absent handover dir skips without lowering verdict"
check 0 'skip: no local appcred file' "T7b absent appcred file skips probe without lowering verdict"

# T8 owner-context probe auth failure (non-JSON + stderr) -> HOLD with stderr shown
mk_home "$TMP/h8" good; mk_fix "$TMP/f8" happy
printf 'The request you have made requires authentication. (HTTP 401)\n' > "$TMP/f8/appcreds.json"
run_case "$TMP/h8" "$TMP/f8" testco
check 1 'owner-context app cred probe failed' "T8 probe auth failure is a HOLD"

# T9 wrong app cred name in owner-context list -> HOLD
mk_home "$TMP/h9" good; mk_fix "$TMP/f9" happy
echo '[{"ID":"e0000000000000000000000000000002","Name":"not-the-svc-cred","Expires At":null}]' > "$TMP/f9/appcreds.json"
run_case "$TMP/h9" "$TMP/f9" testco
check 1 'missing on testco-svc \(owner-context list\)' "T9 wrong cred name detected"

echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1
