#!/usr/bin/env bash # Regression test for copyable installation examples and secret-path validation. set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd -P)" output="$(mktemp "${TMPDIR:-/tmp}/thoth-install-docs-test.XXXXXX")" verifier_functions="$(mktemp "${TMPDIR:-/tmp}/thoth-install-docs-functions.XXXXXX")" negative_root="$(mktemp -d "${TMPDIR:-/tmp}/thoth-install-docs-negative.XXXXXX")" trap 'rm -f "$output" "$verifier_functions"; rm -rf "$negative_root"' EXIT HUP INT TERM "$root/scripts/verify-workspace-install-docs.sh" --fixtures-only >"$output" for fixture in \ "local installation guide contract" \ "Windows line-ending recovery guide contract" \ "Pi management guide contract" \ "local installation example rendered from path with spaces" \ "local manual canonical base+override references" \ "server manual canonical base+override references" \ "canonical local base+override fixture" \ "canonical server base+override fixture" \ "relative secret-source fixture rejected" \ "CRLF recovery rewrites worktree bytes"; do grep -Fqx "$fixture passed" "$output" >/dev/null || { echo "missing fixture verification: $fixture" >&2 cat "$output" >&2 exit 1 } done for manual in \ "$root/docs/install/local-workspace-registry.md" \ "$root/docs/install/server-workspace-registry.md"; do grep -Fq 'export THT_SOURCE_ROOT=/absolute/path/to/ThothII' "$manual" || { echo "installation manual does not publish a self-contained THT_SOURCE_ROOT export: $manual" >&2 exit 1 } grep -Fq 'export THT_WORKSPACE_BINDINGS_ENV_FILE=' "$manual" || { echo "installation manual does not publish a self-contained bindings export: $manual" >&2 exit 1 } if rg -n 'source[[:space:]]+\.env' "$manual"; then echo "installation manual unsafely imports operator .env: $manual" >&2 exit 1 fi done if rg -n 'local-compose\.workspace-registry|server-compose\.workspace-registry|connector-secrets\.workspace-registry|docker compose' \ "$root/docs/install/local-workspace-registry.md" \ "$root/docs/install/server-workspace-registry.md"; then echo "installation manuals still document a bypassed Compose or copied connector override path" >&2 exit 1 fi # Load only the verifier's function definitions so each deliberately unsafe guide can be checked # in isolation without invoking Docker-backed Compose fixtures. sed '/^case "\$mode" in/,$d' "$root/scripts/verify-workspace-install-docs.sh" >"$verifier_functions" # shellcheck source=/dev/null source "$verifier_functions" negative_failures=0 expect_guide_rejected() { local label="$1" validator="$2" source_guide="$3" relative_path="$4" local mutation="$5" expected_error="$6" local fixture_root="$negative_root/${label// /-}" local fixture_output="$fixture_root/output" mkdir -p "$fixture_root/$(dirname "$relative_path")" cp "$source_guide" "$fixture_root/$relative_path" node - "$fixture_root/$relative_path" "$mutation" <<'NODE' const fs = require("fs"); const [path, mutation] = process.argv.slice(2); const original = fs.readFileSync(path, "utf8"); let changed = original; switch (mutation) { case "durable-selector": changed = original.replaceAll("--source build", "--source stale-build"); break; case "dangerous-volumes": changed = original.replace("Do **not** run `docker compose down --volumes`", "Run `docker compose down --volumes`"); break; case "incomplete-powershell": changed = original.replaceAll("icacls.exe", "Write-Output"); break; case "broken-crlf": changed = original.replaceAll("git checkout-index --all --force --prefix=", "git add --renormalize . # "); break; case "raw-pi": changed += "\n```sh\ndocker compose exec core pi --version\n```\n"; break; default: throw new Error(`unknown negative-fixture mutation: ${mutation}`); } if (changed === original) throw new Error(`negative-fixture mutation made no change: ${mutation}`); fs.writeFileSync(path, changed); NODE set +e (root="$fixture_root"; set -e; "$validator") >"$fixture_output" 2>&1 local status=$? set -e if [[ $status -eq 0 ]]; then echo "negative fixture accepted: $label" >&2 cat "$fixture_output" >&2 negative_failures=$((negative_failures + 1)) elif ! grep -Fq -- "$expected_error" "$fixture_output"; then echo "negative fixture failed for the wrong reason: $label" >&2 cat "$fixture_output" >&2 negative_failures=$((negative_failures + 1)) fi } expect_guide_rejected \ "durable selector keeps old core" verify_local_guide \ "$root/docs/install/local.md" docs/install/local.md durable-selector \ "installation-aware source update lacks structural token: --source build" expect_guide_rejected \ "dangerous down volumes instruction" verify_local_guide \ "$root/docs/install/local.md" docs/install/local.md dangerous-volumes \ "docker compose down --volumes must appear only in an explicit prose prohibition" expect_guide_rejected \ "incomplete native PowerShell path" verify_local_guide \ "$root/docs/install/local.md" docs/install/local.md incomplete-powershell \ "native PowerShell setup lacks structural token: icacls.exe" expect_guide_rejected \ "renormalize leaves CRLF worktree bytes" verify_windows_line_endings_guide \ "$root/docs/install/windows-line-endings.md" docs/install/windows-line-endings.md broken-crlf \ "Windows line-ending guide lacks required instruction: git checkout-index --all --force" expect_guide_rejected \ "raw non-installation-aware Pi access" verify_pi_management_guide \ "$root/docs/install/pi-management.md" docs/install/pi-management.md raw-pi \ "raw non-installation-aware Compose Pi access is forbidden" if (( negative_failures != 0 )); then echo "$negative_failures unsafe installation-document fixtures were accepted" >&2 exit 1 fi echo "unsafe installation-document fixtures rejected passed"