CI / Shell-Lint (bash -n, source-check, Validierungs-Tests) (pull_request) Has been cancelled
- CTID prompt re-prompts on invalid interactive input (was: abort)
- env-provided NAMESERVER is validated when non-empty ('' stays inherit)
- prompt_validated handles EOF (no infinite loop, clean abort under -e)
- 10# base forcing in vlan/cidr/ipv4 arithmetic (leading zeros are not
octal errors); is_clean_ascii rejects embedded newline/tab explicitly
(command substitution strips trailing newlines); is_ipv4_list checks
the whole string before word splitting
- nameref guard against reserved variable names in prompt_validated/
require_valid; source-check pattern documented as the repo contract
- 8 new test cases (41 total)
169 lines
8.1 KiB
Bash
Executable File
169 lines
8.1 KiB
Bash
Executable File
#!/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
|
|
|
|
# ── Review-Findings K-114: Oktal, Newline, EOF, Nameref-Guard ────────────────
|
|
assert_true "is_vlan_tag akzeptiert '08' (kein Oktal-Fehler)" is_vlan_tag "08"
|
|
assert_true "is_cidr akzeptiert /08 (kein Oktal-Fehler)" is_cidr "10.0.0.1/08"
|
|
assert_false "is_clean_ascii lehnt eingebettetes Newline ab" is_clean_ascii $'1.1.1.1\n8.8.8.8'
|
|
assert_false "is_ipv4_list lehnt Newline-Liste ab" is_ipv4_list $'1.1.1.1\n8.8.8.8'
|
|
|
|
# EOF statt Eingabe: prompt_validated darf nicht endlos loopen.
|
|
( printf '' | { V=""; prompt_validated V "Wert: " is_uint; } ) >/dev/null 2>&1
|
|
rc=$?
|
|
[[ "$rc" -ne 0 ]] && ok "prompt_validated bricht bei EOF ab (rc=$rc)" \
|
|
|| nok "prompt_validated bricht bei EOF ab"
|
|
|
|
# Reservierte Namen → Guard statt zirkulärem nameref.
|
|
( _pv_ref=""; prompt_validated _pv_ref "x: " is_uint ) >/dev/null 2>&1
|
|
[[ $? -eq 2 ]] && ok "prompt_validated weist reservierte Variablennamen ab" \
|
|
|| nok "prompt_validated weist reservierte Variablennamen ab"
|
|
|
|
# CTID-Re-Prompt: ungültig → erneut fragen, leer wäre auto (hier: gültige Zahl).
|
|
ctid_out="$(
|
|
printf 'abc\n123\n' | env -u CTID bash -c "
|
|
source '$REPO_ROOT/lib/build.func'
|
|
CTID=''
|
|
while true; do
|
|
read -rp 'Container ID [auto]: ' CTID || exit 1
|
|
CTID=\"\$(sanitize_input \"\$CTID\")\"
|
|
[[ -z \"\$CTID\" ]] && exit 1
|
|
is_uint \"\$CTID\" && break
|
|
done
|
|
printf '%s' \"\$CTID\""
|
|
)"
|
|
[[ "$ctid_out" == "123" ]] && ok "CTID-Loop re-promptet bei 'abc' und nimmt '123'" \
|
|
|| nok "CTID-Loop re-promptet (got: '$ctid_out')"
|
|
|
|
# ── 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 ]]
|