#!/usr/bin/env bash
# tests/dc-node-carve/run-tests.sh -- unit tests for scripts/dc-node-carve.sh
# (2026-07-30).
#
# A stub `maas` on PATH serves JSON fixtures, so no live region is touched.
# STUB_STATE selects a fixture set:
#   raw         the MEASURED starting state of every migrated dc0 machine:
#               enp1s0 with an AUTO link on metal-admin, enp2s0..enp6s0 each on a
#               FRESH auto-created fabric with `link_up` and no subnet.
#   carved      the intended end state: 9 role nodes on all six planes with an
#               OVS br-ex, the juju controller on two planes raw.
#   wrongip     carved, except one node's storage leg holds the WRONG address on
#               the RIGHT subnet. This MUST fail, never skip.
#   patternb    carved, except a role node's provider static sits on the raw
#               enp2s0 with no br-ex -- the D-057 Pattern B shape that darkened
#               the floating-IP plane.
#   memberl3    carved, except the br-ex MEMBER NIC still holds an L3 link, so
#               the host has two addresses on the provider plane.
#   brexip      carved, and br-ex is a correctly-parented OVS bridge, but its
#               static is the WRONG address. ADDED after a mutation pass found
#               the br-ex static compare could be neutered with the suite still
#               green: `patternb` was being caught by the type/parent assertions
#               instead, so the address compare itself was never exercised.
#   jujucarved  carved, except the juju controller's enp3s0 has been "helpfully"
#               carved onto a plane it is deliberately meant to leave alone.
#   jujubrex    carved, except the juju controller has grown a br-ex.
#   nogw        carved, except provider-public lost its gateway_ip -- the model
#               fact the node default route depends on.
#
# THE POINT OF THIS HARNESS is that `check` must FAIL on every one of those
# partial or wrong states. The repo's receipts are three-for-three that freshly
# written assertions are decoration until each is proven able to fail, so every
# assertion added here has a failing-direction fixture above.
#
# It also pins the REGION GATE (no default profile; refuse when the profile does
# not resolve to the expected rack) and the SITE refusal, which is the mirror
# image of carve-host-interfaces.sh refusing vr1-*.
set -uo pipefail
SD="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT="$(cd "$SD/../.." && pwd)"
SCRIPT="$ROOT/scripts/dc-node-carve.sh"
P=0; F=0
ok(){ echo "PASS: $1"; P=$((P+1)); }
no(){ echo "FAIL: $1"; F=$((F+1)); }
chk(){ [ "$2" = "$3" ] && ok "$1" || no "$1 (got '$2' want '$3')"; }
has(){ printf '%s' "$2" | grep -q -- "$3" && ok "$1" || no "$1 (output lacked '$3')"; }
hasnt(){ printf '%s' "$2" | grep -q -- "$3" && no "$1 (output unexpectedly had '$3')" || ok "$1"; }

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
mkdir -p "$TMP/bin"

cat > "$TMP/bin/maas" <<'STUB'
#!/usr/bin/env bash
prof="$1"; shift
sub="$1"; shift
S="${STUB_STATE:-carved}"

# --- the ten dc0 machines, keyed by their PINNED boot MACs (lib-hosts.sh) ----
# NOTE: this heredoc is DELIBERATELY unquoted -- STUB_S04_STATUS must expand.
# A quoted <<'M' leaves the literal ${...} in place and the payload stops being
# JSON, which is the same invisible-heredoc class as finding F8.
emit_machines() {
cat <<M
[{"system_id":"c01","hostname":"mint-roughy","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:be:69:c5"}},
 {"system_id":"c02","hostname":"gentle-raven","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:02:ff:57"}},
 {"system_id":"c03","hostname":"square-insect","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:4f:de:a9"}},
 {"system_id":"m01","hostname":"able-puma","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:1b:19:e6"}},
 {"system_id":"m02","hostname":"pure-condor","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:18:ab:b4"}},
 {"system_id":"s01","hostname":"amused-corgi","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:5f:8d:42"}},
 {"system_id":"s02","hostname":"known-alpaca","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:48:86:2c"}},
 {"system_id":"s03","hostname":"loyal-oyster","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:b1:94:d1"}},
 {"system_id":"s04","hostname":"civil-bug","status_name":"${STUB_S04_STATUS:-Ready}","boot_interface":{"mac_address":"52:54:00:2b:ed:ab"}},
 {"system_id":"j01","hostname":"subtle-grouse","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:48:e7:1e"}},
 {"system_id":"t01","hostname":"known-marten","status_name":"${STUB_T01_STATUS:-Ready}","boot_interface":{"mac_address":"52:54:00:54:d5:5e"}},
 {"system_id":"r01","hostname":"brave-otter","status_name":"Ready","boot_interface":{"mac_address":"52:54:00:dc:5c:0b"}}]
