#!/usr/bin/env bash # Exercises the registry through an isolated Compose project. An optional WORKSPACE_GIT_REMOTE # is contacted read-only as a connectivity preflight; all pull/fallback mutations target a fresh # temporary bare repository so this smoke test can never alter an operator's shared registry. set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd -P)" tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")" tmp_slug="$(basename "$tmp" | tr '[:upper:]._' '[:lower:]--' | tr -cd 'a-z0-9-')" project="thoth-workspace-registry-smoke-${tmp_slug}-$$" image="thothii-workspace-registry-smoke:${project}" remote="$tmp/remote.git" seed="$tmp/seed" branch="workspace-registry-smoke" core_remote="/fixtures/remote.git" cleanup_smoke_image() { docker image rm -f "$image" >/dev/null 2>&1 || true } workspace_registry_smoke_leftovers() { { docker ps -a --filter "label=com.docker.compose.project=$project" -q docker volume ls --filter "label=com.docker.compose.project=$project" -q docker network ls --filter "label=com.docker.compose.project=$project" -q docker image inspect --format '{{.Id}}' "$image" 2>/dev/null || true } | sed '/^$/d' } workspace_registry_smoke_self_test_image_cleanup_identity() { local calls exact_image foreign_project foreign_tag leftovers calls="$(mktemp "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke-image-contract.XXXXXX")" project="thoth-workspace-registry-smoke-selftest-123" exact_image="thothii-workspace-registry-smoke:${project}" foreign_project="thothii-workspace-registry-smoke:thoth-workspace-registry-smoke-foreign-456" foreign_tag="thothii-workspace-registry-smoke:local" image="$exact_image" docker() { printf '%s\n' "docker $*" >>"$calls" case "$1 $2" in "image rm") [[ "$3" == "-f" ]] || return 41 [[ "$4" == "$exact_image" ]] || return 42 return 0 ;; "image inspect") [[ "$3" == "--format" ]] || return 43 [[ "$5" == "$exact_image" ]] || return 44 return 1 ;; "ps -a"|"volume ls"|"network ls") [[ "$*" == *"label=com.docker.compose.project=$project"* ]] || return 45 return 0 ;; *) return 46 ;; esac } cleanup_smoke_image leftovers="$(workspace_registry_smoke_leftovers)" [[ -z "$leftovers" ]] || { echo "self-test observed leftovers for the per-run image" >&2 printf '%s\n' "$leftovers" >&2 return 1 } grep -Fq "docker image rm -f $exact_image" "$calls" \ || { echo "self-test did not remove the exact per-run image reference" >&2; return 1; } if grep -Fq "$foreign_project" "$calls" || grep -Fq "$foreign_tag" "$calls"; then echo "self-test cleanup touched a foreign workspace-registry smoke image reference" >&2 return 1 fi echo "workspace registry smoke image cleanup identity self-test passed" } if [[ "${WORKSPACE_REGISTRY_SMOKE_SELF_TEST:-}" == "image-cleanup-identity" ]]; then workspace_registry_smoke_self_test_image_cleanup_identity exit 0 fi cleanup() { local cleanup_status=$? compose down --volumes --remove-orphans >/dev/null 2>&1 || true cleanup_smoke_image if [[ "$cleanup_status" -eq 0 ]]; then local leftovers leftovers="$(workspace_registry_smoke_leftovers)" if [[ -n "$leftovers" ]]; then echo "workspace registry cleanup left owned Docker resources:" >&2 printf '%s\n' "$leftovers" >&2 cleanup_status=1 else echo "workspace registry cleanup proof: no compose containers, volumes, networks, or image remain for $project." fi fi rm -rf "$tmp" exit "$cleanup_status" } trap cleanup EXIT HUP INT TERM compose() { docker compose --project-name "$project" -f - "$@" </dev/null 2>&1; then return 0 fi sleep 1 done compose logs core >&2 || true return 1 } if [[ -n "${WORKSPACE_GIT_REMOTE:-}" ]]; then echo "== Read-only Git remote preflight ==" git ls-remote --heads "$WORKSPACE_GIT_REMOTE" >/dev/null fi echo "== Seed isolated workspace registry ==" git init --bare --initial-branch=main "$remote" >/dev/null git clone "$remote" "$seed" >/dev/null git -C "$seed" checkout -b "$branch" >/dev/null npm --prefix "$root/backend" run build >/dev/null node "$root/backend/dist/workspaces/migrate-legacy.js" \ --input "$root/harness/workspaces/local.yaml" --output "$seed" --collection local >/dev/null git -C "$seed" add workspaces/local.yaml git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \ commit -m 'Seed workspace registry smoke' >/dev/null git -C "$seed" push origin "HEAD:$branch" >/dev/null echo "== Build and start isolated Compose core ==" compose up -d --build wait_for_core initial_status="$(compose exec -T core curl -fsS http://127.0.0.1:8787/workspace-registry/status)" printf '%s' "$initial_status" | grep -Eq '"head":"[0-9a-f]{40}"' compose exec -T core test -f /data/workspace-registry/state/active.json echo "== Recreate offline and prove registry-volume fallback ==" core_remote="/fixtures/offline.git" compose up -d --force-recreate wait_for_core recreated_status="$(compose exec -T core curl -fsS http://127.0.0.1:8787/workspace-registry/status)" initial_head="$(printf '%s' "$initial_status" | sed -n 's/.*"head":"\([0-9a-f]*\)".*/\1/p')" recreated_head="$(printf '%s' "$recreated_status" | sed -n 's/.*"head":"\([0-9a-f]*\)".*/\1/p')" test -n "$initial_head" && test "$initial_head" = "$recreated_head" printf '%s' "$recreated_status" | grep -Fq '"degraded":true' compose exec -T core curl -fsS http://127.0.0.1:8787/workspaces | grep -Fq 'Local' echo "== Pull a valid remote update ==" sed -i.bak 's/name: Local/name: Local Updated/' "$seed/workspaces/local.yaml" rm "$seed/workspaces/local.yaml.bak" git -C "$seed" add workspaces/local.yaml git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \ commit -m 'Update workspace registry smoke' >/dev/null git -C "$seed" push origin "HEAD:$branch" >/dev/null core_remote="/fixtures/remote.git" compose up -d --force-recreate wait_for_core compose exec -T core curl -fsS -X POST http://127.0.0.1:8787/workspace-registry/pull | grep -Eq '"head":"[0-9a-f]{40}"' compose exec -T core curl -fsS http://127.0.0.1:8787/workspaces | grep -Fq 'Local Updated' echo "== Reject invalid remote content and retain the last valid snapshot ==" printf '%s\n' 'workspace: invalid' >"$seed/workspaces/local.yaml" git -C "$seed" add workspaces/local.yaml git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \ commit -m 'Invalid workspace registry smoke fixture' >/dev/null git -C "$seed" push origin "HEAD:$branch" >/dev/null if compose exec -T core curl -fsS -X POST http://127.0.0.1:8787/workspace-registry/pull >/dev/null; then echo "registry accepted invalid remote workspace content" >&2 exit 1 fi compose exec -T core curl -fsS http://127.0.0.1:8787/workspaces | grep -Fq 'Local Updated' echo "workspace registry smoke passed"