#!/usr/bin/env bash
# tests/sandbox-fidelity-check/run-tests.sh
#
# Harness for netbox/sandbox-fidelity-check.py. Offline: builds synthetic upstream
# / sandbox JSON dumps and asserts the checker's verdict. Touches no NetBox.
#
# WHY THIS HARNESS EXISTS AT ALL (2026-07-14): the checker SHIPPED WITH NO HARNESS,
# and it was the script we were citing as PROOF the sandbox was a faithful replica.
# It could return a FALSE GREEN: `unexpected = extra - expected` proves the delta is
# a SUBSET of the plan but NEVER proved the plan was CARRIED OUT. With ZERO of the
# 36 DC prefixes created, extra={} -> unexpected={} -> it printed "Nothing lost,
# nothing stray" and exited 0. EXPECTED_NEW was an upper bound masquerading as an
# assertion. T3 is that exact scenario, now caught.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO="$(cd "$HERE/../.." && pwd)"
CHECK="$REPO/netbox/sandbox-fidelity-check.py"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
P=0; F=0
ok() { echo "  [PASS] $1"; P=$((P+1)); }
no() { echo "  [FAIL] $1"; F=$((F+1)); }

[ -f "$CHECK" ] || { echo "FAIL: $CHECK not found"; exit 1; }

# --- fixtures -----------------------------------------------------------------
# A minimal upstream draft: one shared prefix. The sandbox must reproduce it
# EXACTLY, and may add only what EXPECTED_NEW allows.
mk() { # mk <file> <json>
  printf '%s' "$2" > "$TMP/$1"
}

SHARED='{"prefix":"10.99.0.0/24","status":"active","role":"storage","scope_type":"dcim.site","scope_id":1,"description":"shared","is_pool":false,"mark_utilized":false}'
# an EXPECTED delta prefix (in EXPECTED_NEW via _dc_prefixes(): VR1 DC0 inherited v4)
DELTA_OK='{"prefix":"10.12.4.0/22","status":"active","role":"provider-public","scope_type":"dcim.site","scope_id":8,"description":"VR1 DC0 provider-public","is_pool":false,"mark_utilized":false}'
# the SAME expected prefix but UNSCOPED -- the 17-prefix-scope-drop bug's shape
DELTA_NOSCOPE='{"prefix":"10.12.4.0/22","status":"active","role":"provider-public","scope_type":null,"scope_id":null,"description":"VR1 DC0 provider-public","is_pool":false,"mark_utilized":false}'
# a stray object nobody planned
STRAY='{"prefix":"192.0.2.0/24","status":"active","role":"storage","scope_type":"dcim.site","scope_id":1,"description":"stray","is_pool":false,"mark_utilized":false}'

up_json() { printf '{"ipam/prefixes":[%s]}' "$SHARED"; }
run() {  # run <upstream-json> <sandbox-json> ; echoes rc
  printf '%s' "$1" > "$TMP/u.json"; printf '%s' "$2" > "$TMP/s.json"
  python3 "$CHECK" --upstream "$TMP/u.json" --sandbox "$TMP/s.json" >"$TMP/out" 2>&1
  echo $?
}

echo "=== sandbox-fidelity-check ==="

# T1: THE POSITIVE PATH. A faithful replica carrying the COMPLETE planned delta
#     must pass. The expected set is DERIVED FROM THE CHECKER ITSELF (its own
#     EXPECTED_NEW), never re-typed here -- a harness that hardcodes its own copy of
#     the expectation cannot detect the two drifting apart.
python3 - "$CHECK" "$TMP" <<'PY'
import json, re, sys
src = open(sys.argv[1]).read()
tmp = sys.argv[2]
# exec only the declarative prologue (up to the comparison loop), with the two
# argparse-required inputs stubbed, to harvest EXPECTED_NEW without running the diff.
head = src.split("bad=0")[0]
head = re.sub(r"^ap\s*=.*$|^ap\.add_argument.*$|^args\s*=.*$|^up\s*=.*$|^sb\s*=.*$",
              "", head, flags=re.M)
ns = {}
exec(compile(head, "checker-prologue", "exec"), ns)
SHARED = {"prefix":"10.99.0.0/24","status":"active","role":"storage",
          "scope_type":"dcim.site","scope_id":1,"description":"shared",
          "is_pool":False,"mark_utilized":False}
sandbox = {}
upstream = {}
for ep, key in ns["KEY"].items():
    upstream[ep] = [SHARED] if ep == "ipam/prefixes" else []
    rows = [SHARED] if ep == "ipam/prefixes" else []
    for v in sorted(ns["EXPECTED_NEW"].get(ep, set())):
        rec = {key: v}
        if ep == "ipam/prefixes":
            rec.update({"status":"active","role":"provider-public",
                        "scope_type":"dcim.site","scope_id":8,
                        "description":"planned","is_pool":False,"mark_utilized":False})
        rows.append(rec)
    sandbox[ep] = rows