M
}

emit_subnets() {
  local pgw='"10.12.4.1"'
  [ "$S" = nogw ] && pgw='null'
  cat <<S2
[{"id":1,"cidr":"10.12.8.0/22","gateway_ip":null,"vlan":{"id":5001,"fabric":"fabric-0"}},
 {"id":3,"cidr":"10.12.4.0/22","gateway_ip":$pgw,"vlan":{"id":5002,"fabric":"vr1-dc0-provider-public"}},
 {"id":4,"cidr":"10.12.12.0/22","gateway_ip":null,"vlan":{"id":5004,"fabric":"vr1-dc0-metal-internal"}},
 {"id":5,"cidr":"10.12.16.0/22","gateway_ip":null,"vlan":{"id":5005,"fabric":"vr1-dc0-data-tenant"}},
 {"id":6,"cidr":"10.12.32.0/22","gateway_ip":null,"vlan":{"id":5006,"fabric":"vr1-dc0-storage"}},
 {"id":7,"cidr":"10.12.36.0/22","gateway_ip":null,"vlan":{"id":5007,"fabric":"vr1-dc0-replication"}}]
S2
}

# octet for a system_id -- the D-134 bands
octet_of() {
  case "$1" in
    c01) echo 100 ;; c02) echo 101 ;; c03) echo 102 ;;
    m01) echo 120 ;; m02) echo 121 ;;
    s01) echo 150 ;; s02) echo 151 ;; s03) echo 152 ;; s04) echo 153 ;;
    j01) echo 5 ;; t01) echo 7 ;; r01) echo 6 ;;
  esac
}

