diff --git a/jeannie b/jeannie index 2dfff26..4552558 100755 --- a/jeannie +++ b/jeannie @@ -4344,7 +4344,7 @@ cluster_worker_targets() { if [[ -s "${var_file}" ]] && command -v python3 >/dev/null 2>&1; then while IFS= read -r target; do [[ -n "${target}" ]] || continue - if [[ ! " ${CLUSTER_WORKER_TARGETS[*]} " =~ [[:space:]]${target}[[:space:]] ]]; then + if [[ ! " ${CLUSTER_WORKER_TARGETS[*]-} " =~ [[:space:]]${target}[[:space:]] ]]; then CLUSTER_WORKER_TARGETS+=("${target}") fi done < <(python3 - "${var_file}" <<'PY' @@ -6585,6 +6585,11 @@ Destructive EOF } +if [[ "${JEANNIE_LIBRARY_MODE:-false}" == "true" ]]; then + # shellcheck disable=SC2317 + return 0 2>/dev/null || exit 0 +fi + case "${1:-}" in "" | help | -h | --help) print_usage diff --git a/scripts/validate-homelab b/scripts/validate-homelab index 707d43c..8457f7a 100755 --- a/scripts/validate-homelab +++ b/scripts/validate-homelab @@ -87,9 +87,14 @@ validate_security_static() { fi } +validate_unit_tests() { + "${REPO_ROOT}/tests/jeannie-unit" +} + main() { cd "${REPO_ROOT}" run_step "Inventory" "${REPO_ROOT}/scripts/validate-homelab-inventory" "${HOMELAB_INVENTORY_FILE:-${REPO_ROOT}/homelab.yml}" + run_step "Unit tests" validate_unit_tests run_step "Generated docs" "${REPO_ROOT}/scripts/render-docs" --check run_step "Bash syntax" validate_bash_syntax run_step "Shellcheck" validate_shellcheck diff --git a/tests/jeannie-unit b/tests/jeannie-unit new file mode 100755 index 0000000..116484e --- /dev/null +++ b/tests/jeannie-unit @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +LAB_DIR="${REPO_ROOT}/.lab" +TMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/jeannie-unit.XXXXXX")" +BACKUP_DIR="${TMP_DIR}/backup" +FAILURES=0 + +mkdir -p "${BACKUP_DIR}" "${LAB_DIR}" "${TMP_DIR}/bin" + +cleanup() { + for file in pimox-workers.tsv cluster-workers.auto.tfvars.json; do + rm -f "${LAB_DIR}/${file}" + if [[ -f "${BACKUP_DIR}/${file}" ]]; then + cp "${BACKUP_DIR}/${file}" "${LAB_DIR}/${file}" + fi + done + rm -rf "${TMP_DIR}" +} +trap cleanup EXIT + +for file in pimox-workers.tsv cluster-workers.auto.tfvars.json; do + if [[ -f "${LAB_DIR}/${file}" ]]; then + cp "${LAB_DIR}/${file}" "${BACKUP_DIR}/${file}" + fi +done + +cat >"${TMP_DIR}/bin/tofu" <<'EOF' +#!/usr/bin/env bash +exit 1 +EOF +chmod +x "${TMP_DIR}/bin/tofu" +export PATH="${TMP_DIR}/bin:${PATH}" + +cat >"${LAB_DIR}/pimox-workers.tsv" <<'EOF' +pimox01 192.168.100.66 jv pimox-worker-01 /home/jv/.ssh/id_ed25519 +pimox02 192.168.100.67 jv pimox-worker-02 /home/jv/.ssh/id_ed25519 +pimox03 192.168.100.76 jv pimox-worker-03 /home/jv/.ssh/id_ed25519 +EOF + +cat >"${LAB_DIR}/cluster-workers.auto.tfvars.json" <<'EOF' +{ + "worker_nodes": { + "pimox01": { + "host": "192.168.100.66", + "user": "jv", + "node_name": "pimox-worker-01", + "ssh_private_key_path": "/home/jv/.ssh/id_ed25519" + }, + "pimox02": { + "host": "192.168.100.67", + "user": "jv", + "node_name": "pimox-worker-02", + "ssh_private_key_path": "/home/jv/.ssh/id_ed25519" + }, + "pimox03": { + "host": "192.168.100.76", + "user": "jv", + "node_name": "pimox-worker-03", + "ssh_private_key_path": "/home/jv/.ssh/id_ed25519" + }, + "manual01": { + "host": "192.168.100.99", + "user": "jv", + "node_name": "manual-worker-01", + "ssh_private_key_path": "/home/jv/.ssh/id_ed25519" + } + } +} +EOF + +export JEANNIE_LIBRARY_MODE=true +# shellcheck disable=SC1091 +source "${REPO_ROOT}/jeannie" + +pass() { + printf 'ok %s\n' "$1" +} + +fail() { + printf 'fail %s\n' "$1" + FAILURES=$((FAILURES + 1)) +} + +assert_eq() { + local label="$1" + local expected="$2" + local actual="$3" + + if [[ "${actual}" == "${expected}" ]]; then + pass "${label}" + else + fail "${label}: expected '${expected}', got '${actual}'" + fi +} + +test_pimox_worker_count_detects_generated_topology() { + unset LAB_PIMOX_WORKER_COUNT + assert_eq "detects 3 generated Pimox workers" "3" "$(pimox_worker_count_default)" +} + +test_configured_worker_count_cannot_hide_existing_workers() { + LAB_PIMOX_WORKER_COUNT=1 + assert_eq "effective count keeps detected 3 workers" "3" "$(pimox_worker_count_effective)" + unset LAB_PIMOX_WORKER_COUNT +} + +test_configured_worker_count_can_expand_target_set() { + LAB_PIMOX_WORKER_COUNT=4 + assert_eq "effective count allows explicit expansion" "4" "$(pimox_worker_count_effective)" + unset LAB_PIMOX_WORKER_COUNT +} + +test_cluster_worker_targets_include_pimox_and_manual_workers() { + CLUSTER_WORKER_TARGETS=() + cluster_worker_targets + local targets + targets="$(printf '%s\n' "${CLUSTER_WORKER_TARGETS[@]}" | sort | paste -sd ',' -)" + assert_eq "cluster targets include generated Pimox workers" "jv@192.168.100.66,jv@192.168.100.67,jv@192.168.100.76,jv@192.168.100.99" "${targets}" +} + +test_report_renderer_hides_ok_links() { + local output + output="$( + printf 'ok\tArea\tHealthy\tok\tdetail\tfix\tgraph\tquery\nwarn\tArea\tWarning\tneeds attention\tdetail\tfix\tgraph\tquery\n' | + "${REPO_ROOT}/scripts/report-render" --title "Unit Report" --only all + )" + if grep -A4 'Healthy' <<<"${output}" | grep -Eq 'graph:|query:'; then + fail "report renderer hides graph/query for ok rows" + else + pass "report renderer hides graph/query for ok rows" + fi +} + +test_pimox_worker_count_detects_generated_topology +test_configured_worker_count_cannot_hide_existing_workers +test_configured_worker_count_can_expand_target_set +test_cluster_worker_targets_include_pimox_and_manual_workers +test_report_renderer_hides_ok_links + +if ((FAILURES > 0)); then + printf '\n%s unit test(s) failed.\n' "${FAILURES}" >&2 + exit 1 +fi + +printf '\nAll jeannie unit tests passed.\n'