#!/usr/bin/env bash
# tests/dc-rack-mgmt-import/run-tests.sh
#
# Harness for netbox/dc-rack-mgmt-import.py. OFFLINE -- touches no NetBox.
#
# Two layers, both required (mirrors dc-edge-wan-import + d120-compose-bands):
#   1. STATIC greps that PIN the D-124 scheme values + the guards every sandbox-loop
#      writer must have (dry-by-default, upstream-write gated IN CODE, whole-plan
#      preflight, NO invented literal). A changed constant a behavioral test would ALSO
#      change is caught here.
#   2. test_logic.py drives the REAL main()/preflight against an in-memory fake NB
#      (injected at the get_nb() seam) -- half-write guard, role/scope binding, band
#      arithmetic, idempotent-skip, dry-run-writes-nothing. In-process on purpose: no
#      port / readiness / cleanup flakiness to redden the shared gauntlet.
# ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S="$(cd "$HERE/../../netbox" && pwd)/dc-rack-mgmt-import.py"
pass=0; fail=0
ok()  { pass=$((pass+1)); }
bad() { fail=$((fail+1)); echo "  FAIL: $1"; }

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

python3 -c "import ast;ast.parse(open('$S').read())" 2>/dev/null && ok || bad "does not parse"
python3 "$S" --help >/dev/null 2>&1 && ok || bad "--help -> 0"

# no env -> must fail loud, not guess a target
NETBOX_URL= NETBOX_TOKEN= python3 "$S" --transit-cidr 172.31.0.0/30 --rack-ip 10.12.8.5 \
  >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "missing NETBOX_URL/TOKEN must fail"

# NO INVENTED LITERAL -- both inputs are REQUIRED (no default CIDR/IP baked in)
NETBOX_URL=http://10.10.1.10:8000 NETBOX_TOKEN=x python3 "$S" --rack-ip 10.12.8.5 \
  >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "missing --transit-cidr must fail (no invented CIDR)"
NETBOX_URL=http://10.10.1.10:8000 NETBOX_TOKEN=x python3 "$S" --transit-cidr 172.31.0.0/30 \
  >/dev/null 2>&1; [ $? -ne 0 ] && ok || bad "missing --rack-ip must fail (no invented IP)"
grep -q 'default=os.environ.get("TRANSIT_CIDR")' "$S" && ok || bad "lost the TRANSIT_CIDR env fallback"
grep -q 'default=os.environ.get("RACK_IP")' "$S" && ok || bad "lost the RACK_IP env fallback"
# guard against a baked-in transit /30 or /31 literal masquerading as a default
grep -qE '=\s*"10\.[0-9]+\.[0-9]+\.[0-9]+/3[01]"' "$S" && bad "a /30 or /31 literal is baked in -- must be an INPUT" || ok

# DRY BY DEFAULT
grep -q '"--commit", action="store_true"' "$S" && ok || bad "no --commit flag -- must be dry by default"
grep -q 'DRY RUN -- nothing will be written' "$S" && ok || bad "does not announce its dry run"

# UPSTREAM WRITE GATED IN CODE (not by discipline)
grep -q 'yes-write-upstream' "$S" && ok || bad "lost the --yes-write-upstream gate"
grep -q 'SANDBOX_HOSTS' "$S" && ok || bad "lost the SANDBOX_HOSTS gate"
out="$(NETBOX_URL=https://netbox.baldurkeep.com NETBOX_TOKEN=x python3 "$S" \
  --transit-cidr 172.31.0.0/30 --rack-ip 10.12.8.5 --commit 2>&1)"
printf '%s' "$out" | grep -q "REFUSING to --commit" && ok \
  || bad "did NOT refuse a --commit to a non-sandbox host (it would have written to production)"

# WAF: UA-aware or every upstream call 403s (looks like auth failure)
grep -q 'User-Agent' "$S" && grep -q 'curl/8.5.0' "$S" && ok || bad "lost the WAF-safe User-Agent"

# WHOLE-PLAN PREFLIGHT -- validate role/container/transit/band/site BEFORE any create
grep -q "outside the container" "$S" && ok || bad "lost the transit-in-container preflight"
grep -q "container .* absent" "$S" && ok || bad "lost the container-exists preflight"
grep -q "role '.*' absent" "$S" && ok || bad "lost the role-exists preflight"
grep -q "site '.*' absent" "$S" && ok || bad "lost the site-resolves preflight"
grep -q "GATEWAY" "$S" && ok || bad "lost the .1-gateway rejection"
grep -q "static band" "$S" && ok || bad "lost the D-120 static-band check"
grep -q "outside metal-admin" "$S" && ok || bad "lost the metal-admin containment check"
grep -q "must be a /30 or /31" "$S" && ok || bad "lost the /30-or-/31 shape check"

# THE D-124 SCHEME VALUES -- role, container, site scope, metal-admin band, rack dns.
grep -qF 'ROLE_SLUG = "transit"' "$S" && ok || bad "transit role slug changed"
grep -qF 'CONTAINER = "172.31.0.0/24"' "$S" && ok || bad "container is not 172.31.0.0/24 (D-124 dedicated transit supernet, operator-pinned)"
grep -qF 'SITE_SLUG = "vr1-dc0"' "$S" && ok || bad "transit site scope is not vr1-dc0"
grep -qF 'METAL_ADMIN = "10.12.8.0/22"' "$S" && ok || bad "metal-admin is not 10.12.8.0/22"
grep -qF 'RACK_DNS = "vvr1-dc0"' "$S" && ok || bad "rack dns name is not vvr1-dc0"
grep -q '"scope_type": "dcim.site"' "$S" && ok || bad "the transit prefix is not dcim.site-scoped"

# This tool must NOT create the role/container/site -- they are preconditions.
grep -q 'create("ipam/roles"\|create("dcim/sites"' "$S" \
  && bad "this tool CREATES a role/site -- those are preconditions" || ok

# ---- behavioral layer: drive the real main() against the injected fake NB ----
echo "  -- behavioral (test_logic.py) --"
tl_out="$(python3 "$HERE/test_logic.py" 2>&1)"; tl_rc=$?
printf '%s\n' "$tl_out" | grep -E '^(FAIL|PASS: |test_logic:)' | sed 's/^/    /'
tl_n="$(printf '%s\n' "$tl_out" | sed -n 's/^test_logic: ALL PASS (\([0-9]*\) checks)$/\1/p')"
if [ "$tl_rc" -eq 0 ] && [ -n "$tl_n" ]; then
  pass=$((pass + tl_n))
else
  bad "test_logic.py behavioral suite FAILED (rc=$tl_rc)"
fi

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