#!/usr/bin/env bash
# tests/d063-apply/run-tests.sh -- mock harness for scripts/d063-apply.sh
# Branches: audit=0 (zero mutations) | apply-happy=0 (adds strictly BEFORE removes; 2-unit
# derivation) | duplicate-add=0 | confirm-mismatch=24 | derive-fail=21 (zero mutations) |
# readback-miss=27 (zero removes) | missing-sg=20
set -u
SD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"; SCRIPT="$SD/../../scripts/d063-apply.sh"
W=$(mktemp -d); trap 'rm -rf "$W"' EXIT
mkdir -p "$W/bin"; touch "$W/admin-openrc"
cat > "$W/ptyrun.py" <<'PTY'
import pty, os, sys
cmd = sys.argv[1:-1]; feed = sys.argv[-1]
pid, fd = pty.fork()
if pid == 0:
    os.execvp(cmd[0], cmd)
os.write(fd, (feed + "\n").encode())
out = b""
while True:
    try:
        d = os.read(fd, 4096)
    except OSError:
        break
    if not d:
        break
    out += d
_, st = os.waitpid(pid, 0)
sys.stdout.write(out.decode(errors="replace"))
sys.exit(os.waitstatus_to_exitcode(st))
PTY
cat > "$W/bin/juju" <<'JM'
#!/usr/bin/env bash
S="${MOCK_SCEN:-happy}"
case "$*" in
  *"status magnum"*) echo '{"applications":{"magnum":{"units":{"magnum/0":{},"magnum/1":{}}}}}';;
  *"ssh"*magnum/0*)
    if [ "$S" = derivefail ]; then echo "RTNETLINK answers: no route"; else echo "10.12.7.222 dev eth0 src 10.12.4.154 uid 1000 \\    cache"; fi;;
  *"ssh"*magnum/1*) echo "10.12.7.222 dev eth0 src 10.12.4.155 uid 1000 \\    cache";;
esac
JM
cat > "$W/bin/openstack" <<'OM'
#!/usr/bin/env bash
S="${MOCK_SCEN:-happy}"; ST="${MOCK_STATE:?}"; mkdir -p "$ST"
case "$*" in
  "project show"*) python3 -c 'print("c"*32)';;
  "security group list"*)
    if [ "$S" = missingsg ]; then :; else printf '%s capi-mgmt-sg\n' "$(python3 -c "import uuid;print(uuid.UUID(bytes=b's'*16))")"; fi;;
  "security group rule list"*)
    echo "The --long option has been deprecated and is no longer needed" >&2  # faithful: real CLI warns on stderr
    python3 - "$ST" <<'PY'
import json,sys,os,glob
st=sys.argv[1]; scen=os.environ.get("MOCK_SCEN","happy")
rules=[{"ID":"wide22","Direction":"ingress","IP Protocol":"tcp","Port Range":"22:22","IP Range":"0.0.0.0/0"},
       {"ID":"wide6443","Direction":"ingress","IP Protocol":"tcp","Port Range":"6443:6443","IP Range":"0.0.0.0/0"},
       {"ID":"egr","Direction":"egress","IP Protocol":None,"Port Range":"","IP Range":""}]
if scen=="dupadd":  # truthful 409 world: the duplicate rule genuinely pre-exists
    rules.append({"ID":"pre154","Direction":"ingress","IP Protocol":"tcp","Port Range":"6443:6443","IP Range":"10.12.4.154/32"})
adds=sorted(glob.glob(st+"/add-*"))
if scen=="readbackmiss" and adds: adds=adds[:-1]
for i,a in enumerate(adds):
    port,src=open(a).read().split()
    rules.append({"ID":"new%d"%i,"Direction":"ingress","IP Protocol":"tcp","Port Range":port+":"+port,"IP Range":src})
deleted=set(open(st+"/deleted").read().split()) if os.path.exists(st+"/deleted") else set()
print(json.dumps([r for r in rules if r["ID"] not in deleted]))
PY
    ;;
  "security group rule create"*)
    PORT=""; SRC=""; prev=""
    for a in "$@"; do [ "$prev" = "--dst-port" ] && PORT="$a"; [ "$prev" = "--remote-ip" ] && SRC="$a"; prev="$a"; done
    if [ "$S" = dupadd ] && [ "$SRC" = "10.12.4.154/32" ] && [ ! -f "$ST/dupfired" ]; then
      touch "$ST/dupfired"; echo "SecurityGroupRuleExists: Security group rule already exists. (HTTP 409)"; exit 1; fi
    N=$(ls "$ST"/add-* 2>/dev/null | wc -l); printf '%s %s\n' "$PORT" "$SRC" > "$ST/add-$(printf '%03d' "$N")"
    echo "MUT create $PORT $SRC" >> "$ST/mutlog";;
  "security group rule delete"*)
    echo "${@: -1}" >> "$ST/deleted"; echo "MUT delete ${@: -1}" >> "$ST/mutlog";;
