#!/usr/bin/env bash # K-114: Unit-Tests für die Input-Validierung in lib/build.func. # Läuft ohne PVE (sourct nur die Helfer). Aufruf: bash tests/test_validation.sh set -u REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" # shellcheck source=/dev/null source "$REPO_ROOT/lib/build.func" PASS=0 FAIL=0 ok() { PASS=$((PASS + 1)); echo "ok - $1"; } nok() { FAIL=$((FAIL + 1)); echo "NOT OK - $1"; } assert_true() { # assert_true "desc" cmd args... local desc="$1"; shift if "$@"; then ok "$desc"; else nok "$desc"; fi } assert_false() { local desc="$1"; shift if "$@"; then nok "$desc"; else ok "$desc"; fi } # ── Repro des Vorfalls vom 2026-06-11: VLAN-Tag mit unsichtbarem Byte ──────── JUNK_VLAN="$(printf '2\x800')" # "20" mit eingebettetem Non-UTF-8-Byte assert_false "VLAN-Repro: '2<0x80>0' wird abgelehnt (kein silent strip)" is_vlan_tag "$JUNK_VLAN" assert_false "is_clean_ascii erkennt eingebettetes Junk-Byte" is_clean_ascii "$JUNK_VLAN" # Re-Prompt-Verhalten: erste Eingabe ist der Junk-Wert, zweite ist sauber — # prompt_validated muss die zweite liefern (AC: Re-Prompt statt pct-Fehler). reprompt_result="$( printf '%s\n20\n' "$JUNK_VLAN" | { VLAN="" prompt_validated VLAN "VLAN tag (empty for none): " is_vlan_tag "" yes >/dev/null 2>&1 printf '%s' "$VLAN" } )" if [[ "$reprompt_result" == "20" ]]; then ok "prompt_validated re-promptet bei Junk und akzeptiert dann '20'" else nok "prompt_validated re-promptet bei Junk (got: '$reprompt_result')" fi # ── sanitize_input: CR + Rand-Whitespace weg, Inhalt unangetastet ───────────── [[ "$(sanitize_input $' 42\r\n')" == "42" ]] && ok "sanitize_input trimmt CR/Whitespace" \ || nok "sanitize_input trimmt CR/Whitespace" [[ "$(sanitize_input "$JUNK_VLAN")" == "$JUNK_VLAN" ]] && ok "sanitize_input strippt KEIN eingebettetes Junk (Validator soll es sehen)" \ || nok "sanitize_input lässt eingebettetes Junk unangetastet" # ── numerische Validatoren ──────────────────────────────────────────────────── assert_true "is_uint akzeptiert 8" is_uint "8" assert_false "is_uint lehnt '8 ' mit Junk ab" is_uint "$(printf '8\x01')" assert_false "is_uint lehnt 'abc' ab" is_uint "abc" assert_false "is_uint lehnt leeren Wert ab" is_uint "" assert_true "is_vlan_tag akzeptiert 20" is_vlan_tag "20" assert_false "is_vlan_tag lehnt 0 ab" is_vlan_tag "0" assert_false "is_vlan_tag lehnt 5000 ab" is_vlan_tag "5000" # ── Netz-Validatoren ────────────────────────────────────────────────────────── assert_true "is_ipv4 akzeptiert 10.11.20.66" is_ipv4 "10.11.20.66" assert_false "is_ipv4 lehnt 10.11.20.666 ab" is_ipv4 "10.11.20.666" assert_false "is_ipv4 lehnt 10.11.20 ab" is_ipv4 "10.11.20" assert_true "is_cidr akzeptiert 10.11.20.5/24" is_cidr "10.11.20.5/24" assert_false "is_cidr lehnt 10.11.20.5/33 ab" is_cidr "10.11.20.5/33" assert_false "is_cidr lehnt 10.11.20.5 ohne Maske" is_cidr "10.11.20.5" assert_true "is_ipcfg akzeptiert dhcp" is_ipcfg "dhcp" assert_true "is_ipcfg akzeptiert CIDR" is_ipcfg "192.168.0.7/24" assert_false "is_ipcfg lehnt 'static' ab" is_ipcfg "static" assert_true "is_ipv4_list akzeptiert Liste" is_ipv4_list "1.1.1.1, 8.8.8.8" assert_false "is_ipv4_list lehnt Hostnamen ab" is_ipv4_list "dns.local" # ── Namen/Token ─────────────────────────────────────────────────────────────── assert_true "is_hostname akzeptiert nexus-db" is_hostname "nexus-db" assert_false "is_hostname lehnt '-bad' ab" is_hostname "-bad" assert_false "is_hostname lehnt 'a b' ab" is_hostname "a b" assert_true "is_token akzeptiert local-lvm" is_token "local-lvm" assert_false "is_token lehnt 'a;b' ab" is_token "a;b" # ── require_valid: env-Werte werden sanitisiert + geprüft ──────────────────── CHECKVAL=$' 7\r' require_valid CHECKVAL is_uint "Testwert" && [[ "$CHECKVAL" == "7" ]] \ && ok "require_valid sanitisiert env-Wert (CR weg)" \ || nok "require_valid sanitisiert env-Wert" ( CHECKBAD="$JUNK_VLAN"; require_valid CHECKBAD is_uint "Testwert" ) >/dev/null 2>&1 \ && nok "require_valid bricht bei Junk-env-Wert ab" \ || ok "require_valid bricht bei Junk-env-Wert ab (exit != 0)" # ── Negativ-Beweis: build.func-Source-Check schlägt bei Präparat an ────────── TMPDIR_CT="$(mktemp -d)" cat >"$TMPDIR_CT/broken.sh" <<'EOF' #!/usr/bin/env bash # absichtlich ohne source build.func (Repro des zweimal aufgetretenen Bugs) APP="broken" prompt_lxc_config EOF if bash "$REPO_ROOT/tests/check_ct_source.sh" "$TMPDIR_CT" >/dev/null 2>&1; then nok "check_ct_source.sh erkennt fehlendes 'source build.func'" else ok "check_ct_source.sh erkennt fehlendes 'source build.func'" fi rm -rf "$TMPDIR_CT" # Positiv: das echte ct/-Verzeichnis ist sauber. if bash "$REPO_ROOT/tests/check_ct_source.sh" "$REPO_ROOT/ct" >/dev/null 2>&1; then ok "alle ct/*.sh sourcen build.func" else nok "alle ct/*.sh sourcen build.func" fi # ── Dry-Run: prompt_lxc_config komplett aus env, ohne PVE/TTY ──────────────── smoke_out="$( env CTID=999 CT_HOSTNAME=smoke DISK_SIZE=8 CORES=2 RAM=1024 BRIDGE=vmbr0 \ VLAN_TAG=20 TEMPLATE_STORAGE=local ROOTFS_STORAGE=local-lvm \ IPCFG=10.11.20.99/24 GATEWAY=10.11.20.1 NAMESERVER="" \ NET_PROFILES_FILE="$REPO_ROOT/lib/networks.conf" \ bash -c "source '$REPO_ROOT/lib/build.func' && prompt_lxc_config >/dev/null && echo SMOKE-OK" )" || true [[ "$smoke_out" == *SMOKE-OK* ]] \ && ok "prompt_lxc_config Dry-Run mit validen env-Werten läuft durch" \ || nok "prompt_lxc_config Dry-Run (got: '$smoke_out')" smoke_bad="$( env CTID="$(printf '9\x809')" CT_HOSTNAME=smoke DISK_SIZE=8 CORES=2 RAM=1024 BRIDGE=vmbr0 \ VLAN_TAG=20 TEMPLATE_STORAGE=local ROOTFS_STORAGE=local-lvm IPCFG=dhcp NAMESERVER="" \ NET_PROFILES_FILE="$REPO_ROOT/lib/networks.conf" \ bash -c "source '$REPO_ROOT/lib/build.func' && prompt_lxc_config >/dev/null && echo SMOKE-OK" 2>/dev/null )" || true [[ "$smoke_bad" == *SMOKE-OK* ]] \ && nok "prompt_lxc_config bricht bei Junk-CTID aus env ab" \ || ok "prompt_lxc_config bricht bei Junk-CTID aus env ab" echo echo "passed=$PASS failed=$FAIL" [[ "$FAIL" -eq 0 ]]