#!/usr/bin/env bash
# tests/opnsense-bootstrap-apikey/run-tests.sh -- offline harness for
# scripts/opnsense-bootstrap-apikey.sh. Stubs ssh/scp on PATH, so no edge is ever
# contacted and no real key is ever minted.
#
# The load-bearing case is T5: the script MUST refuse to overwrite an existing creds
# file. An existing file means a LIVE key on the edge; overwriting the local copy
# would strand it there permanently, because OPNsense stores the secret as
# crypt(secret,'$6$') and it can never be read back. That is an unrecoverable
# credential loss, and a guard clause is the only thing standing in front of it.
#
# Exit: 0 all pass | 1 any case failed.  ASCII + LF.
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT="$HERE/../../scripts/opnsense-bootstrap-apikey.sh"
MINTER="$HERE/../../scripts/opnsense-mint-apikey.php"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PASS=0; FAIL=0

FAKE_KEY="FAKEKEYaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
FAKE_SECRET="FAKESECRETbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"

ok()  { PASS=$((PASS+1)); echo "  PASS  $1"; }
bad() { FAIL=$((FAIL+1)); echo "  FAIL  $1"; }

# --- stub ssh/scp: scp "retrieving" the remote file writes a fake creds payload ---
mk_stubs() {
  cat > "$TMP/bin/ssh" <<'STUB'
#!/usr/bin/env bash
exit 0
STUB
  cat > "$TMP/bin/scp" <<STUB
#!/usr/bin/env bash
# last arg is the destination; if it is a LOCAL path (no colon), we are retrieving
dest="\${!#}"
case "\$dest" in
  *:*) exit 0 ;;   # shipping TO the edge -- nothing to do
  *)   printf 'key=%s\nsecret=%s\n' "$FAKE_KEY" "$FAKE_SECRET" > "\$dest"; exit 0 ;;
esac
STUB
  chmod +x "$TMP/bin/ssh" "$TMP/bin/scp"
}
mkdir -p "$TMP/bin"; mk_stubs

echo "T1: the minter PHP exists and is what we ship"
if [ -f "$MINTER" ] && grep -q "apikeys->add()" "$MINTER"; then
  ok "T1 minter present and calls the VENDOR model (apikeys->add)"
else
  bad "T1 minter missing or does not call apikeys->add()"
fi

echo "T2: the minter never prints the secret"
if grep -qE 'printf|echo' "$MINTER" && ! grep -E "printf|echo" "$MINTER" | grep -q "\['secret'\]"; then
  ok "T2 minter prints lengths, never the secret itself"
else
  bad "T2 minter may print the secret"
fi

echo "T3: no args -> usage, exit 1"
out="$(bash "$SCRIPT" 2>&1)"; rc=$?
{ [ "$rc" = "1" ] && grep -q usage <<<"$out"; } && ok "T3 usage guard" || bad "T3 usage guard (rc=$rc)"

echo "T4: dry-run needs no ssh key and contacts nothing"
out="$(env -u OPNSENSE_SSH_KEY bash "$SCRIPT" --dry-run 10.10.0.1 "$TMP/nope.txt" 2>&1)"; rc=$?
{ [ "$rc" = "0" ] && grep -q "DRY-RUN" <<<"$out"; } && ok "T4 dry-run" || bad "T4 dry-run (rc=$rc)"

echo "T5: REFUSES to overwrite an existing creds file (the unrecoverable-loss guard)"
printf 'key=existing\nsecret=existing\n' > "$TMP/existing.txt"
out="$(OPNSENSE_SSH_KEY=/dev/null PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/existing.txt" 2>&1)"; rc=$?
if [ "$rc" = "1" ] && grep -q "refusing to overwrite" <<<"$out"; then
  # and it must NOT have touched the file
  if grep -q "^key=existing$" "$TMP/existing.txt"; then
    ok "T5 refuses to clobber a live credential, and leaves it intact"
  else
    bad "T5 refused but MODIFIED the existing file"
  fi
else
  bad "T5 did NOT refuse to overwrite an existing creds file (rc=$rc) -- would strand a live key"
fi

echo "T6: missing SSH key -> fail loud (not a silent agent-auth attempt)"
out="$(env -u OPNSENSE_SSH_KEY PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out6.txt" 2>&1)"; rc=$?
{ [ "$rc" = "1" ] && grep -q "OPNSENSE_SSH_KEY" <<<"$out"; } && ok "T6 ssh-key guard" || bad "T6 ssh-key guard (rc=$rc)"

