Files
gitea-mirror/tests/e2e/run-e2e.sh
Xyndra 2e00a610cb Add E2E testing (#201)
* feat: add E2E testing infrastructure with fake GitHub, Playwright, and CI workflow

- Add fake GitHub API server (tests/e2e/fake-github-server.ts) with
  management API for seeding test data
- Add Playwright E2E test suite covering full mirror workflow:
  service health checks, user registration, config, sync, verify
- Add Docker Compose for E2E Gitea instance
- Add orchestrator script (run-e2e.sh) with cleanup
- Add GitHub Actions workflow (e2e-tests.yml) with Gitea service container
- Make GITHUB_API_URL configurable via env var for testing
- Add npm scripts: test:e2e, test:e2e:ci, test:e2e:keep, test:e2e:cleanup

* feat: add real git repos + backup config testing to E2E suite

- Create programmatic test git repos (create-test-repos.ts) with real
  commits, branches (main, develop, feature/*), and tags (v1.0.0, v1.1.0)
- Add git-server container to docker-compose serving bare repos via
  dumb HTTP protocol so Gitea can actually clone them
- Update fake GitHub server to emit reachable clone_url fields pointing
  to the git-server container (configurable via GIT_SERVER_URL env var)
- Add management endpoint POST /___mgmt/set-clone-url for runtime config
- Update E2E spec with real mirroring verification:
  * Verify repos appear in Gitea with actual content
  * Check branches, tags, commits, file content
  * Verify 4/4 repos mirrored successfully
- Add backup configuration test suite:
  * Enable/disable backupBeforeSync config
  * Toggle blockSyncOnBackupFailure
  * Trigger re-sync with backup enabled and verify activities
  * Verify config persistence across changes
- Update CI workflow to use docker compose (not service containers)
  matching the local run-e2e.sh approach
- Update cleanup.sh for git-repos directory and git-server port
- All 22 tests passing with real git content verification

* refactor: split E2E tests into focused files + add force-push tests

Split the monolithic e2e.spec.ts (1335 lines) into 5 focused spec files
and a shared helpers module:

  helpers.ts                 — constants, GiteaAPI, auth, saveConfig, utilities
  01-health.spec.ts          — service health checks (4 tests)
  02-mirror-workflow.spec.ts — full first-mirror journey (8 tests)
  03-backup.spec.ts          — backup config toggling (6 tests)
  04-force-push.spec.ts      — force-push simulation & backup verification (9 tests)
  05-sync-verification.spec.ts — dynamic repos, content integrity, reset (5 tests)

The force-push tests are the critical addition:
  F0: Record original state (commit SHAs, file content)
  F1: Rewrite source repo history (simulate force-push)
  F2: Sync to Gitea WITHOUT backup
  F3: Verify data loss — LICENSE file gone, README overwritten
  F4: Restore source, re-mirror to clean state
  F5: Enable backup, force-push again, sync through app
  F6: Verify Gitea reflects the force-push
  F7: Verify backup system was invoked (snapshot activities logged)
  F8: Restore source repo for subsequent tests

Also added to helpers.ts:
  - GiteaAPI.getBranch(), .getCommit(), .triggerMirrorSync()
  - getRepositoryIds(), triggerMirrorJobs(), triggerSyncRepo()

All 32 tests passing.

* Try to fix actions

* Try to fix the other action

* Add debug info to check why e2e action is failing

* More debug info

* Even more debug info

* E2E fix attempt #1

* E2E fix attempt #2

* more debug again

* E2E fix attempt #3

* E2E fix attempt #4

* Remove a bunch of debug info

* Hopefully fix backup bug

* Force backups to succeed
2026-03-01 07:35:13 +05:30

456 lines
19 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ────────────────────────────────────────────────────────────────────────────────
# E2E Test Orchestrator
#
# Starts all required services, runs Playwright E2E tests, and tears down.
#
# Services managed:
# 1. Gitea instance (Docker/Podman on port 3333)
# 2. Fake GitHub API (Node.js on port 4580)
# 3. gitea-mirror app (Astro dev server on port 4321)
#
# Usage:
# ./tests/e2e/run-e2e.sh # full run (cleanup → start → test → teardown)
# ./tests/e2e/run-e2e.sh --no-build # skip the Astro build step
# ./tests/e2e/run-e2e.sh --keep # don't tear down services after tests
# ./tests/e2e/run-e2e.sh --ci # CI-friendly mode (stricter, no --keep)
#
# Environment variables:
# GITEA_PORT (default: 3333)
# FAKE_GITHUB_PORT (default: 4580)
# APP_PORT (default: 4321)
# SKIP_CLEANUP (default: false) set "true" to skip initial cleanup
# BUN_CMD (default: auto-detected bun or "npx --yes bun")
# ────────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# ─── Resolve paths ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.e2e.yml"
# ─── Configuration ────────────────────────────────────────────────────────────
GITEA_PORT="${GITEA_PORT:-3333}"
FAKE_GITHUB_PORT="${FAKE_GITHUB_PORT:-4580}"
APP_PORT="${APP_PORT:-4321}"
GIT_SERVER_PORT="${GIT_SERVER_PORT:-4590}"
GITEA_URL="http://localhost:${GITEA_PORT}"
FAKE_GITHUB_URL="http://localhost:${FAKE_GITHUB_PORT}"
APP_URL="http://localhost:${APP_PORT}"
GIT_SERVER_URL="http://localhost:${GIT_SERVER_PORT}"
# URL that Gitea (inside Docker) uses to reach the git-server container
GIT_SERVER_INTERNAL_URL="http://git-server"
NO_BUILD=false
KEEP_RUNNING=false
CI_MODE=false
for arg in "$@"; do
case "$arg" in
--no-build) NO_BUILD=true ;;
--keep) KEEP_RUNNING=true ;;
--ci) CI_MODE=true ;;
--help|-h)
echo "Usage: $0 [--no-build] [--keep] [--ci]"
exit 0
;;
esac
done
# ─── Detect tools ─────────────────────────────────────────────────────────────
# Container runtime
COMPOSE_CMD=""
CONTAINER_CMD=""
if command -v podman-compose &>/dev/null; then
COMPOSE_CMD="podman-compose"
CONTAINER_CMD="podman"
elif command -v docker-compose &>/dev/null; then
COMPOSE_CMD="docker-compose"
CONTAINER_CMD="docker"
elif command -v docker &>/dev/null && docker compose version &>/dev/null 2>&1; then
COMPOSE_CMD="docker compose"
CONTAINER_CMD="docker"
else
echo "ERROR: No container compose tool found. Install docker-compose or podman-compose."
exit 1
fi
# Bun or fallback
if command -v bun &>/dev/null; then
BUN_CMD="${BUN_CMD:-bun}"
elif command -v npx &>/dev/null; then
# Use npx to run bun commands works on CI with setup-bun action
BUN_CMD="${BUN_CMD:-npx --yes bun}"
else
echo "ERROR: Neither bun nor npx found."
exit 1
fi
# Node/tsx for the fake GitHub server
if command -v tsx &>/dev/null; then
TSX_CMD="tsx"
elif command -v npx &>/dev/null; then
TSX_CMD="npx --yes tsx"
else
echo "ERROR: Neither tsx nor npx found."
exit 1
fi
echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ E2E Test Orchestrator ║"
echo "╠══════════════════════════════════════════════════════════════╣"
echo "║ Container runtime : $COMPOSE_CMD"
echo "║ Bun command : $BUN_CMD"
echo "║ TSX command : $TSX_CMD"
echo "║ Gitea URL : $GITEA_URL"
echo "║ Fake GitHub URL : $FAKE_GITHUB_URL"
echo "║ App URL : $APP_URL"
echo "║ Git Server URL : $GIT_SERVER_URL"
echo "║ Git Server (int) : $GIT_SERVER_INTERNAL_URL"
echo "║ CI mode : $CI_MODE"
echo "╚══════════════════════════════════════════════════════════════╝"
echo ""
# ─── PID tracking for cleanup ─────────────────────────────────────────────────
FAKE_GITHUB_PID=""
APP_PID=""
EXIT_CODE=0
cleanup_on_exit() {
local code=$?
echo ""
echo "────────────────────────────────────────────────────────────────"
echo "[teardown] Cleaning up..."
# Kill fake GitHub server
if [[ -n "$FAKE_GITHUB_PID" ]] && kill -0 "$FAKE_GITHUB_PID" 2>/dev/null; then
echo "[teardown] Stopping fake GitHub server (PID $FAKE_GITHUB_PID)..."
kill "$FAKE_GITHUB_PID" 2>/dev/null || true
wait "$FAKE_GITHUB_PID" 2>/dev/null || true
fi
rm -f "$SCRIPT_DIR/.fake-github.pid"
# Kill app server
if [[ -n "$APP_PID" ]] && kill -0 "$APP_PID" 2>/dev/null; then
echo "[teardown] Stopping gitea-mirror app (PID $APP_PID)..."
kill "$APP_PID" 2>/dev/null || true
wait "$APP_PID" 2>/dev/null || true
fi
rm -f "$SCRIPT_DIR/.app.pid"
# Stop containers (unless --keep)
if [[ "$KEEP_RUNNING" == false ]]; then
if [[ -n "$COMPOSE_CMD" ]] && [[ -f "$COMPOSE_FILE" ]]; then
echo "[teardown] Stopping Gitea container..."
$COMPOSE_CMD -f "$COMPOSE_FILE" down --volumes --remove-orphans 2>/dev/null || true
fi
else
echo "[teardown] --keep flag set, leaving services running"
fi
echo "[teardown] Done."
# Use the test exit code, not the cleanup exit code
if [[ $EXIT_CODE -ne 0 ]]; then
exit $EXIT_CODE
fi
exit $code
}
trap cleanup_on_exit EXIT INT TERM
# ─── Step 0: Cleanup previous run ────────────────────────────────────────────
if [[ "${SKIP_CLEANUP:-false}" != "true" ]]; then
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 0: Cleanup previous E2E run │"
echo "└──────────────────────────────────────────────────────────────┘"
bash "$SCRIPT_DIR/cleanup.sh" --soft 2>/dev/null || true
echo ""
fi
# ─── Step 1: Install dependencies ────────────────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 1: Install dependencies │"
echo "└──────────────────────────────────────────────────────────────┘"
cd "$PROJECT_ROOT"
$BUN_CMD install 2>&1 | tail -5
echo "[deps] ✓ Dependencies installed"
# Install Playwright browsers if needed
if ! npx playwright install --dry-run chromium &>/dev/null 2>&1; then
echo "[deps] Installing Playwright browsers..."
npx playwright install chromium 2>&1 | tail -3
fi
# Always ensure system deps are available (needed in CI/fresh environments)
if [[ "$CI_MODE" == true ]]; then
echo "[deps] Installing Playwright system dependencies..."
npx playwright install-deps chromium 2>&1 | tail -5 || true
fi
echo "[deps] ✓ Playwright ready"
echo ""
# ─── Step 1.5: Create test git repositories ─────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 1.5: Create test git repositories │"
echo "└──────────────────────────────────────────────────────────────┘"
GIT_REPOS_DIR="$SCRIPT_DIR/git-repos"
echo "[git-repos] Creating bare git repos in $GIT_REPOS_DIR ..."
$BUN_CMD run "$SCRIPT_DIR/create-test-repos.ts" --output-dir "$GIT_REPOS_DIR" 2>&1
if [[ ! -f "$GIT_REPOS_DIR/manifest.json" ]]; then
echo "ERROR: Test git repos were not created (manifest.json missing)"
EXIT_CODE=1
exit 1
fi
echo "[git-repos] ✓ Test repositories created"
echo ""
# ─── Step 2: Build the app ──────────────────────────────────────────────────
if [[ "$NO_BUILD" == false ]]; then
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 2: Build gitea-mirror │"
echo "└──────────────────────────────────────────────────────────────┘"
cd "$PROJECT_ROOT"
# Initialize the database
echo "[build] Initializing database..."
$BUN_CMD run manage-db init 2>&1 | tail -3 || true
# Build the Astro project
echo "[build] Building Astro project..."
GITHUB_API_URL="$FAKE_GITHUB_URL" \
BETTER_AUTH_SECRET="e2e-test-secret" \
$BUN_CMD run build 2>&1 | tail -10
echo "[build] ✓ Build complete"
echo ""
else
echo "[build] Skipped (--no-build flag)"
echo ""
fi
# ─── Step 3: Start Gitea container ──────────────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 3: Start Gitea container │"
echo "└──────────────────────────────────────────────────────────────┘"
$COMPOSE_CMD -f "$COMPOSE_FILE" up -d 2>&1
# Wait for git-server to be healthy first (Gitea depends on it)
echo "[git-server] Waiting for git HTTP server..."
GIT_SERVER_READY=false
for i in $(seq 1 30); do
if curl -sf "${GIT_SERVER_URL}/manifest.json" &>/dev/null; then
GIT_SERVER_READY=true
break
fi
printf "."
sleep 1
done
echo ""
if [[ "$GIT_SERVER_READY" != true ]]; then
echo "ERROR: Git HTTP server did not start within 30 seconds"
echo "[git-server] Container logs:"
$COMPOSE_CMD -f "$COMPOSE_FILE" logs git-server --tail=20 2>/dev/null || true
EXIT_CODE=1
exit 1
fi
echo "[git-server] ✓ Git HTTP server is ready on $GIT_SERVER_URL"
echo "[gitea] Waiting for Gitea to become healthy..."
GITEA_READY=false
for i in $(seq 1 60); do
if curl -sf "${GITEA_URL}/api/v1/version" &>/dev/null; then
GITEA_READY=true
break
fi
printf "."
sleep 2
done
echo ""
if [[ "$GITEA_READY" != true ]]; then
echo "ERROR: Gitea did not become healthy within 120 seconds"
echo "[gitea] Container logs:"
$COMPOSE_CMD -f "$COMPOSE_FILE" logs gitea-e2e --tail=30 2>/dev/null || true
EXIT_CODE=1
exit 1
fi
GITEA_VERSION=$(curl -sf "${GITEA_URL}/api/v1/version" | grep -o '"version":"[^"]*"' | cut -d'"' -f4)
echo "[gitea] ✓ Gitea is ready (version: ${GITEA_VERSION:-unknown})"
echo ""
# ─── Step 4: Start fake GitHub API ──────────────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 4: Start fake GitHub API server │"
echo "└──────────────────────────────────────────────────────────────┘"
PORT=$FAKE_GITHUB_PORT GIT_SERVER_URL="$GIT_SERVER_INTERNAL_URL" \
$TSX_CMD "$SCRIPT_DIR/fake-github-server.ts" &
FAKE_GITHUB_PID=$!
echo "$FAKE_GITHUB_PID" > "$SCRIPT_DIR/.fake-github.pid"
echo "[fake-github] Started (PID: $FAKE_GITHUB_PID)"
echo "[fake-github] Waiting for server to be ready..."
FAKE_READY=false
for i in $(seq 1 30); do
if curl -sf "${FAKE_GITHUB_URL}/___mgmt/health" &>/dev/null; then
FAKE_READY=true
break
fi
# Check if process died
if ! kill -0 "$FAKE_GITHUB_PID" 2>/dev/null; then
echo "ERROR: Fake GitHub server process died"
EXIT_CODE=1
exit 1
fi
printf "."
sleep 1
done
echo ""
if [[ "$FAKE_READY" != true ]]; then
echo "ERROR: Fake GitHub server did not start within 30 seconds"
EXIT_CODE=1
exit 1
fi
echo "[fake-github] ✓ Fake GitHub API is ready on $FAKE_GITHUB_URL"
# Tell the fake GitHub server to use the git-server container URL for clone_url
# (This updates existing repos in the store so Gitea can actually clone them)
echo "[fake-github] Setting clone URL base to $GIT_SERVER_INTERNAL_URL ..."
curl -sf -X POST "${FAKE_GITHUB_URL}/___mgmt/set-clone-url" \
-H "Content-Type: application/json" \
-d "{\"url\": \"${GIT_SERVER_INTERNAL_URL}\"}" || true
echo "[fake-github] ✓ Clone URLs configured"
echo ""
# ─── Step 5: Start gitea-mirror app ────────────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 5: Start gitea-mirror application │"
echo "└──────────────────────────────────────────────────────────────┘"
cd "$PROJECT_ROOT"
# Reinitialize the database in case build step reset it
$BUN_CMD run manage-db init 2>&1 | tail -2 || true
# Start the app with E2E environment
GITHUB_API_URL="$FAKE_GITHUB_URL" \
BETTER_AUTH_SECRET="e2e-test-secret" \
BETTER_AUTH_URL="$APP_URL" \
DATABASE_URL="file:data/gitea-mirror.db" \
HOST="0.0.0.0" \
PORT="$APP_PORT" \
NODE_ENV="production" \
PRE_SYNC_BACKUP_ENABLED="false" \
ENCRYPTION_SECRET="e2e-encryption-secret-32char!!" \
$BUN_CMD run start &
APP_PID=$!
echo "$APP_PID" > "$SCRIPT_DIR/.app.pid"
echo "[app] Started (PID: $APP_PID)"
echo "[app] Waiting for app to be ready..."
APP_READY=false
for i in $(seq 1 90); do
# Try the health endpoint first, then fall back to root
if curl -sf "${APP_URL}/api/health" &>/dev/null 2>&1 || \
curl -sf -o /dev/null -w "%{http_code}" "${APP_URL}/" 2>/dev/null | grep -q "^[23]"; then
APP_READY=true
break
fi
# Check if process died
if ! kill -0 "$APP_PID" 2>/dev/null; then
echo ""
echo "ERROR: gitea-mirror app process died"
EXIT_CODE=1
exit 1
fi
printf "."
sleep 2
done
echo ""
if [[ "$APP_READY" != true ]]; then
echo "ERROR: gitea-mirror app did not start within 180 seconds"
EXIT_CODE=1
exit 1
fi
echo "[app] ✓ gitea-mirror app is ready on $APP_URL"
echo ""
# ─── Step 6: Run Playwright E2E tests ──────────────────────────────────────
echo "┌──────────────────────────────────────────────────────────────┐"
echo "│ Step 6: Run Playwright E2E tests │"
echo "└──────────────────────────────────────────────────────────────┘"
cd "$PROJECT_ROOT"
# Ensure test-results directory exists
mkdir -p "$SCRIPT_DIR/test-results"
# Run Playwright
set +e
APP_URL="$APP_URL" \
GITEA_URL="$GITEA_URL" \
FAKE_GITHUB_URL="$FAKE_GITHUB_URL" \
npx playwright test \
--config "$SCRIPT_DIR/playwright.config.ts" \
--reporter=list
PLAYWRIGHT_EXIT=$?
set -e
echo ""
if [[ $PLAYWRIGHT_EXIT -eq 0 ]]; then
echo "═══════════════════════════════════════════════════════════════"
echo " ✅ E2E tests PASSED"
echo "═══════════════════════════════════════════════════════════════"
else
echo "═══════════════════════════════════════════════════════════════"
echo " ❌ E2E tests FAILED (exit code: $PLAYWRIGHT_EXIT)"
echo "═══════════════════════════════════════════════════════════════"
# On failure, dump some diagnostic info
echo ""
echo "[diag] Gitea container status:"
$COMPOSE_CMD -f "$COMPOSE_FILE" ps 2>/dev/null || true
echo ""
echo "[diag] Gitea container logs (last 20 lines):"
$COMPOSE_CMD -f "$COMPOSE_FILE" logs gitea-e2e --tail=20 2>/dev/null || true
echo ""
echo "[diag] Git server logs (last 10 lines):"
$COMPOSE_CMD -f "$COMPOSE_FILE" logs git-server --tail=10 2>/dev/null || true
echo ""
echo "[diag] Git server health:"
curl -sf "${GIT_SERVER_URL}/manifest.json" 2>/dev/null || echo "(unreachable)"
echo ""
echo "[diag] Fake GitHub health:"
curl -sf "${FAKE_GITHUB_URL}/___mgmt/health" 2>/dev/null || echo "(unreachable)"
echo ""
echo "[diag] App health:"
curl -sf "${APP_URL}/api/health" 2>/dev/null || echo "(unreachable)"
echo ""
# Point to HTML report
if [[ -d "$SCRIPT_DIR/playwright-report" ]]; then
echo "[diag] HTML report: $SCRIPT_DIR/playwright-report/index.html"
echo " Run: npx playwright show-report $SCRIPT_DIR/playwright-report"
fi
EXIT_CODE=$PLAYWRIGHT_EXIT
fi
# EXIT_CODE is used by the trap handler
exit $EXIT_CODE