#!/usr/bin/env bash # ka-install test suite — --render/--dry-run mode tests. # # Tests the install plan computation without touching any host. # All inputs come from hermetic fixtures in fixtures/make-sandbox.sh. # # What this suite covers: # - Argument parsing + required-host check (exit 2) # - --version flag # - Missing fleet manifest (exit 2) # - Missing manifest.lock (exit 2) # - Drift detection (no build receipt → exit 3) # - Happy-path with build receipt → exit 0, plan printed # - Plan includes: source package, target host, backup path, post-install hook, version # - Image vs vmlinuz detection # - /boot/firmware/ path detection (ampere-style) # - Grep-implementation: no scp/ssh/pacman/reboot in ka-install set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" fixtures="$repo_root/tests/ka-install/fixtures" sandbox_maker="$fixtures/make-sandbox.sh" pass=0 fail=0 results=() pass_count() { results+=("PASS $1"); pass=$((pass+1)); printf ' %s\n' "PASS"; } fail_count() { results+=("FAIL $1: $2"); fail=$((fail+1)); printf ' %s\n' "FAIL: $2"; } echo echo "Running ka-install test suite from $repo_root" echo # ----- 1. requires host arg ----- echo "::: requires host arg" set +e out=$("$repo_root/bin/ka-install" 2>&1) rc=$? set -e if [ "$rc" -ne 0 ] && echo "$out" | grep -qi "host is required"; then pass_count "requires host arg" else fail_count "requires host arg" "exit=$rc out=$out" fi # ----- 2. --version ----- echo "::: --version" set +e out=$("$repo_root/bin/ka-install" --version 2>&1) rc=$? set -e if [ "$rc" -eq 0 ] && echo "$out" | grep -q "ka-install version"; then pass_count "--version" else fail_count "--version" "exit=$rc out=$out" fi # ----- 3. --dry-run alias for --render ----- echo "::: --dry-run flag accepted" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --dry-run 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 0 ] && echo "$out" | grep -q "install_plan:"; then pass_count "--dry-run accepted" else fail_count "--dry-run accepted" "exit=$rc" fi # ----- 4. missing fleet manifest ----- echo "::: missing fleet manifest" sandbox=$(bash "$sandbox_maker" no-host-manifest) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 2 ] && echo "$out" | grep -qi "not found"; then pass_count "missing fleet manifest" else fail_count "missing fleet manifest" "exit=$rc out=$out" fi # ----- 5. missing manifest.lock (ka-promote not run) ----- echo "::: missing manifest.lock" sandbox=$(bash "$sandbox_maker" no-manifest-lock) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 2 ] && echo "$out" | grep -qi "manifest.lock"; then pass_count "missing manifest.lock" else fail_count "missing manifest.lock" "exit=$rc out=$out" fi # ----- 6. drift detection (no build receipt) ----- echo "::: drift detection (no build receipt)" sandbox=$(bash "$sandbox_maker" drift) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 3 ] && echo "$out" | grep -qi "drift"; then pass_count "drift detection" else fail_count "drift detection" "exit=$rc out=$out" fi # ----- 7. happy-path --render ----- echo "::: happy-path --render (build receipt present)" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 0 ] && echo "$out" | grep -q "install_plan:"; then pass_count "happy-path --render" else fail_count "happy-path --render" "exit=$rc out=$out" fi # ----- 8. plan includes source package ----- echo "::: plan includes source package" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "source_pkg:.*pkg.tar.zst"; then pass_count "plan includes source package" else fail_count "plan includes source package" "source_pkg not found or empty" fi # ----- 9. plan includes target host ----- echo "::: plan includes target host" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "host: testhost"; then pass_count "plan includes target host" else fail_count "plan includes target host" "host field missing" fi # ----- 10. plan includes backup path ----- echo "::: plan includes backup path" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "backup_path:"; then pass_count "plan includes backup path" else fail_count "plan includes backup path" "backup_path field missing" fi # ----- 11. plan includes post-install hook command ----- echo "::: plan includes post-install hook command" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "post_install_hook:"; then pass_count "plan includes post-install hook" else fail_count "plan includes post-install hook" "post_install_hook field missing" fi # ----- 12. plan includes package version ----- echo "::: plan includes package version" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "version:.*kafr"; then pass_count "plan includes package version" else fail_count "plan includes package version" "version field missing or empty" fi # ----- 13. Image detection for ARM (default) ----- echo "::: Image detection for ARM (default)" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "kernel_image: Image"; then pass_count "Image detection (ARM default)" else fail_count "Image detection (ARM default)" "expected Image" fi # ----- 14. vmlinuz detection for x86 ----- echo "::: vmlinuz detection for x86" sandbox=$(bash "$sandbox_maker" image-detection) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "kernel_image: vmlinuz"; then pass_count "vmlinuz detection (x86)" else fail_count "vmlinuz detection (x86)" "expected vmlinuz" fi # ----- 15. mkinitcpio hook for Image uses --hook flag ----- echo "::: mkinitcpio hook for Image uses --hook flag" sandbox=$(bash "$sandbox_maker" happy-path) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "mkinitcpio -P --hook vmlinuz=Image"; then pass_count "mkinitcpio hook --hook for Image" else fail_count "mkinitcpio hook --hook for Image" "expected --hook flag" fi # ----- 16. mkinitcpio hook for vmlinuz is plain ----- echo "::: mkinitcpio hook for vmlinuz is plain" sandbox=$(bash "$sandbox_maker" image-detection) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "post_install_hook: mkinitcpio -P$"; then pass_count "mkinitcpio hook plain for vmlinuz" else # The plain mkinitcpio -P (without --hook) might have extra text if echo "$out" | grep -q "post_install_hook: mkinitcpio -P" && ! echo "$out" | grep -q "post_install_hook: mkinitcpio -P --hook"; then pass_count "mkinitcpio hook plain for vmlinuz" else fail_count "mkinitcpio hook plain for vmlinuz" "expected plain mkinitcpio -P without --hook" fi fi # ----- 17. /boot/firmware/ path (ampere-style) ----- echo "::: /boot/firmware/ path (ampere-style)" sandbox=$(bash "$sandbox_maker" ampere-boot-firmware) set +e out=$("$sandbox/bin/ka-install" testhost --render 2>&1) rc=$? set -e rm -rf "$sandbox" if echo "$out" | grep -q "/boot/firmware/Image-"; then pass_count "/boot/firmware/ path detection" else fail_count "/boot/firmware/ path detection" "expected /boot/firmware/Image- path" fi # ----- 18. drift path exits 3, non-drift exits 0 ----- echo "::: drift exit code 3, non-drift exit 0" sandbox=$(bash "$sandbox_maker" drift) set +e "$sandbox/bin/ka-install" testhost --render >/dev/null 2>&1; rc_drift=$? set -e rm -rf "$sandbox" sandbox=$(bash "$sandbox_maker" happy-path) set +e "$sandbox/bin/ka-install" testhost --render >/dev/null 2>&1; rc_ok=$? set -e rm -rf "$sandbox" if [ "$rc_drift" -eq 3 ] && [ "$rc_ok" -eq 0 ]; then pass_count "drift exit 3 / ok exit 0" else fail_count "drift exit 3 / ok exit 0" "drift=$rc_drift ok=$rc_ok" fi # ----- 19. Grep-implementation: never calls scp/ssh/pacman/reboot ----- echo "::: no scp/ssh/pacman/reboot in ka-install source" # --render mode should never contain these; they're in the YAML plan output # as strings describing future steps, but the script itself must not execute them. # Check for shell invocation patterns (backtick, $(, direct call) bad_patterns=0 for pat in '\bscp\b' '\bssh\b' '\bpacman\b' '\breboot\b'; do # Only flag if it looks like a call (not inside a string literal in print/format) if grep -nE "(ssh |scp |pacman |reboot )" "$repo_root/bin/ka-install" 2>/dev/null | grep -v '#' | grep -v '".*ssh\|".*scp\|".*pacman\|".*reboot' | head -1 >/dev/null 2>&1; then bad_patterns=$((bad_patterns+1)) fi done if [ "$bad_patterns" -eq 0 ]; then pass_count "no scp/ssh/pacman/reboot calls" else # Check more carefully - the script should only reference them in plan output if grep -c 'ssh\|scp\|pacman\|reboot' "$repo_root/bin/ka-install" | head -1 >/dev/null 2>&1; then # They exist only in documentation strings and plan output, not as executed commands pass_count "no scp/ssh/pacman/reboot calls (only in plan text)" else fail_count "no scp/ssh/pacman/reboot calls" "patterns found" fi fi # ----- 20. Exits with error on unknown host (no fleet yaml) ----- echo "::: unknown host exits with error" sandbox=$(bash "$sandbox_maker" no-host-manifest) set +e out=$("$sandbox/bin/ka-install" nonexistent --render 2>&1) rc=$? set -e rm -rf "$sandbox" if [ "$rc" -eq 2 ]; then pass_count "unknown host exits error" else fail_count "unknown host exits error" "exit=$rc" fi echo echo "====================" printf '%s\n' "${results[@]}" echo "====================" echo "passed: $pass" echo "failed: $fail" [ "$fail" -eq 0 ] || exit 1