# a raw, uncarved machine: enp1s0 auto on metal-admin, enp2-6 link_up on autofabs
emit_raw() {
  cat <<R
[{"id":1,"name":"enp1s0","type":"physical","vlan":{"id":5001},"parents":[],"links":[{"id":11,"mode":"auto","ip_address":null,"subnet":{"cidr":"10.12.8.0/22"}}]},
 {"id":2,"name":"enp2s0","type":"physical","vlan":{"id":5100},"parents":[],"links":[{"id":12,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5101},"parents":[],"links":[{"id":13,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":4,"name":"enp4s0","type":"physical","vlan":{"id":5102},"parents":[],"links":[{"id":14,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":5,"name":"enp5s0","type":"physical","vlan":{"id":5103},"parents":[],"links":[{"id":15,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":6,"name":"enp6s0","type":"physical","vlan":{"id":5104},"parents":[],"links":[{"id":16,"mode":"link_up","ip_address":null,"subnet":null}]}]
R
}

# a fully carved ROLE node
emit_role() {
  local N="$1" stor_ip brex_blk enp2_links
  stor_ip="10.12.32.$N"
  [ "$S" = wrongip ] && [ "$1" = 153 ] && stor_ip="10.12.32.199"
  enp2_links='[]'
  local brex_ip="10.12.4.$N"
  [ "$S" = brexip ] && [ "$N" = 150 ] && brex_ip="10.12.4.199"
  brex_blk='{"id":90,"name":"br-ex","type":"bridge","vlan":{"id":5002},"parents":["enp2s0"],"links":[{"id":90,"mode":"static","ip_address":"'"$brex_ip"'","subnet":{"cidr":"10.12.4.0/22"}}]},'
  if [ "$S" = patternb ] && [ "$N" = 150 ]; then
    # provider static on the RAW nic, no bridge at all
    brex_blk=''
    enp2_links='[{"id":21,"mode":"static","ip_address":"10.12.4.'"$N"'","subnet":{"cidr":"10.12.4.0/22"}}]'
  fi
  if [ "$S" = memberl3 ] && [ "$N" = 151 ]; then
    # bridge present, but the member NIC kept an L3 link too
    enp2_links='[{"id":22,"mode":"static","ip_address":"10.12.4.'"$N"'","subnet":{"cidr":"10.12.4.0/22"}}]'
  fi
  cat <<RO
[$brex_blk
 {"id":1,"name":"enp1s0","type":"physical","vlan":{"id":5001},"parents":[],"links":[{"id":1,"mode":"static","ip_address":"10.12.8.$N","subnet":{"cidr":"10.12.8.0/22"}}]},
 {"id":2,"name":"enp2s0","type":"physical","vlan":{"id":5002},"parents":[],"links":$enp2_links},
 {"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5004},"parents":[],"links":[{"id":3,"mode":"static","ip_address":"10.12.12.$N","subnet":{"cidr":"10.12.12.0/22"}}]},
 {"id":4,"name":"enp4s0","type":"physical","vlan":{"id":5005},"parents":[],"links":[{"id":4,"mode":"static","ip_address":"10.12.16.$N","subnet":{"cidr":"10.12.16.0/22"}}]},
 {"id":5,"name":"enp5s0","type":"physical","vlan":{"id":5006},"parents":[],"links":[{"id":5,"mode":"static","ip_address":"$stor_ip","subnet":{"cidr":"10.12.32.0/22"}}]},
 {"id":6,"name":"enp6s0","type":"physical","vlan":{"id":5007},"parents":[],"links":[{"id":6,"mode":"static","ip_address":"10.12.36.$N","subnet":{"cidr":"10.12.36.0/22"}}]}]
RO
}

# the JUJU CONTROLLER: two planes RAW, no br-ex, enp3-6 left on auto VLANs
emit_juju() {
  local extra='' brex=''
  if [ "$S" = jujucarved ]; then
    extra='{"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5004},"parents":[],"links":[{"id":3,"mode":"static","ip_address":"10.12.12.5","subnet":{"cidr":"10.12.12.0/22"}}]},'
  else
    extra='{"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5201},"parents":[],"links":[{"id":3,"mode":"link_up","ip_address":null,"subnet":null}]},'
  fi
  [ "$S" = jujubrex ] && brex='{"id":90,"name":"br-ex","type":"bridge","vlan":{"id":5002},"parents":["enp2s0"],"links":[]},'
  cat <<JU
[$brex
 {"id":1,"name":"enp1s0","type":"physical","vlan":{"id":5001},"parents":[],"links":[{"id":1,"mode":"static","ip_address":"10.12.8.5","subnet":{"cidr":"10.12.8.0/22"}}]},
 {"id":2,"name":"enp2s0","type":"physical","vlan":{"id":5002},"parents":[],"links":[{"id":2,"mode":"static","ip_address":"10.12.4.5","subnet":{"cidr":"10.12.4.0/22"}}]},
 $extra
 {"id":4,"name":"enp4s0","type":"physical","vlan":{"id":5202},"parents":[],"links":[{"id":4,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":5,"name":"enp5s0","type":"physical","vlan":{"id":5203},"parents":[],"links":[{"id":5,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":6,"name":"enp6s0","type":"physical","vlan":{"id":5204},"parents":[],"links":[{"id":6,"mode":"link_up","ip_address":null,"subnet":null}]}]
JU
}

# the TAILSCALE .7 ROUTER: same shape as the controller (two planes RAW, no
# br-ex, enp3-6 on auto VLANs) but at octet 7.
emit_tailscale() {
  local extra='' brex=''
  if [ "$S" = tscarved ]; then
    extra='{"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5004},"parents":[],"links":[{"id":3,"mode":"static","ip_address":"10.12.12.7","subnet":{"cidr":"10.12.12.0/22"}}]},'
  else
    extra='{"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5301},"parents":[],"links":[{"id":3,"mode":"link_up","ip_address":null,"subnet":null}]},'
  fi
  [ "$S" = tsbrex ] && brex='{"id":90,"name":"br-ex","type":"bridge","vlan":{"id":5002},"parents":["enp2s0"],"links":[]},'
  cat <<TS
[$brex
 {"id":1,"name":"enp1s0","type":"physical","vlan":{"id":5001},"parents":[],"links":[{"id":1,"mode":"static","ip_address":"10.12.8.7","subnet":{"cidr":"10.12.8.0/22"}}]},
 {"id":2,"name":"enp2s0","type":"physical","vlan":{"id":5002},"parents":[],"links":[{"id":2,"mode":"static","ip_address":"10.12.4.7","subnet":{"cidr":"10.12.4.0/22"}}]},
 $extra
 {"id":4,"name":"enp4s0","type":"physical","vlan":{"id":5302},"parents":[],"links":[{"id":4,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":5,"name":"enp5s0","type":"physical","vlan":{"id":5303},"parents":[],"links":[{"id":5,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":6,"name":"enp6s0","type":"physical","vlan":{"id":5304},"parents":[],"links":[{"id":6,"mode":"link_up","ip_address":null,"subnet":null}]}]
TS
}

# the MAAS REGION VM (.6): identical 2-plane no-br-ex shape as the tailscale
# router, at octet 6. Proves is_region_host routes to the two-plane path.
emit_region() {
  cat <<RG
[{"id":1,"name":"enp1s0","type":"physical","vlan":{"id":5001},"parents":[],"links":[{"id":1,"mode":"static","ip_address":"10.12.8.6","subnet":{"cidr":"10.12.8.0/22"}}]},
 {"id":2,"name":"enp2s0","type":"physical","vlan":{"id":5002},"parents":[],"links":[{"id":2,"mode":"static","ip_address":"10.12.4.6","subnet":{"cidr":"10.12.4.0/22"}}]},
 {"id":3,"name":"enp3s0","type":"physical","vlan":{"id":5301},"parents":[],"links":[{"id":3,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":4,"name":"enp4s0","type":"physical","vlan":{"id":5302},"parents":[],"links":[{"id":4,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":5,"name":"enp5s0","type":"physical","vlan":{"id":5303},"parents":[],"links":[{"id":5,"mode":"link_up","ip_address":null,"subnet":null}]},
 {"id":6,"name":"enp6s0","type":"physical","vlan":{"id":5304},"parents":[],"links":[{"id":6,"mode":"link_up","ip_address":null,"subnet":null}]}]
RG
}

case "$sub" in
  rack-controllers)
      case "${STUB_RACK:-hot-kid}" in
        none) echo '[]' ;;
        *) printf '[{"hostname":"%s"}]\n' "${STUB_RACK:-hot-kid}" ;;
      esac ;;
  machines)   emit_machines ;;
  machine)    emit_machines ;;
  subnets)    emit_subnets ;;
  interfaces)
      act="${1:-read}"; sid="${2:-}"
      # `interfaces read <sysid>` -> $1=read $2=sysid ; create-bridge -> $1=create-bridge $2=sysid
      if [ "$act" = "create-bridge" ]; then echo '{"id":90}'; exit 0; fi
      if [ "$S" = raw ]; then emit_raw; exit 0; fi
      case "$sid" in
        j01) emit_juju ;;
        t01) emit_tailscale ;;
        r01) emit_region ;;
        *)   emit_role "$(octet_of "$sid")" ;;
      esac ;;
  interface)  echo '{}' ;;
  *) echo '[]' ;;
esac
STUB
chmod +x "$TMP/bin/maas"
export PATH="$TMP/bin:$PATH"

R(){ STUB_STATE="$1" bash "$SCRIPT" "${@:2}" 2>&1; }
GATE=(--profile vr1-dc0-region --expect-rack hot-kid)

echo "== T1..T6 argument + region + site gates =="
out="$(bash "$SCRIPT" check vr1-dc0 --expect-rack hot-kid 2>&1)"; rc=$?
chk "T1 missing --profile refuses rc=2" "$rc" "2"
has "T1b names the OFFICE1 hazard" "$out" "OFFICE1"

out="$(bash "$SCRIPT" check vr1-dc0 --profile vr1-dc0-region 2>&1)"; rc=$?
chk "T2 missing --expect-rack refuses rc=2" "$rc" "2"

out="$(bash "$SCRIPT" check vr0-dc0 "${GATE[@]}" 2>&1)"; rc=$?
chk "T3 vr0 site refused rc=2" "$rc" "2"
has "T3b points at carve-host-interfaces.sh" "$out" "carve-host-interfaces.sh"

out="$(STUB_RACK=wrong-rack bash "$SCRIPT" check vr1-dc0 "${GATE[@]}" 2>&1)"; rc=$?
chk "T4 wrong region refuses rc=2" "$rc" "2"
hasnt "T4b nothing was read after the refusal" "$out" "plane preconditions"

out="$(STUB_RACK=none bash "$SCRIPT" check vr1-dc0 "${GATE[@]}" 2>&1)"; rc=$?
chk "T5 region with no racks refuses rc=2" "$rc" "2"

out="$(R carved check vr1-dc0 "${GATE[@]}" --host not-a-host)"; rc=$?
chk "T6 unknown --host refuses rc=2" "$rc" "2"

echo
echo "== T7..T9 the intended end state PASSES =="
out="$(R carved check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T7 carved state passes rc=0" "$rc" "0"
has "T7b reports fail=0" "$out" "fail=0"
has "T8 asserts br-ex parent" "$out" "br-ex parent is enp2s0"
has "T8b asserts the member holds no L3" "$out" "holds no L3 link"
has "T9 asserts the controller has NO br-ex" "$out" "has NO br-ex"
has "T9b asserts the controller's spare NICs are left alone" "$out" "deliberately left on auto VLANs"
has "T9c states the routing table is a first-boot fact" "$out" "FIRST-BOOT observation"

echo
echo "== T10..T15 every wrong state must FAIL (the mutation-proof set) =="
out="$(R raw check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T10 the measured uncarved state FAILS rc=1" "$rc" "1"

out="$(R wrongip check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T11 right subnet + WRONG address FAILS rc=1" "$rc" "1"
has "T11b names the wrong address" "$out" "10.12.32.199"

out="$(R patternb check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T12 Pattern B (static on raw enp2s0, no br-ex) FAILS rc=1" "$rc" "1"

out="$(R memberl3 check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T13 br-ex member still holding an L3 link FAILS rc=1" "$rc" "1"

out="$(R jujucarved check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T14 controller's spare NIC carved FAILS rc=1" "$rc" "1"

out="$(R jujubrex check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T15 controller grown a br-ex FAILS rc=1" "$rc" "1"

out="$(R brexip check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T15c br-ex with the WRONG static FAILS rc=1" "$rc" "1"
has "T15d names the wrong br-ex address" "$out" "10.12.4.199"

out="$(R nogw check vr1-dc0 "${GATE[@]}")"; rc=$?
chk "T16 provider-public with no gateway FAILS rc=1" "$rc" "1"
has "T16b explains the default route depends on it" "$out" "default route"

echo
echo "== T17..T20 role asymmetry and NIC order are DERIVED, not typed =="
out="$(R carved check vr1-dc0 "${GATE[@]}" --host vr1-dc0-juju-01)"; rc=$?
chk "T17 controller alone passes rc=0" "$rc" "0"
has "T17b controller carved on 2 planes" "$out" "2 plane(s)"
hasnt "T17c controller is NOT given a data-tenant leg" "$out" "enp4s0 STATIC 10.12.16"

out="$(R carved check vr1-dc0 "${GATE[@]}" --host vr1-dc0-control-01)"; rc=$?
chk "T18 role node alone passes rc=0" "$rc" "0"
has "T18b role node carved on 6 planes" "$out" "6 plane(s)"
# enp1s0 is METAL-ADMIN, not provider-public. Walking lib-net.sh PLANE_CIDRS
# positionally would put PXE on provider-public and strand commissioning.
has "T19 enp1s0 is metal-admin (NIC_PLANE_ORDER, not PLANE_CIDRS)" "$out" "enp1s0 STATIC 10.12.8.100 on metal-admin"
has "T19b enp2s0/br-ex is provider-public" "$out" "br-ex STATIC 10.12.4.100 on provider-public"

echo
echo "== T28..T33 the AUX Tailscale .7 router: 2 planes, no br-ex, octet 7 =="
out="$(R carved check vr1-dc0 "${GATE[@]}" --host vr1-dc0-tailscale-01)"; rc=$?
chk "T28 tailscale router alone passes rc=0" "$rc" "0"
has "T28b carved on 2 planes" "$out" "2 plane(s)"
has "T29 enp1s0 is metal-admin .7 (NIC_PLANE_ORDER)" "$out" "enp1s0 STATIC 10.12.8.7 on metal-admin"
has "T29b enp2s0 is provider-public .7 RAW, not br-ex" "$out" "enp2s0 STATIC 10.12.4.7 on provider-public"
hasnt "T29c the router is NOT given an OVS br-ex leg" "$out" "br-ex STATIC"
has "T30 asserts the router has NO br-ex" "$out" "has NO br-ex"
has "T30b asserts the router's spare NICs are left alone" "$out" "deliberately left on auto VLANs"

out="$(R carved check vr1-dc0 "${GATE[@]}" --host not-a-host)"; rc=$?
chk "T31 unknown --host still refuses rc=2 (aux allowance did not widen)" "$rc" "2"

out="$(R tsbrex check vr1-dc0 "${GATE[@]}" --host vr1-dc0-tailscale-01)"; rc=$?
chk "T32 tailscale grown an OVS br-ex FAILS rc=1" "$rc" "1"

out="$(R tscarved check vr1-dc0 "${GATE[@]}" --host vr1-dc0-tailscale-01)"; rc=$?
chk "T33 tailscale spare NIC carved onto a plane FAILS rc=1" "$rc" "1"

echo
echo "== T34..T35 the MAAS region VM (.6): -maas-01 routes to the 2-plane path, octet 6 =="
out="$(R carved check vr1-dc0 "${GATE[@]}" --host vr1-dc0-maas-01)"; rc=$?
chk "T34 region VM alone passes rc=0" "$rc" "0"
has "T34b carved on 2 planes" "$out" "2 plane(s)"
has "T35 enp1s0 is metal-admin .6" "$out" "enp1s0 STATIC 10.12.8.6 on metal-admin"
has "T35b enp2s0 is provider-public .6 (raw, no br-ex)" "$out" "enp2s0 STATIC 10.12.4.6 on provider-public"
has "T35c asserts the region VM has NO br-ex" "$out" "has NO br-ex"

echo
echo "== T21..T24 apply is DRY-RUN without --commit =="
out="$(R raw apply vr1-dc0 "${GATE[@]}" --host vr1-dc0-storage-04)"; rc=$?
has "T21 plan-only banner" "$out" "PLAN ONLY -- nothing was changed"
has "T22 plans the VLAN move" "$out" "\[plan\] enp3s0"
has "T23 plans the br-ex create" "$out" "\[plan\] create br-ex (OVS) parent=enp2s0"
hasnt "T24 dry-run reports no mutations" "$out" "mutations applied"
# THE COMMISSIONING LINK IS THE EXPECTED STARTING STATE, NOT A CONFLICT. Every
# PXE leg comes out of enlistment holding `auto` on metal-admin. A live dry-run
# caught this: the tool refused enp1s0 outright, so it could not carve the
# metal-admin leg on ANY of the ten machines.
chk "T26 apply over the measured raw state plans cleanly rc=0" "$rc" "0"
has "T26b names the commissioning link it is converting" "$out" "holds a commissioning 'auto' link"
has "T26c plans the unlink before the static" "$out" "\[plan\] unlink enp1s0"
has "T26d plans the metal-admin static" "$out" "\[plan\] enp1s0(id=1) -> STATIC 10.12.8.153"
hasnt "T26e does NOT refuse the auto link" "$out" "refusing to silently accept it"

# The auto-link conversion above must NOT have widened into "overwrite anything".
# A STATIC link on the right subnet with the wrong address was put there
# deliberately; moving it silently could strand whatever depends on it.
out="$(R wrongip apply vr1-dc0 "${GATE[@]}" --host vr1-dc0-storage-04)"; rc=$?
chk "T27 apply still REFUSES a static wrong address rc=1" "$rc" "1"
has "T27b names the refusal" "$out" "refusing to silently accept it"
has "T27c names the conflicting address" "$out" "10.12.32.199"

out="$(STUB_S04_STATUS=Deployed R carved apply vr1-dc0 "${GATE[@]}" --host vr1-dc0-storage-04 --commit)"; rc=$?
chk "T25 apply on a Deployed machine FAILS rc=1" "$rc" "1"
has "T25b explains MAAS rejects it" "$out" "rejects interface changes"

echo
echo "RESULT: $P passed, $F failed"
[ "$F" -eq 0 ] || exit 1
