#!/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.
# Cases: happy PASS; escalation HOLD; keypair-trap HOLD; missing-user HOLD;
# bad handover mode HOLD; no-args precondition (exit 2).
# 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" ;;
"application credential list --user $ID_SVC -f json")
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-svc-appcred.txt testco-key.pem; do
printf 'x\n' > "$d/$f"; chmod 600 "$d/$f"
done
[ "$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)
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"
# 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 -> skip note, 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"
echo; echo "RESULT: PASS=$PASS FAIL=$FAIL"
[[ "$FAIL" -eq 0 ]] && { echo "ALL PASS"; exit 0; } || exit 1