9e8f1bbeed
Dashboard / frontend (push) Failing after 0s
Dashboard / api (push) Failing after 0s
Lint PowerShell / powershell-lint (ubuntu-latest) (push) Failing after 1s
Python Lint / Lint Python with Ruff (push) Failing after 1s
ShellCheck / Lint shell scripts (push) Failing after 1s
Matrix Smoke / linux-smoke (push) Failing after 1s
Matrix Smoke / distro: cachyos (push) Failing after 15s
Matrix Smoke / distro: linux-mint-21.3 (push) Failing after 15s
Matrix Smoke / distro: debian-12 (push) Failing after 5m21s
Matrix Smoke / distro: fedora-41 (push) Failing after 4m56s
Matrix Smoke / distro: ubuntu-24.04 (push) Failing after 2m13s
Matrix Smoke / distro: rocky-9 (push) Failing after 10m39s
Matrix Smoke / distro: manjaro (push) Failing after 12m11s
Matrix Smoke / distro: opensuse-tw (push) Failing after 11m53s
Matrix Smoke / distro: archlinux (push) Failing after 20m3s
Matrix Smoke / distro: ubuntu-22.04 (push) Failing after 13m49s
Validate .env Schema / tier-1-env-validation (push) Successful in 52s
Validate .env Schema / tier-2-env-validation (push) Successful in 44s
Validate .env Schema / tier-3-env-validation (push) Successful in 52s
Validate .env Schema / tier-4-env-validation (push) Successful in 51s
Validate Extensions Catalog / Check catalog is up-to-date (push) Failing after 9m47s
Secret Scan / Scan for secrets (push) Failing after 21m4s
Validate Docker Compose / Validate Docker Compose files (push) Has been cancelled
Python Type Check / Type check with mypy (push) Has been cancelled
Validate .env Schema / tier-0-env-validation (push) Has been cancelled
Test Linux / integration-smoke (push) Has been cancelled
Lint PowerShell / powershell-lint (windows-latest) (push) Has been cancelled
Matrix Smoke / macos-smoke (push) Has been cancelled
249 lines
7.4 KiB
Bash
Executable File
249 lines
7.4 KiB
Bash
Executable File
#!/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" <<EOF
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' "\$*" >> "$BASH_WRAPPER_LOG"
|
|
exec "$REAL_BASH" "\$@"
|
|
EOF
|
|
chmod +x "$BASH_WRAPPER"
|
|
|
|
SECRET_VALUE="support-bundle-super-secret"
|
|
cat > "$ENV_PATH" <<EOF
|
|
DASHBOARD_API_KEY=$SECRET_VALUE
|
|
OPENAI_API_KEY=$SECRET_VALUE
|
|
NORMAL_VALUE=visible-value
|
|
ODS_MODE=cloud
|
|
GPU_BACKEND=cpu
|
|
GPU_COUNT=1
|
|
LLM_API_URL=http://litellm:4000
|
|
HERMES_LLM_BASE_URL=http://litellm:4000/v1
|
|
EOF
|
|
|
|
OUTPUT_DIR="$TMP_DIR/out"
|
|
RESULT_JSON="$TMP_DIR/result.json"
|
|
|
|
if ODS_SUPPORT_BUNDLE_BASH="$BASH_WRAPPER" ODS_SUPPORT_BUNDLE_DISABLE_DOCKER=1 bash "$SUPPORT_SCRIPT" --output "$OUTPUT_DIR" --no-logs --json > "$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 ]]
|