json.dump(upstream, open(f"{tmp}/u1.json","w"))
json.dump(sandbox,  open(f"{tmp}/s1.json","w"))
PY
python3 "$CHECK" --upstream "$TMP/u1.json" --sandbox "$TMP/s1.json" >"$TMP/out" 2>&1; rc=$?
[ "$rc" = 0 ] && ok "T1 faithful replica WITH the complete planned delta -> exit 0" \
              || { no "T1 the complete, correct delta should PASS (rc=$rc)"; sed 's/^/       /' "$TMP/out" | tail -6; }

# T2: a shared object with a MANGLED field -> FAIL (the original 17-prefix bug,
#     as it appeared on SHARED objects; the check already caught this)
MANGLED='{"prefix":"10.99.0.0/24","status":"active","role":"storage","scope_type":null,"scope_id":null,"description":"shared","is_pool":false,"mark_utilized":false}'
rc=$(run "$(up_json)" "$(printf '{"ipam/prefixes":[%s]}' "$MANGLED")")
[ "$rc" = 1 ] && grep -q "FIELD DRIFT" "$TMP/out" && ok "T2 dropped scope on a SHARED object -> FIELD DRIFT, exit 1" \
              || { no "T2 should fail on shared-object field drift (rc=$rc)"; sed 's/^/       /' "$TMP/out"; }

# T3: THE FALSE GREEN. The planned delta was NOT applied (zero DC prefixes created).
#     A subset-only check calls this clean. It must FAIL.
rc=$(run "$(up_json)" "$(printf '{"ipam/prefixes":[%s]}' "$SHARED")")
# ^ T1 is the no-delta-expected case; to exercise EXPECTED-BUT-ABSENT we need the
#   checker's own EXPECTED_NEW to be non-empty, which it always is. T1 above already
#   proves it: with an empty sandbox delta, the 36+ EXPECTED_NEW prefixes are absent.
grep -q "EXPECTED-BUT-ABSENT" "$TMP/out" \
  && ok "T3 FALSE GREEN CAUGHT: the planned delta was never applied -> EXPECTED-BUT-ABSENT" \
  || no "T3 the false green is BACK -- a subset-only check passes an unapplied plan"
[ "$rc" = 1 ] && ok "T3 an unapplied plan exits 1 (was exit 0 -- 'Nothing lost, nothing stray')" \
              || no "T3 unapplied plan still exits $rc"

# T4: an UNEXPECTED stray write -> FAIL
rc=$(run "$(up_json)" "$(printf '{"ipam/prefixes":[%s,%s]}' "$SHARED" "$STRAY")")
[ "$rc" = 1 ] && grep -q "UNEXPECTED EXTRA" "$TMP/out" && ok "T4 a stray write -> UNEXPECTED EXTRA, exit 1" \
              || { no "T4 should fail on a stray write (rc=$rc)"; sed 's/^/       /' "$TMP/out"; }

# T5: an object MISSING from the sandbox -> FAIL
rc=$(run "$(up_json)" '{"ipam/prefixes":[]}')
[ "$rc" = 1 ] && grep -q "MISSING FROM SANDBOX" "$TMP/out" && ok "T5 a lost upstream object -> MISSING, exit 1" \
              || { no "T5 should fail on a lost object (rc=$rc)"; sed 's/^/       /' "$TMP/out"; }

# T6: DELTA FIELD-CORRECTNESS. An EXPECTED new prefix created with NO SCOPE must
#     fail. The shared-object drift loop CANNOT see this (the object exists only in
#     the sandbox) -- it is precisely the 17-prefix scope-drop bug, relocated into
#     the delta where the old check was blind to it.
rc=$(run "$(up_json)" "$(printf '{"ipam/prefixes":[%s,%s]}' "$SHARED" "$DELTA_NOSCOPE")")
grep -q "has NO SCOPE" "$TMP/out" && ok "T6 an expected DELTA prefix with no scope -> caught" \
                                  || { no "T6 delta field-correctness is blind again"; sed 's/^/       /' "$TMP/out"; }
[ "$rc" = 1 ] && ok "T6 an unscoped delta prefix exits 1" || no "T6 unscoped delta exits $rc"

# T7: a correctly-formed expected delta prefix is NOT flagged as unscoped/roleless
rc=$(run "$(up_json)" "$(printf '{"ipam/prefixes":[%s,%s]}' "$SHARED" "$DELTA_OK")")
grep -q "has NO SCOPE" "$TMP/out" && no "T7 a well-formed delta prefix was wrongly flagged" \
                                  || ok "T7 a well-formed delta prefix is NOT flagged (guard is not a blanket refusal)"

# T8: read-only -- no write verbs anywhere in the checker
grep -qE '\.(create|update|delete|save)\(' "$CHECK" \
  && no "T8 the checker contains a WRITE verb -- it must be read-only" \
  || ok "T8 read-only: no create/update/delete/save in the checker"

echo
if [ "$F" = 0 ]; then echo "sandbox-fidelity-check: $P/$P PASS"; exit 0; fi
echo "sandbox-fidelity-check: FAILURES: $F (passed $P)"; exit 1
