#!/usr/bin/env bash # #region run-enterprise-clean-e2e [C:5] [TYPE Script] [SEMANTICS e2e, enterprise-clean, orchestration, bundle, test] # @BRIEF Enterprise Clean E2E Orchestrator — полный протокол тестирования пустого образа перед передачей. # Stage 1: Сборка dist bundle, развёртывание enterprise-clean стека, PostgreSQL, Playwright проверка # первоначальной настройки (StartupEnvironmentWizard), добавление Superset окружения. # Stage 2: Запуск ./run.sh (локальный dev стек), Playwright проверка живого проекта, # LLM анализ дашборда, изменение настроек. # @PRE Docker и docker compose установлены. Node 20+, python3, .venv настроен. # @POST Stage 1: Enterprise-clean bundle собран, развёрнут, протестирован, стек остановлен. # Stage 2: Локальный dev стек запущен, протестирован, остановлен. # @SIDE_EFFECT Создаёт bundle в dist/docker/. Запускает и останавливает Docker контейнеры. # Останавливает dev процессы (uvicorn, vite). Использует временный PostgreSQL контейнер. # @INVARIANT Stage 1 и Stage 2 выполняются последовательно. Ошибка в Stage 1 прерывает весь протокол. # @INVARIANT После завершения (успех или ошибка) все контейнеры и процессы останавливаются. # @LAYER Infrastructure # @RELATION DEPENDS_ON -> [build.sh] # @RELATION DEPENDS_ON -> [run.sh] # @RELATION DEPENDS_ON -> [../playwright.config.js] # @RELATION DEPENDS_ON -> [./tests/enterprise-clean-setup.e2e.js] # @RELATION DEPENDS_ON -> [./tests/live-project-check.e2e.js] # @RATIONALE Пустой образ (enterprise-clean bundle) требует полного цикла проверки: от сборки до # развёртывания и первичной настройки. Без этой orchestration QA не может гарантировать, # что образ ready-to-deliver. Разделение на 2 stage позволяет проверить как "чистую" установку # (fresh PostgreSQL, StartupEnvironmentWizard), так и "живой" проект с настроенными окружениями. # @REJECTED Использование одного docker-compose файла для обоих стейджей — enterprise-clean требует # build из исходников + внешний PostgreSQL, а run.sh использует локальный dev compose. # @DATA_CONTRACT Input: тег релиза (аргумент), .env.enterprise-clean.example, backend/.env # Output: dist/docker/*.tar.xz, dist/docker/manifest*.txt, playwright-report/ # #endregion run-enterprise-clean-e2e set -euo pipefail # ====================================================================== # CONFIGURATION # ====================================================================== SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" FRONTEND_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" PROJECT_ROOT="$(cd "$FRONTEND_DIR/.." && pwd)" # Default tag (timestamp-based) RELEASE_TAG="${1:-e2e-test-$(date +%Y%m%d-%H%M%S)}" # Stage 1: Enterprise Clean ports EC_BACKEND_PORT=8104 EC_FRONTEND_PORT=8105 EC_POSTGRES_PORT=5436 # Stage 2: Live project (run.sh defaults) # Stage 2: Live project (run.sh defaults — avoid conflicts with enterprise-clean on 8000/8001) LIVE_BACKEND_PORT=8106 LIVE_FRONTEND_PORT=5173 # Таймауты TIMEOUT_BACKEND_START=60 TIMEOUT_FRONTEND_START=30 # Цвета для вывода RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # ====================================================================== # HELP # ====================================================================== show_help() { cat <<'HELP' Usage: ./e2e/run-enterprise-clean-e2e.sh [release-tag] Enterprise Clean E2E Test Orchestrator — полный протокол тестирования пустого образа. Протокол: Stage 1 — Empty Image Setup: 1. Сборка enterprise-clean bundle (./build.sh bundle ) 2. Загрузка образов в Docker (xz -dc ... | docker load) 3. Запуск PostgreSQL контейнера 4. Развёртывание enterprise-clean стека 5. Playwright: логин, StartupEnvironmentWizard, добавление ss-dev.bebesh.ru 6. Остановка enterprise-clean стека Stage 2 — Live Project Verification: 1. Запуск ./run.sh (backend + frontend) 2. Playwright: логин (admin/admin123), LLM проверка дашборда, изменение настроек 3. Остановка dev процессов Аргументы: release-tag Тег релиза (по умолчанию: e2e-test-) Пример: ./e2e/run-enterprise-clean-e2e.sh v2.1.0 HELP exit 0 } if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then show_help fi # ====================================================================== # UTILITY FUNCTIONS # ====================================================================== log_info() { echo -e "${BLUE}[E2E]${NC} $*"; } log_ok() { echo -e "${GREEN}[E2E] ✅${NC} $*"; } log_warn() { echo -e "${YELLOW}[E2E] ⚠️${NC} $*"; } log_error() { echo -e "${RED}[E2E] ❌${NC} $*"; } cleanup() { local exit_code=$? log_info "Cleanup: stopping all services..." # Stop Stage 2 processes (run.sh) if [[ -n "${BACKEND_PID:-}" ]]; then kill "$BACKEND_PID" 2>/dev/null || true fi if [[ -n "${FRONTEND_PID:-}" ]]; then kill "$FRONTEND_PID" 2>/dev/null || true fi # Stop Stage 1 docker compose if [[ -f "${PROJECT_ROOT}/dist/docker/docker-compose.enterprise-clean.yml" ]]; then log_info "Stopping enterprise-clean stack..." docker compose -p ss-tools-e2e-ec \ -f "${PROJECT_ROOT}/dist/docker/docker-compose.enterprise-clean.yml" \ down -t 10 2>/dev/null || true fi # Stop PostgreSQL container docker rm -f ss-tools-e2e-pg 2>/dev/null || true log_info "Cleanup complete (exit code: $exit_code)" exit "$exit_code" } trap cleanup EXIT INT TERM wait_for_backend() { local url="$1" local timeout="${2:-$TIMEOUT_BACKEND_START}" log_info "Waiting for backend at $url (timeout: ${timeout}s)..." for i in $(seq 1 "$timeout"); do local http_code http_code=$(curl -s -o /dev/null -w "%{http_code}" "${url}/api/health/summary" 2>/dev/null || echo "000") # 200 = healthy, 401/403 = up but needs auth (still means server is running) if [[ "$http_code" == "200" || "$http_code" == "401" || "$http_code" == "403" ]]; then log_ok "Backend responding (HTTP ${http_code}) after ${i}s" return 0 fi sleep 1 done log_error "Backend failed to start within ${timeout}s" return 1 } wait_for_frontend() { local url="$1" local timeout="${2:-$TIMEOUT_FRONTEND_START}" log_info "Waiting for frontend at $url (timeout: ${timeout}s)..." for i in $(seq 1 "$timeout"); do if curl -sf -o /dev/null "${url}/login" 2>/dev/null; then log_ok "Frontend reachable after ${i}s" return 0 fi sleep 1 done log_error "Frontend failed to start within ${timeout}s" return 1 } run_playwright_tests() { local grep_pattern="$1" local frontend_url="$2" local backend_url="$3" local description="$4" log_info "Running Playwright tests: ${description}..." cd "$FRONTEND_DIR" BACKEND_URL="$backend_url" \ FRONTEND_URL="$frontend_url" \ E2E_USERNAME="admin" \ E2E_PASSWORD="admin123" \ npx playwright test --grep "$grep_pattern" \ --reporter=list,html \ || { local exit_code=$? log_warn "Playwright tests completed with exit code: $exit_code" return "$exit_code" } log_ok "Playwright tests passed: ${description}" } # ====================================================================== # STAGE 1: Enterprise Clean — Empty Image Setup # ====================================================================== stage1_enterprise_clean() { echo "" echo "═══════════════════════════════════════════════════════════════" echo " STAGE 1: Enterprise Clean — Empty Image Setup" echo " Bundle tag: ${RELEASE_TAG}" echo "═══════════════════════════════════════════════════════════════" echo "" # ── 1.1. CREATE DIST BUNDLE ────────────────────────────── log_info "Step 1.1: Building enterprise-clean bundle '${RELEASE_TAG}'..." cd "$PROJECT_ROOT" if ! ./build.sh bundle "${RELEASE_TAG}"; then log_error "Bundle build failed" return 1 fi log_ok "Bundle built: dist/docker/" # ── 1.2. LOAD IMAGES ───────────────────────────────────── log_info "Step 1.2: Loading Docker images from bundle..." if [[ -f "${PROJECT_ROOT}/dist/docker/backend.${RELEASE_TAG}.tar.xz" ]]; then xz -dc "${PROJECT_ROOT}/dist/docker/backend.${RELEASE_TAG}.tar.xz" | docker load log_ok "Backend image loaded" else log_warn "Backend archive not found, attempting docker build instead..." cd "$PROJECT_ROOT" docker build -f docker/backend.Dockerfile -t "ss-tools-backend:${RELEASE_TAG}" . fi if [[ -f "${PROJECT_ROOT}/dist/docker/frontend.${RELEASE_TAG}.tar.xz" ]]; then xz -dc "${PROJECT_ROOT}/dist/docker/frontend.${RELEASE_TAG}.tar.xz" | docker load log_ok "Frontend image loaded" else log_warn "Frontend archive not found, attempting docker build instead..." cd "$PROJECT_ROOT" docker build -f docker/frontend.Dockerfile -t "ss-tools-frontend:${RELEASE_TAG}" . fi # ── 1.3. START POSTGRESQL ──────────────────────────────── log_info "Step 1.3: Starting new PostgreSQL container..." docker rm -f ss-tools-e2e-pg 2>/dev/null || true # Create the network that docker compose will use (ss-tools-e2e-ec_default) # so PostgreSQL is on the same network and resolvable by container name. if ! docker network inspect ss-tools-e2e-ec_default >/dev/null 2>&1; then docker network create ss-tools-e2e-ec_default fi docker run -d \ --name ss-tools-e2e-pg \ --network ss-tools-e2e-ec_default \ -e POSTGRES_DB=ss_tools \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -p "${EC_POSTGRES_PORT}:5432" \ postgres:16-alpine log_info "Waiting for PostgreSQL to be ready..." for i in $(seq 1 20); do if docker exec ss-tools-e2e-pg pg_isready -U postgres -d ss_tools > /dev/null 2>&1; then log_ok "PostgreSQL ready after ${i}s" break fi if [ "$i" -eq 20 ]; then log_error "PostgreSQL failed to start" docker logs ss-tools-e2e-pg --tail 20 return 1 fi sleep 1 done # Get PostgreSQL container IP on the enterprise network EC_PG_HOST="ss-tools-e2e-pg" EC_PG_PORT="5432" # ── 1.4. DEPLOY ENTERPRISE CLEAN STACK ─────────────────── log_info "Step 1.4: Deploying enterprise-clean stack..." log_info " PostgreSQL: ${EC_PG_HOST}:${EC_PG_PORT}" log_info " Backend port: ${EC_BACKEND_PORT}" log_info " Frontend port: ${EC_FRONTEND_PORT}" # Generate a minimal env file for E2E testing cat > "/tmp/.env.e2e-enterprise-clean" </dev/null || echo "") export E2E_TOKEN if [[ -z "$E2E_TOKEN" ]]; then log_warn "Could not obtain auth token (may be normal on first startup with wizard)" fi cd "$FRONTEND_DIR" BACKEND_URL="http://127.0.0.1:${EC_BACKEND_PORT}" \ FRONTEND_URL="http://127.0.0.1:${EC_FRONTEND_PORT}" \ E2E_TOKEN="${E2E_TOKEN}" \ E2E_USERNAME="admin" \ E2E_PASSWORD="admin123" \ npx playwright test --grep "Enterprise Clean Setup" \ --reporter=list,html \ || { local ec_exit=$? log_warn "Stage 1 Playwright tests exited with code: $ec_exit" # Continue despite failure — we want logs } log_ok "Stage 1 complete!" # ── 1.7. STOP ENTERPRISE CLEAN STACK ───────────────────── log_info "Step 1.7: Stopping enterprise-clean stack..." docker compose -p ss-tools-e2e-ec \ -f "$EC_COMPOSE_FILE" \ --env-file "/tmp/.env.e2e-enterprise-clean" \ down -t 10 docker rm -f ss-tools-e2e-pg 2>/dev/null || true log_ok "Stage 1: Enterprise clean stack stopped" } # ====================================================================== # STAGE 2: Live Project Verification # ====================================================================== stage2_live_project() { echo "" echo "═══════════════════════════════════════════════════════════════" echo " STAGE 2: Live Project Verification" echo " Run: ./run.sh (local dev stack)" echo "═══════════════════════════════════════════════════════════════" echo "" # ── 2.1. ENSURE POSTGRESQL RUNNING ─────────────────────── log_info "Step 2.1: Ensuring PostgreSQL is running for local dev stack..." cd "$PROJECT_ROOT" # Check if PostgreSQL is already running on standard port if python3 -c "import socket; socket.create_connection(('localhost', 5432), timeout=1).close()" 2>/dev/null; then log_ok "PostgreSQL already running on localhost:5432" else log_info "Starting PostgreSQL via docker compose..." docker compose -p ss-tools-current -f docker-compose.yml up -d db log_info "Waiting for PostgreSQL..." for i in $(seq 1 20); do if python3 -c "import socket; socket.create_connection(('localhost', 5432), timeout=1).close()" 2>/dev/null; then log_ok "PostgreSQL ready after ${i}s" break fi if [ "$i" -eq 20 ]; then log_error "PostgreSQL failed to start" docker compose -p ss-tools-current logs db --tail 20 return 1 fi sleep 1 done fi # ── 2.2. START BACKEND ─────────────────────────────────── log_info "Step 2.2: Starting backend (uvicorn)..." cd "$PROJECT_ROOT/backend" # Activate virtual environment if [[ -f ".venv/bin/activate" ]]; then source .venv/bin/activate else log_warn "No .venv found, trying system python" fi python3 -m uvicorn src.app:app \ --reload \ --port "${LIVE_BACKEND_PORT}" \ --env-file .env \ 2>&1 | sed "s/^/[Backend] /" & BACKEND_PID=$! cd "$PROJECT_ROOT" log_info "Backend PID: ${BACKEND_PID}" if ! wait_for_backend "http://127.0.0.1:${LIVE_BACKEND_PORT}" "$TIMEOUT_BACKEND_START"; then log_error "Backend failed to start" return 1 fi # ── 2.3. START FRONTEND ────────────────────────────────── log_info "Step 2.3: Starting frontend (vite)..." cd "$FRONTEND_DIR" npm run dev -- --port "${LIVE_FRONTEND_PORT}" \ 2>&1 | sed "s/^/[Frontend] /" & FRONTEND_PID=$! cd "$PROJECT_ROOT" log_info "Frontend PID: ${FRONTEND_PID}" if ! wait_for_frontend "http://127.0.0.1:${LIVE_FRONTEND_PORT}" "$TIMEOUT_FRONTEND_START"; then log_error "Frontend failed to start" return 1 fi # ── 2.4. OBTAIN AUTH TOKEN ─────────────────────────────── log_info "Step 2.4: Obtaining auth token..." LOGIN_RESPONSE=$(curl -s -X POST "http://127.0.0.1:${LIVE_BACKEND_PORT}/api/auth/login" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin&password=admin123") E2E_TOKEN=$(echo "$LOGIN_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null || echo "") export E2E_TOKEN if [[ -z "$E2E_TOKEN" ]]; then log_error "Failed to obtain auth token" log_error "Login response: $LOGIN_RESPONSE" return 1 fi log_ok "Auth token obtained" # ── 2.5. RUN PLAYWRIGHT — STAGE 2 ─────────────────────── log_info "Step 2.5: Running Stage 2 Playwright tests..." cd "$FRONTEND_DIR" BACKEND_URL="http://127.0.0.1:${LIVE_BACKEND_PORT}" \ FRONTEND_URL="http://127.0.0.1:${LIVE_FRONTEND_PORT}" \ E2E_TOKEN="${E2E_TOKEN}" \ E2E_USERNAME="admin" \ E2E_PASSWORD="admin123" \ npx playwright test --grep "Live Project Check" \ --reporter=list,html \ || { local live_exit=$? log_warn "Stage 2 Playwright tests exited with code: $live_exit" } log_ok "Stage 2 complete!" } # ====================================================================== # MAIN # ====================================================================== main() { echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Enterprise Clean E2E Test Orchestrator ║" echo "║ Release tag: ${RELEASE_TAG} ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" # Check prerequisites cd "$PROJECT_ROOT" log_info "Prerequisites check..." command -v docker >/dev/null 2>&1 || { log_error "docker is required"; return 1; } command -v node >/dev/null 2>&1 || { log_error "node is required"; return 1; } command -v npm >/dev/null 2>&1 || { log_error "npm is required"; return 1; } command -v python3 >/dev/null 2>&1 || { log_error "python3 is required"; return 1; } command -v curl >/dev/null 2>&1 || { log_error "curl is required"; return 1; } log_ok "All prerequisites met" # Install frontend dependencies if needed if [[ ! -d "$FRONTEND_DIR/node_modules" ]]; then log_info "Installing frontend dependencies..." cd "$FRONTEND_DIR" npm ci cd "$PROJECT_ROOT" fi # Install Playwright browsers if needed if ! npx playwright --version >/dev/null 2>&1; then log_info "Installing Playwright..." cd "$FRONTEND_DIR" npx playwright install chromium cd "$PROJECT_ROOT" fi # ═══════════════════════════════════════════════════════════ # STAGE 1: Enterprise Clean Empty Image # ═══════════════════════════════════════════════════════════ log_info "Starting Stage 1: Enterprise Clean Empty Image Setup" if ! stage1_enterprise_clean; then log_error "Stage 1 failed. Aborting." return 1 fi log_ok "Stage 1 completed successfully" # ═══════════════════════════════════════════════════════════ # STAGE 2: Live Project Verification # ═══════════════════════════════════════════════════════════ log_info "Starting Stage 2: Live Project Verification" if ! stage2_live_project; then log_error "Stage 2 failed." return 1 fi log_ok "Stage 2 completed successfully" # ═══════════════════════════════════════════════════════════ # COMPLETE # ═══════════════════════════════════════════════════════════ echo "" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Enterprise Clean E2E Test Complete! ║" echo "║ ║" echo "║ ✅ Stage 1: Empty Image Setup — Bundle built, deployed, ║" echo "║ StartupEnvironmentWizard verified, environment added. ║" echo "║ ║" echo "║ ✅ Stage 2: Live Project — Dev stack verified, ║" echo "║ LLM analysis triggered, settings modified. ║" echo "║ ║" echo "║ Bundle: dist/docker/ (tag: ${RELEASE_TAG}) ║" echo "║ Report: frontend/playwright-report/ ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" } # Run main main "$@"