#!/usr/bin/env bash
# tests/roles-aggregates-import/run-tests.sh
#
# Harness for netbox/roles-aggregates-import.py. OFFLINE -- touches no NetBox.
#
# THIS SCRIPT SHIPPED WITH NO HARNESS, and that is not incidental to the bug it
# carried. It was only ever exercised against an EMPTY NetBox. The moment it met
# one holding the real upstream draft (2026-07-13) it created 4 roles, hit a 400
# on the 5th, and DIED -- leaving the IPAM apex half-populated with no RIRs and
# no aggregates, and no rollback.
#
# Two properties are pinned here, both learned from that failure:
# 1. NetBox enforces UNIQUE ROLE NAMES, not just unique slugs. The legacy `repl`
# role already holds the name "Replication", so the six-plane role (slug
# `replication`) MUST NOT be named "Replication".
# 2. The importer must PREFLIGHT every name/slug before writing ANYTHING. An
# IPAM importer that can die partway corrupts the thing it exists to populate.
# ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
S="$(cd "$HERE/../../netbox" && pwd)/roles-aggregates-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"
# This script needs pynetbox (unlike the stdlib tools). On a host without it, it
# must say so CLEANLY rather than traceback -- so accept either.
out="$(python3 "$S" --help 2>&1)"
if printf '%s' "$out" | grep -q "pynetbox not installed"; then ok # clean, actionable
elif printf '%s' "$out" | grep -q -- "--commit"; then ok # real help text
else bad "--help neither printed help nor a clean 'pynetbox not installed' message"; fi
# DRY BY DEFAULT -- this one already was, and must stay so.
grep -q -- '--commit' "$S" && ok || bad "no --commit flag"
grep -q 'default: preview only, no writes' "$S" && ok || bad "no longer documents preview-by-default"
# 1. THE NAME COLLISION. Naming the six-plane role "Replication" 400s against any
# NetBox carrying the draft, because legacy slug `repl` holds that name.
grep -q '"Replication Plane"' "$S" && ok \
|| bad "the six-plane replication role is not named 'Replication Plane' -- it will 400 against the real draft (legacy slug 'repl' holds the name 'Replication')"
grep -qE '\("replication", "Replication",' "$S" \
&& bad "the six-plane role is named exactly 'Replication' -- COLLIDES with legacy slug 'repl'" || ok
# the slug must NOT drift: lib-net.sh SPACES6 and dc-dc-prefixes-import.py look it up.
grep -q '"replication", "Replication Plane"' "$S" && ok \
|| bad "the replication slug drifted -- lib-net.sh SPACES6 and the prefix importer look up slug 'replication'"
# 2. PREFLIGHT. Nothing may be written until the whole plan is known viable.
grep -q 'PREFLIGHT FAILED -- nothing was written' "$S" && ok \
|| bad "the preflight is gone -- a name collision can again leave the apex HALF-WRITTEN"
grep -q 'NetBox enforces UNIQUE NAMES, not just slugs' "$S" && ok \
|| bad "the preflight no longer checks NAME collisions (only slugs) -- that IS the bug"
# the preflight must run BEFORE the first create
pf=$(grep -n 'PREFLIGHT' "$S" | head -1 | cut -d: -f1)
cr=$(grep -n 'nb.ipam.roles.create' "$S" | head -1 | cut -d: -f1)
[ -n "$pf" ] && [ -n "$cr" ] && [ "$pf" -lt "$cr" ] && ok \
|| bad "preflight does not run BEFORE the first create -- half-written apex is back"
# 3. the six-plane slugs must match lib-net.sh SPACES6 exactly.
for slug in provider-public metal-admin metal-internal data-tenant replication; do
grep -q "\"$slug\"" "$S" && ok || bad "six-plane role slug '$slug' is gone"
done
# storage is deliberately NOT created -- it pre-exists upstream and is reused.
grep -q 'already exists -- intentionally omitted' "$S" && ok \
|| bad "the note that 'storage' is reused (not created) is gone"
echo
total=$((pass+fail))
if [ "$fail" -eq 0 ]; then echo "roles-aggregates-import: $pass/$total PASS"; exit 0; fi
echo "roles-aggregates-import: $fail/$total FAIL"; exit 1