Files
member-console/test/verify-stack-isolation.sh

188 lines
6.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Spike verification for the worktree-test-stack-isolation change.
#
# Bootstraps a stack with a forced non-default port slot, brings up Keycloak +
# its seed, and asserts that the seeded redirectUris/webOrigins on the
# member-console and temporal-ui clients reflect the templated MC_BASE_URL /
# TEMPORAL_UI_URL — not the hardcoded localhost:8081 / :8233 defaults.
#
# This is the canary for tasks 2.6 and 2.7 in tasks.md: it proves Keycloak's
# admin API actually accepts our partial PUT bodies on /clients/{id} and
# stores the values, rather than silently ignoring them.
#
# Usage:
# ./test/verify-stack-isolation.sh # uses slot 137 by default
# FORCE_SLOT=42 ./test/verify-stack-isolation.sh
#
# Side effects: brings the stack up, then tears it down on exit. Requires
# docker, jq, curl. Will refuse to run if test/.env already exists (run
# teardown-stack.sh first).
set -euo pipefail
REPO_ROOT="$(git rev-parse --show-toplevel)"
TEST_DIR="${REPO_ROOT}/test"
ENV_FILE="${TEST_DIR}/.env"
FORCE_SLOT="${FORCE_SLOT:-137}"
PORT_STEP=50
cleanup() {
local rc=$?
echo ""
echo "--- Cleaning up ---"
"${TEST_DIR}/teardown-stack.sh" >/dev/null 2>&1 || true
exit "$rc"
}
trap cleanup EXIT
if [[ -f "$ENV_FILE" ]]; then
echo "ERROR: ${ENV_FILE} already exists. Run ./test/teardown-stack.sh first." >&2
exit 1
fi
# Compute expected ports for FORCE_SLOT.
KEYCLOAK_PORT=$((8080 + FORCE_SLOT * PORT_STEP))
MC_PORT=$((8081 + FORCE_SLOT * PORT_STEP))
TEMPORAL_UI_PORT=$((8233 + FORCE_SLOT * PORT_STEP))
EXPECTED_MC_BASE_URL="http://localhost:${MC_PORT}"
EXPECTED_TEMPORAL_UI_URL="http://localhost:${TEMPORAL_UI_PORT}"
EXPECTED_KC_HOSTNAME="keycloak-slot${FORCE_SLOT}.localhost"
echo "=== Spike: forcing slot ${FORCE_SLOT} ==="
echo " Keycloak port: ${KEYCLOAK_PORT}"
echo " MC port: ${MC_PORT}"
echo " Temporal UI port: ${TEMPORAL_UI_PORT}"
# Hand-write a .env that pins the slot, bypassing bootstrap's hash so this is
# deterministic regardless of working directory name.
cat > "$ENV_FILE" <<EOF
COMPOSE_PROJECT_NAME=mc-spike-slot${FORCE_SLOT}
SLOT=${FORCE_SLOT}
POSTGRES_PORT=$((5432 + FORCE_SLOT * PORT_STEP))
VALKEY_PORT=$((6379 + FORCE_SLOT * PORT_STEP))
KEYCLOAK_PORT=${KEYCLOAK_PORT}
TEMPORAL_PORT=$((7233 + FORCE_SLOT * PORT_STEP))
TEMPORAL_UI_PORT=${TEMPORAL_UI_PORT}
FEDWIKI_PORT=$((8090 + FORCE_SLOT * PORT_STEP))
KC_HOSTNAME=${EXPECTED_KC_HOSTNAME}
MC_BASE_URL=${EXPECTED_MC_BASE_URL}
TEMPORAL_UI_URL=${EXPECTED_TEMPORAL_UI_URL}
MC_PORT=${MC_PORT}
EOF
# Ensure secrets/ exists so compose doesn't bail. Copy from main worktree if
# missing (the bootstrap script does this normally).
if [[ ! -d "${TEST_DIR}/secrets" ]]; then
MAIN_WT=$(git worktree list --porcelain | awk '/^worktree / { print $2; exit }')
if [[ -d "${MAIN_WT}/test/secrets" ]]; then
cp -r "${MAIN_WT}/test/secrets" "${TEST_DIR}/secrets"
else
echo "ERROR: no test/secrets/ in this or main worktree" >&2
exit 1
fi
fi
echo ""
echo "--- Bringing up keycloak + keycloak-seed ---"
docker compose -f "${TEST_DIR}/compose.yaml" --env-file "$ENV_FILE" \
up -d keycloak keycloak-seed
echo "Waiting for keycloak-seed to complete..."
SEED_CONTAINER=$(docker compose -f "${TEST_DIR}/compose.yaml" --env-file "$ENV_FILE" \
ps -q keycloak-seed)
for i in $(seq 1 60); do
STATE=$(docker inspect -f '{{.State.Status}}' "$SEED_CONTAINER" 2>/dev/null || echo "missing")
EXIT_CODE=$(docker inspect -f '{{.State.ExitCode}}' "$SEED_CONTAINER" 2>/dev/null || echo "")
if [[ "$STATE" == "exited" ]]; then
if [[ "$EXIT_CODE" != "0" ]]; then
echo "ERROR: keycloak-seed exited non-zero (${EXIT_CODE}). Logs:" >&2
docker logs "$SEED_CONTAINER" >&2
exit 1
fi
echo "Seed completed."
break
fi
if [[ "$i" -eq 60 ]]; then
echo "ERROR: keycloak-seed did not finish within 120s" >&2
docker logs "$SEED_CONTAINER" >&2 || true
exit 1
fi
sleep 2
done
echo ""
echo "--- Querying Keycloak admin API for stored client config ---"
KC_URL="http://localhost:${KEYCLOAK_PORT}"
TOKEN=$(curl -sf "${KC_URL}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=admin" \
-d "password=admin" | jq -r '.access_token')
if [[ -z "$TOKEN" || "$TOKEN" == "null" ]]; then
echo "ERROR: failed to obtain admin token from ${KC_URL}" >&2
exit 1
fi
kc_get() {
curl -sf -H "Authorization: Bearer ${TOKEN}" \
"${KC_URL}/admin/realms/master$1"
}
assert_eq() {
local label="$1" expected="$2" actual="$3"
if [[ "$expected" == "$actual" ]]; then
echo " PASS ${label}: ${actual}"
else
echo " FAIL ${label}"
echo " expected: ${expected}"
echo " actual: ${actual}"
FAILED=1
fi
}
FAILED=0
MC_CLIENT=$(kc_get "/clients?clientId=member-console" | jq '.[0]')
MC_REDIRECT=$(echo "$MC_CLIENT" | jq -r '.redirectUris[0]')
MC_WEBORIGIN=$(echo "$MC_CLIENT" | jq -r '.webOrigins[0]')
MC_LOGOUT=$(echo "$MC_CLIENT" | jq -r '.attributes."post.logout.redirect.uris"')
assert_eq "member-console.redirectUris[0]" "${EXPECTED_MC_BASE_URL}/*" "$MC_REDIRECT"
assert_eq "member-console.webOrigins[0]" "${EXPECTED_MC_BASE_URL}" "$MC_WEBORIGIN"
assert_eq "member-console.post.logout.redirect" "${EXPECTED_MC_BASE_URL}/*" "$MC_LOGOUT"
TUI_CLIENT=$(kc_get "/clients?clientId=temporal-ui" | jq '.[0]')
TUI_REDIRECT=$(echo "$TUI_CLIENT" | jq -r '.redirectUris[0]')
TUI_WEBORIGIN=$(echo "$TUI_CLIENT" | jq -r '.webOrigins[0]')
assert_eq "temporal-ui.redirectUris[0]" "${EXPECTED_TEMPORAL_UI_URL}/*" "$TUI_REDIRECT"
assert_eq "temporal-ui.webOrigins[0]" "${EXPECTED_TEMPORAL_UI_URL}" "$TUI_WEBORIGIN"
echo ""
echo "--- Re-running seed to verify idempotent update (task 2.6) ---"
docker compose -f "${TEST_DIR}/compose.yaml" --env-file "$ENV_FILE" \
run --rm keycloak-seed >/dev/null
# Re-fetch and re-assert (values must still match after a second seed run).
TOKEN=$(curl -sf "${KC_URL}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" -d "client_id=admin-cli" \
-d "username=admin" -d "password=admin" | jq -r '.access_token')
MC_REDIRECT2=$(kc_get "/clients?clientId=member-console" | jq -r '.[0].redirectUris[0]')
assert_eq "member-console.redirectUris[0] (after re-seed)" \
"${EXPECTED_MC_BASE_URL}/*" "$MC_REDIRECT2"
echo ""
if [[ "$FAILED" -eq 0 ]]; then
echo "=== ALL ASSERTIONS PASSED ==="
exit 0
else
echo "=== ASSERTIONS FAILED ===" >&2
exit 1
fi