#!/usr/bin/env bash
# tests/site-forward/run-tests.sh
#
# Harness for scripts/site-forward.sh (D-126). OFFLINE -- touches no SSH, no
# systemd, no network. Renders units and greps them; drives the error paths.
# It PINS the security-relevant shape (rootless, 127.0.0.1-bound, no baked
# key/secret, no inferred DC IP) so a careless edit reddens here, not in prod.
# ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S="$(cd "$HERE/../../scripts" && pwd)/site-forward.sh"
pass=0; fail=0
ok()  { pass=$((pass+1)); }
bad() { fail=$((fail+1)); echo "  FAIL: $1"; }

command -v bash >/dev/null 2>&1 || { echo "FAIL: bash required"; exit 1; }

# --- parse / help / list ---
bash -n "$S" 2>/dev/null && ok || bad "does not parse (bash -n)"
bash "$S" --help >/dev/null 2>&1 && ok || bad "--help -> nonzero"
bash "$S" >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "no-arg must be nonzero (prints usage)"
bash "$S" list 2>&1 | grep -q 'office1-netbox' && ok || bad "list lost office1-netbox"

R="$(bash "$S" render office1-netbox 2>&1)"
RK="$(bash "$S" render office1-netbox --key /tmp/fake.key 2>&1)"

# --- the forward shape (measured/as-built values) ---
printf '%s' "$R" | grep -q -- '-L 127.0.0.1:8000:10.10.1.10:8000' && ok || bad "wrong/missing local-forward spec"
printf '%s' "$R" | grep -q 'jessea123@10.10.0.20' && ok || bad "lost the voffice1 ssh dest"
printf '%s' "$R" | grep -q 'Description=D-126' && ok || bad "unit not tagged D-126"

# --- SECURITY: bind loopback only, never 0.0.0.0 ---
printf '%s' "$R" | grep -q '0.0.0.0' && bad "binds 0.0.0.0 -- must be 127.0.0.1 only" || ok
printf '%s' "$R" | grep -q '127.0.0.1:8000:' && ok || bad "local bind is not 127.0.0.1"

# --- SSH hardening flags present ---
for f in 'BatchMode=yes' 'ExitOnForwardFailure=yes' 'ServerAliveInterval=30' 'StrictHostKeyChecking=accept-new' '\-N' '\-T'; do
  printf '%s' "$R" | grep -q -- "$f" && ok || bad "lost ssh flag: $f"
done

# --- resilience: Restart + ExitOnForwardFailure ARE the resilience (no inert
#     network-online.target in a --user unit -- the user manager doesn't provide it) ---
printf '%s' "$R" | grep -q 'Restart=always' && ok || bad "lost Restart=always"
printf '%s' "$R" | grep -q 'WantedBy=default.target' && ok || bad "lost user-target install"
printf '%s' "$R" | grep -q 'network-online.target' && bad "readded inert network-online.target to a --user unit" || ok

# --- ssh path is MEASURED (command -v), not a baked literal (hard rule 2) ---
grep -q 'command -v ssh' "$S" && ok || bad "ssh path not measured at render time"
printf '%s' "$R" | grep -qE '^ExecStart=/[^ ]*/ssh ' && ok || bad "ExecStart ssh is not an absolute measured path"

# --- SECRETS BOUNDARY: no baked key path/material; key is an operator INPUT ---
printf '%s' "$R" | grep -q '<KEY-PATH>' && ok || bad "no-key render must show a <KEY-PATH> slot, not a real path"
printf '%s' "$RK" | grep -q -- '-i /tmp/fake.key' && ok || bad "--key not substituted into -i"
grep -qE '\-creds/|BEGIN [A-Z]* ?PRIVATE KEY|/root/netbox-secrets|api\.token' "$S" && bad "script bakes a key/secret path or key material" || ok
grep -q 'NEVER reads key' "$S" && ok || bad "lost the secrets-boundary contract comment"

# --- NO INFERRED VALUE: no unmeasured DC IP in an ACTIVE site row ---
# active rows are the declare -A body lines that are NOT commented; DC supernets
# (10.12., 172.31.) must not appear un-commented until measured.
active="$(sed -n '/declare -A SITES=(/,/^)/p' "$S" | grep -vE '^\s*#')"
printf '%s' "$active" | grep -qE '10\.12\.|172\.31\.' && bad "an inferred DC IP is in an ACTIVE site row -- measure first" || ok
printf '%s' "$active" | grep -q 'office1-netbox' && ok || bad "office1-netbox not an active site row"

# --- install refuses without a key (never guesses one) ---
bash "$S" install office1-netbox >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "install without --key must fail"
bash "$S" install office1-netbox --key /nonexistent/key >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "install with unreadable key must fail"

# --- unknown site fails loud on every subcommand ---
for c in render install status remove; do
  bash "$S" "$c" bogus --key /tmp/fake.key >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "$c bogus-site must fail"
done

# --- rootless: never invokes sudo/root; enable-linger only PRINTED, not run ---
grep -qE '^\s*sudo |systemctl (enable|start|--system)' "$S" && bad "invokes sudo/system systemctl -- must be rootless" || ok
grep -q 'systemctl --user' "$S" && ok || bad "does not use systemctl --user"

# --- L9 self-ref: self-locates, no hardcoded repo path ---
grep -q 'BASH_SOURCE' "$S" && ok || bad "does not self-locate via BASH_SOURCE"
grep -q '/home/jessea123/openstack' "$S" && bad "hardcodes an absolute repo path (L9)" || ok

# --- ASCII + LF ---
LC_ALL=C grep -qP '[^\x00-\x7F]' "$S" && bad "non-ASCII byte in script" || ok
grep -qU $'\r' "$S" && bad "CR byte (must be LF)" || ok

echo
total=$((pass+fail))
if [ "$fail" -eq 0 ]; then echo "site-forward: $pass/$total PASS"; exit 0; fi
echo "site-forward: $fail/$total FAIL"; exit 1
