#!/bin/bash # ============================================================================ # ODS Support Bundle Test Suite # ============================================================================ # Verifies the standalone diagnostics bundle generator creates a useful, # redacted archive without requiring Docker. # # Usage: bash tests/test-support-bundle.sh # ============================================================================ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" SUPPORT_SCRIPT="$ROOT_DIR/scripts/ods-support-bundle.sh" GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' PASSED=0 FAILED=0 SKIPPED=0 pass() { echo -e " ${GREEN}PASS${NC} $1"; PASSED=$((PASSED + 1)); } fail() { echo -e " ${RED}FAIL${NC} $1"; FAILED=$((FAILED + 1)); } skip() { echo -e " ${YELLOW}SKIP${NC} $1"; SKIPPED=$((SKIPPED + 1)); } echo "" echo "ODS Support Bundle Test Suite" echo "======================================" TMP_DIR="$(mktemp -d)" ENV_PATH="$ROOT_DIR/.env" ENV_BACKUP="" HAD_ENV=false BASH_WRAPPER_LOG="$TMP_DIR/bash-wrapper.log" REAL_BASH="$(command -v bash)" BASH_WRAPPER="$TMP_DIR/bash-wrapper" cleanup() { if [[ "$HAD_ENV" == "true" && -n "$ENV_BACKUP" && -f "$ENV_BACKUP" ]]; then cp "$ENV_BACKUP" "$ENV_PATH" else rm -f "$ENV_PATH" fi rm -rf "$TMP_DIR" } trap cleanup EXIT if [[ -f "$ENV_PATH" ]]; then HAD_ENV=true ENV_BACKUP="$TMP_DIR/env.backup" cp "$ENV_PATH" "$ENV_BACKUP" fi if [[ -f "$SUPPORT_SCRIPT" ]]; then pass "support bundle script exists" else fail "support bundle script is missing" exit 1 fi if bash -n "$SUPPORT_SCRIPT"; then pass "support bundle script passes bash syntax check" else fail "support bundle script has a syntax error" fi if bash "$SUPPORT_SCRIPT" --help | grep -q "Create a redacted diagnostics bundle"; then pass "--help describes support bundle behavior" else fail "--help output is missing expected text" fi cat > "$BASH_WRAPPER" <> "$BASH_WRAPPER_LOG" exec "$REAL_BASH" "\$@" EOF chmod +x "$BASH_WRAPPER" SECRET_VALUE="support-bundle-super-secret" cat > "$ENV_PATH" < "$RESULT_JSON"; then pass "support bundle command succeeds with Docker disabled" else fail "support bundle command failed with Docker disabled" fi if grep -q 'scripts/ods-doctor.sh' "$BASH_WRAPPER_LOG" && grep -q 'scripts/resolve-compose-stack.sh' "$BASH_WRAPPER_LOG"; then pass "support bundle uses selected Bash for nested project scripts" else fail "support bundle did not use selected Bash for nested project scripts" fi if python3 -m json.tool "$RESULT_JSON" >/dev/null; then pass "--json output is valid JSON" else fail "--json output is not valid JSON" fi read_bundle_field() { python3 - "$RESULT_JSON" "$1" <<'PY' import json import sys payload = json.load(open(sys.argv[1], encoding="utf-8")) print(payload[sys.argv[2]]) PY } BUNDLE_DIR="$(read_bundle_field bundle_dir)" ARCHIVE_PATH="$(read_bundle_field archive)" MANIFEST_PATH="$(read_bundle_field manifest)" if [[ "$BUNDLE_DIR" != *\\* ]] && [[ "$ARCHIVE_PATH" != *\\* ]]; then pass "--json paths are POSIX-safe (no backslashes)" else fail "--json paths contain backslashes — not portable across shells" fi if [[ -d "$BUNDLE_DIR" ]]; then pass "bundle directory is created" else fail "bundle directory was not created" fi if [[ -f "$ARCHIVE_PATH" ]]; then pass "bundle archive is created" else fail "bundle archive was not created" fi if [[ -f "$MANIFEST_PATH" ]] && python3 -m json.tool "$MANIFEST_PATH" >/dev/null; then pass "manifest.json is valid JSON" else fail "manifest.json is missing or invalid" fi TAR_LIST="$TMP_DIR/archive-list.txt" tar -tzf "$ARCHIVE_PATH" > "$TAR_LIST" if grep -q "/manifest.json$" "$TAR_LIST"; then pass "archive contains manifest.json" else fail "archive does not contain manifest.json" fi if grep -q "/manifest/evidence.json$" "$TAR_LIST"; then pass "archive contains manifest/evidence.json" else fail "archive does not contain manifest/evidence.json" fi if [[ -f "$BUNDLE_DIR/config/env.redacted" ]] && grep -q "DASHBOARD_API_KEY=\\[REDACTED\\]" "$BUNDLE_DIR/config/env.redacted"; then pass ".env is included only as redacted env" else fail "redacted env file is missing expected redaction" fi if grep -R "$SECRET_VALUE" "$BUNDLE_DIR" >/dev/null 2>&1; then fail "raw test secret leaked into bundle directory" else pass "raw test secret is absent from bundle directory" fi EVIDENCE_PATH="$BUNDLE_DIR/manifest/evidence.json" if [[ -f "$EVIDENCE_PATH" ]] && python3 -m json.tool "$EVIDENCE_PATH" >/dev/null; then pass "evidence.json is valid JSON" else fail "evidence.json is missing or invalid" fi if python3 - "$EVIDENCE_PATH" <<'PY' import json import sys evidence = json.load(open(sys.argv[1], encoding="utf-8")) assert evidence["version"] == "1" assert "platform" in evidence assert isinstance(evidence["platform"]["wsl"], bool) assert "backend" in evidence assert "inference_contract" in evidence assert "compose" in evidence assert "config_hashes" in evidence assert evidence["env_keys"]["DASHBOARD_API_KEY"]["redacted"] is True assert evidence["env_keys"]["DASHBOARD_API_KEY"]["value"] is None assert evidence["env_keys"]["NORMAL_VALUE"]["value"] == "visible-value" assert "docker-compose.cloud.yml" in evidence["compose"]["files"] assert "docker-compose.cpu.yml" not in evidence["compose"]["files"] assert evidence["inference_contract"]["issue_counts"]["blockers"] == 0 PY then pass "evidence.json records redacted env and support metadata" else fail "evidence.json is missing expected support metadata" fi ENV_MEMBER="$(grep '/config/env.redacted$' "$TAR_LIST" | sed -n '1p')" ARCHIVE_ENV="$TMP_DIR/env-from-archive" if [[ -z "$ENV_MEMBER" ]]; then fail "archive does not contain config/env.redacted" elif ! tar -xOf "$ARCHIVE_PATH" "$ENV_MEMBER" > "$ARCHIVE_ENV"; then fail "could not extract config/env.redacted from archive" elif grep -q "$SECRET_VALUE" "$ARCHIVE_ENV"; then fail "raw test secret leaked into bundle archive" else pass "raw test secret is absent from bundle archive env" fi if [[ -f "$BUNDLE_DIR/docker/unavailable.txt" ]]; then pass "Docker-disabled run records Docker as unavailable" else fail "Docker-disabled run did not record Docker unavailable state" fi if python3 - "$MANIFEST_PATH" <<'PY' import json import sys manifest = json.load(open(sys.argv[1], encoding="utf-8")) assert manifest["tool"] == "ods-support-bundle" assert isinstance(manifest["files"], list) and manifest["files"] assert isinstance(manifest["commands"], list) and manifest["commands"] assert any(command["label"] == "docker-version" for command in manifest["commands"]) assert any(item["path"] == "manifest/evidence.json" for item in manifest["files"]) PY then pass "manifest records files and command exit codes" else fail "manifest is missing expected file/command metadata" fi echo "" echo "Result: $PASSED passed, $FAILED failed, $SKIPPED skipped" [[ "$FAILED" -eq 0 ]]