esac
OM
chmod +x "$W/bin/"*
P=0; F=0
chk(){ if [ "$2" = "$3" ]; then echo "PASS: $1 (exit $2)"; P=$((P+1)); else echo "FAIL: $1 (exit $2, want $3)"; F=$((F+1)); fi; }
muts(){ cat "$1/mutlog" 2>/dev/null | wc -l; }
export HOME="$W" PATH="$W/bin:$PATH"
ST="$W/s1"; MOCK_STATE="$ST" bash "$SCRIPT" --audit >/dev/null 2>&1; RC=$?
[ "$RC" = 0 ] && [ "$(muts "$ST")" = 0 ] && { echo "PASS: audit (exit 0, 0 mutations)"; P=$((P+1)); } || { echo "FAIL: audit (exit $RC, muts $(muts "$ST"))"; F=$((F+1)); }
ST="$W/s2"; MOCK_STATE="$ST" python3 "$W/ptyrun.py" bash "$SCRIPT" --apply d063 >"$W/happy.out" 2>&1; RC=$?
LASTC=$(grep -n 'MUT create' "$ST/mutlog" | tail -1 | cut -d: -f1); FIRSTD=$(grep -n 'MUT delete' "$ST/mutlog" | head -1 | cut -d: -f1)
if [ "$RC" = 0 ] && [ -n "$FIRSTD" ] && [ "$LASTC" -lt "$FIRSTD" ] && grep -q '10.12.4.155/32' "$ST/mutlog"; then
  echo "PASS: apply-happy (exit 0, adds before removes, both units derived)"; P=$((P+1))
else echo "FAIL: apply-happy (exit $RC, lastC=$LASTC firstD=$FIRSTD)"; F=$((F+1)); fi
ST="$W/s3"; MOCK_STATE="$ST" MOCK_SCEN=dupadd python3 "$W/ptyrun.py" bash "$SCRIPT" --apply d063 >/dev/null 2>&1; chk duplicate-add $? 0
ST="$W/s4"; MOCK_STATE="$ST" python3 "$W/ptyrun.py" bash "$SCRIPT" --apply WRONG >/dev/null 2>&1; RC=$?
[ "$RC" = 24 ] && [ "$(muts "$ST")" = 0 ] && { echo "PASS: confirm-mismatch (exit 24, 0 mutations)"; P=$((P+1)); } || { echo "FAIL: confirm-mismatch (exit $RC)"; F=$((F+1)); }
ST="$W/s5"; MOCK_STATE="$ST" MOCK_SCEN=derivefail python3 "$W/ptyrun.py" bash "$SCRIPT" --apply d063 >/dev/null 2>&1; RC=$?
[ "$RC" = 21 ] && [ "$(muts "$ST")" = 0 ] && { echo "PASS: derive-fail (exit 21, 0 mutations)"; P=$((P+1)); } || { echo "FAIL: derive-fail (exit $RC, muts $(muts "$ST"))"; F=$((F+1)); }
ST="$W/s6"; MOCK_STATE="$ST" MOCK_SCEN=readbackmiss python3 "$W/ptyrun.py" bash "$SCRIPT" --apply d063 >/dev/null 2>&1; RC=$?
DELS=$(grep -c 'MUT delete' "$ST/mutlog" 2>/dev/null || true)
[ "$RC" = 27 ] && [ "${DELS:-0}" = 0 ] && { echo "PASS: readback-miss (exit 27, 0 removes)"; P=$((P+1)); } || { echo "FAIL: readback-miss (exit $RC, dels $DELS)"; F=$((F+1)); }
ST="$W/s7"; MOCK_STATE="$ST" MOCK_SCEN=missingsg bash "$SCRIPT" --audit >/dev/null 2>&1; chk missing-sg $? 20
echo; [ "$F" = 0 ] && { echo "ALL PASS ($P/7)"; exit 0; } || { echo "FAILURES: $F"; exit 1; }