echo "T7: happy path writes a 0600 creds file in the shape opnsense-api.sh parses"
out="$(OPNSENSE_SSH_KEY=/dev/null PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out7.txt" 2>&1)"; rc=$?
if [ "$rc" = "0" ] && [ -f "$TMP/out7.txt" ]; then
  mode=$(stat -c %a "$TMP/out7.txt")
  k=$(grep -c '^key=' "$TMP/out7.txt"); s=$(grep -c '^secret=' "$TMP/out7.txt")
  if [ "$mode" = "600" ] && [ "$k" = "1" ] && [ "$s" = "1" ]; then
    ok "T7 minted file: mode 600, one key= line, one secret= line"
  else
    bad "T7 bad output file (mode=$mode key=$k secret=$s)"
  fi
else
  bad "T7 happy path failed (rc=$rc): $out"
fi

echo "T8: the secret never appears on stdout/stderr"
case "$out" in
  *"$FAKE_SECRET"*) bad "T8 SECRET PRINTED to output";;
  *)                ok "T8 secret absent from output";;
esac

# T9/T10 cover the post-bootstrap sshd restart transient (measured dc1 2026-07-23):
# the edge's `configctl openssh restart` can close the FIRST connection after the
# console bootstrap. The script must wait for sshd and retry the first scp once --
# and must FAIL LOUD (not hang) when the edge never comes back.
# OPNSENSE_BOOTSTRAP_RETRY_DELAY=0 exists for exactly this harness: no real sleeps.

echo "T9: a TRANSIENT first-scp failure is retried and succeeds"
rm -f "$TMP/scp-attempts"
cat > "$TMP/bin/scp" <<STUB
#!/usr/bin/env bash
dest="\${!#}"
case "\$dest" in
  *:*)  # shipping TO the edge: fail the FIRST attempt (post-restart transient), succeed after
    n=0; [ -f "$TMP/scp-attempts" ] && n=\$(cat "$TMP/scp-attempts")
    n=\$((n+1)); echo "\$n" > "$TMP/scp-attempts"
    [ "\$n" -eq 1 ] && { echo "scp: Connection closed" >&2; exit 1; }
    exit 0 ;;
  *)   printf 'key=%s\nsecret=%s\n' "$FAKE_KEY" "$FAKE_SECRET" > "\$dest"; exit 0 ;;
esac
STUB
chmod +x "$TMP/bin/scp"
out="$(OPNSENSE_SSH_KEY=/dev/null OPNSENSE_BOOTSTRAP_RETRY_DELAY=0 PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out9.txt" 2>&1)"; rc=$?
if [ "$rc" = "0" ] && [ -f "$TMP/out9.txt" ] && grep -q "retrying once" <<<"$out" && [ "$(cat "$TMP/scp-attempts")" = "2" ]; then
  ok "T9 transient first scp retried once, mint completed"
else
  bad "T9 transient-scp retry (rc=$rc attempts=$(cat "$TMP/scp-attempts" 2>/dev/null)): $out"
fi
mk_stubs   # restore the plain stubs

echo "T10: edge sshd NEVER ready -> bounded loud failure (exit 2), nothing shipped"
cat > "$TMP/bin/ssh" <<'STUB'
#!/usr/bin/env bash
echo "ssh: connect to host 10.10.0.1 port 22: Connection refused" >&2
exit 255
STUB
chmod +x "$TMP/bin/ssh"
out="$(OPNSENSE_SSH_KEY=/dev/null OPNSENSE_BOOTSTRAP_RETRY_DELAY=0 PATH="$TMP/bin:$PATH" bash "$SCRIPT" 10.10.0.1 "$TMP/out10.txt" 2>&1)"; rc=$?
if [ "$rc" = "2" ] && grep -q "never became ready" <<<"$out" && [ ! -e "$TMP/out10.txt" ]; then
  ok "T10 dead edge fails loud after bounded probes, no creds file created"
else
  bad "T10 dead-edge readiness guard (rc=$rc): $out"
fi
mk_stubs   # restore for any later case

echo
echo "RESULT: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] && { echo "ALL PASS"; exit 0; } || { echo "FAILURES"; exit 1; }
