disinto/tests/smoke-init.sh

282 lines
9.5 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# tests/smoke-init.sh — End-to-end smoke test for disinto init with mock Forgejo
#
# Validates the full init flow using mock Forgejo server:
# 1. Verify mock Forgejo is ready
# 2. Set up mock binaries (docker, claude, tmux)
# 3. Run disinto init
# 4. Verify Forgejo state (users, repo)
# 5. Verify local state (TOML, .env, repo clone)
# 6. Verify cron setup
#
# Required env: FORGE_URL (default: http://localhost:3000)
# Required tools: bash, curl, jq, python3, git
set -euo pipefail
FACTORY_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
FORGE_URL="${FORGE_URL:-http://localhost:3000}"
MOCK_BIN="/tmp/smoke-mock-bin"
TEST_SLUG="smoke-org/smoke-repo"
FAILED=0
fail() { printf 'FAIL: %s\n' "$*" >&2; FAILED=1; }
pass() { printf 'PASS: %s\n' "$*"; }
cleanup() {
rm -rf "$MOCK_BIN" /tmp/smoke-test-repo \
"${FACTORY_ROOT}/projects/smoke-repo.toml"
# Restore .env only if we created the backup
if [ -f "${FACTORY_ROOT}/.env.smoke-backup" ]; then
mv "${FACTORY_ROOT}/.env.smoke-backup" "${FACTORY_ROOT}/.env"
else
rm -f "${FACTORY_ROOT}/.env"
fi
}
trap cleanup EXIT
# Back up existing .env if present
if [ -f "${FACTORY_ROOT}/.env" ]; then
cp "${FACTORY_ROOT}/.env" "${FACTORY_ROOT}/.env.smoke-backup"
fi
# Start with a clean .env
printf '' > "${FACTORY_ROOT}/.env"
# ── 1. Verify mock Forgejo is ready ─────────────────────────────────────────
echo "=== 1/6 Verifying mock Forgejo at ${FORGE_URL} ==="
retries=0
api_version=""
while true; do
api_version=$(curl -sf --max-time 3 "${FORGE_URL}/api/v1/version" 2>/dev/null \
| jq -r '.version // empty' 2>/dev/null) || api_version=""
if [ -n "$api_version" ]; then
break
fi
retries=$((retries + 1))
if [ "$retries" -gt 30 ]; then
fail "Mock Forgejo API not responding after 30s"
exit 1
fi
sleep 1
done
pass "Mock Forgejo API v${api_version} (${retries}s)"
# ── 2. Set up mock binaries ─────────────────────────────────────────────────
echo "=== 2/6 Setting up mock binaries ==="
mkdir -p "$MOCK_BIN"
# ── Mock: docker ──
# Intercepts docker exec calls that disinto init --bare makes to Forgejo CLI
cat > "$MOCK_BIN/docker" << 'DOCKERMOCK'
#!/usr/bin/env bash
set -euo pipefail
FORGE_URL="${SMOKE_FORGE_URL:-${FORGE_URL:-http://localhost:3000}}"
if [ "${1:-}" = "ps" ]; then exit 0; fi
if [ "${1:-}" = "exec" ]; then
shift
while [ $# -gt 0 ] && [ "${1#-}" != "$1" ]; do
case "$1" in -u|-w|-e) shift 2 ;; *) shift ;; esac
done
shift # container name
if [ "${1:-}" = "forgejo" ] && [ "${2:-}" = "admin" ] && [ "${3:-}" = "user" ]; then
subcmd="${4:-}"
if [ "$subcmd" = "list" ]; then echo "ID Username Email"; exit 0; fi
if [ "$subcmd" = "create" ]; then
shift 4; username="" password="" email="" is_admin="false"
while [ $# -gt 0 ]; do
case "$1" in
--admin) is_admin="true"; shift ;; --username) username="$2"; shift 2 ;;
--password) password="$2"; shift 2 ;; --email) email="$2"; shift 2 ;;
--must-change-password*) shift ;; *) shift ;;
esac
done
curl -sf -X POST -H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/admin/users" \
-d "{\"username\":\"${username}\",\"password\":\"${password}\",\"email\":\"${email}\",\"must_change_password\":false}" >/dev/null 2>&1
if [ "$is_admin" = "true" ]; then
curl -sf -X PATCH -H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/admin/users/${username}" \
-d "{\"admin\":true,\"must_change_password\":false}" >/dev/null 2>&1 || true
fi
echo "New user '${username}' has been successfully created!"; exit 0
fi
if [ "$subcmd" = "change-password" ]; then
shift 4; username=""
while [ $# -gt 0 ]; do
case "$1" in --username) username="$2"; shift 2 ;; --password) shift 2 ;; --must-change-password*|--config*) shift ;; *) shift ;; esac
done
curl -sf -X PATCH -H "Content-Type: application/json" \
"${FORGE_URL}/api/v1/admin/users/${username}" \
-d "{\"must_change_password\":false}" >/dev/null 2>&1 || true
exit 0
fi
fi
fi
exit 1
DOCKERMOCK
chmod +x "$MOCK_BIN/docker"
# ── Mock: claude ──
cat > "$MOCK_BIN/claude" << 'CLAUDEMOCK'
#!/usr/bin/env bash
case "$*" in
*"auth status"*) printf '{"loggedIn":true}\n' ;;
*"--version"*) printf 'claude 1.0.0 (mock)\n' ;;
esac
exit 0
CLAUDEMOCK
chmod +x "$MOCK_BIN/claude"
# ── Mock: tmux ──
printf '#!/usr/bin/env bash\nexit 0\n' > "$MOCK_BIN/tmux"
chmod +x "$MOCK_BIN/tmux"
export PATH="$MOCK_BIN:$PATH"
pass "Mock binaries installed"
# ── 3. Run disinto init ─────────────────────────────────────────────────────
echo "=== 3/6 Running disinto init ==="
rm -f "${FACTORY_ROOT}/projects/smoke-repo.toml"
# Configure git identity (needed for git operations)
git config --global user.email "smoke@test.local"
git config --global user.name "Smoke Test"
# USER needs to be set twice: assignment then export (SC2155)
USER=$(whoami)
export USER
# Create mock git repo to avoid clone failure (mock server has no git support)
mkdir -p "/tmp/smoke-test-repo"
cd "/tmp/smoke-test-repo"
git init --quiet
git config user.email "smoke@test.local"
git config user.name "Smoke Test"
echo "# smoke-repo" > README.md
git add README.md
git commit --quiet -m "Initial commit"
export SMOKE_FORGE_URL="$FORGE_URL"
export FORGE_URL
# Skip push to mock server (no git support)
export SKIP_PUSH=true
if bash "${FACTORY_ROOT}/bin/disinto" init \
"${TEST_SLUG}" \
--bare --yes \
--forge-url "$FORGE_URL" \
--repo-root "/tmp/smoke-test-repo"; then
pass "disinto init completed successfully"
else
fail "disinto init exited non-zero"
fi
# ── 4. Verify Forgejo state ─────────────────────────────────────────────────
echo "=== 4/6 Verifying Forgejo state ==="
# Admin user exists
if curl -sf --max-time 5 "${FORGE_URL}/api/v1/users/disinto-admin" >/dev/null 2>&1; then
pass "Admin user 'disinto-admin' exists on Forgejo"
else
fail "Admin user 'disinto-admin' not found on Forgejo"
fi
# Bot users exist
for bot in dev-bot review-bot; do
if curl -sf --max-time 5 "${FORGE_URL}/api/v1/users/${bot}" >/dev/null 2>&1; then
pass "Bot user '${bot}' exists on Forgejo"
else
fail "Bot user '${bot}' not found on Forgejo"
fi
done
# Repo exists (try org path, then fallback paths)
repo_found=false
for repo_path in "${TEST_SLUG}" "dev-bot/smoke-repo" "disinto-admin/smoke-repo"; do
if curl -sf --max-time 5 "${FORGE_URL}/api/v1/repos/${repo_path}" >/dev/null 2>&1; then
pass "Repo '${repo_path}' exists on Forgejo"
repo_found=true
break
fi
done
if [ "$repo_found" = false ]; then
fail "Repo not found on Forgejo under any expected path"
fi
# ── 5. Verify local state ───────────────────────────────────────────────────
echo "=== 5/6 Verifying local state ==="
# TOML was generated
toml_path="${FACTORY_ROOT}/projects/smoke-repo.toml"
if [ -f "$toml_path" ]; then
toml_name=$(python3 -c "
import tomllib, sys
with open(sys.argv[1], 'rb') as f:
print(tomllib.load(f)['name'])
" "$toml_path" 2>/dev/null) || toml_name=""
if [ "$toml_name" = "smoke-repo" ]; then
pass "TOML generated with correct project name"
else
fail "TOML name mismatch: expected 'smoke-repo', got '${toml_name}'"
fi
else
fail "TOML not generated at ${toml_path}"
fi
# .env has tokens
env_file="${FACTORY_ROOT}/.env"
if [ -f "$env_file" ]; then
if grep -q '^FORGE_TOKEN=' "$env_file"; then
pass ".env contains FORGE_TOKEN"
else
fail ".env missing FORGE_TOKEN"
fi
if grep -q '^FORGE_REVIEW_TOKEN=' "$env_file"; then
pass ".env contains FORGE_REVIEW_TOKEN"
else
fail ".env missing FORGE_REVIEW_TOKEN"
fi
else
fail ".env not found"
fi
# Repo was cloned (mock git repo created before disinto init)
if [ -d "/tmp/smoke-test-repo/.git" ]; then
pass "Repo cloned to /tmp/smoke-test-repo"
else
fail "Repo not cloned to /tmp/smoke-test-repo"
fi
# ── 6. Verify cron setup ────────────────────────────────────────────────────
echo "=== 6/6 Verifying cron setup ==="
cron_output=$(crontab -l 2>/dev/null) || cron_output=""
if [ -n "$cron_output" ]; then
if printf '%s' "$cron_output" | grep -q 'dev-poll.sh'; then
pass "Cron includes dev-poll entry"
else
fail "Cron missing dev-poll entry"
fi
if printf '%s' "$cron_output" | grep -q 'review-poll.sh'; then
pass "Cron includes review-poll entry"
else
fail "Cron missing review-poll entry"
fi
if printf '%s' "$cron_output" | grep -q 'gardener-run.sh'; then
pass "Cron includes gardener entry"
else
fail "Cron missing gardener entry"
fi
else
fail "No cron entries found (crontab -l returned empty)"
fi
# ── Summary ──────────────────────────────────────────────────────────────────
echo ""
if [ "$FAILED" -ne 0 ]; then
echo "=== SMOKE-INIT TEST FAILED ==="
exit 1
fi
echo "=== SMOKE-INIT TEST PASSED ==="