From 7e9af20f851222868bdcbd1b550aa08049d81254 Mon Sep 17 00:00:00 2001 From: Galindo97 Date: Wed, 20 May 2026 11:51:49 -0500 Subject: [PATCH] refactor(jenkins): update CI/CD pipeline to run tests in parallel and remove linting stages --- Jenkinsfile | 344 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 199 insertions(+), 145 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 8e6d7692..78b1d33f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,5 +1,5 @@ // Pipeline CI/CD — Aduanasoft Anexo76 -// Ejecuta: lint → tests (unit) → security scan → docker build/push → deploy dev → smoke test +// Ejecuta: tests (backend + frontend en paralelo) → security scan → docker build/push → deploy dev → smoke test // // Credenciales requeridas en Jenkins: // - harbor-credentials : username/password para dev.aduanasoft.com @@ -33,162 +33,216 @@ pipeline { stages { - // ── Paso 5a — Lint ──────────────────────────────────────────────────── - stage('Lint — Backend') { - steps { - sh ''' - set -euo pipefail - C="a76-lint-py-${BUILD_NUMBER}" - cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } - trap cleanup EXIT + // ── Paso 5b — Tests (backend y frontend en paralelo) ────────────────── + stage('Tests') { + parallel { - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" "$PYTHON_IMAGE" sleep infinity - docker cp "$WORKSPACE/backend/." "$C:/app" + stage('Test — Backend') { + steps { + sh ''' + set -euo pipefail + C="a76-test-py-${BUILD_NUMBER}" + DB_C="a76-test-db-${BUILD_NUMBER}" + NET="a76-test-net-${BUILD_NUMBER}" + DB_NAME="anexo76_test" + DB_USER="anexo76" + DB_PASS="anexo76" + TEST_DB_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_C}:5432/${DB_NAME}" - docker exec -w /app "$C" bash -lc ' - set -euxo pipefail - pip install --quiet "ruff==0.6.0" - ruff check . --exclude venv - ruff format --check . --exclude venv - ' - ''' - } - } + cleanup() { + docker rm -f "$C" >/dev/null 2>&1 || true + docker rm -f "$DB_C" >/dev/null 2>&1 || true + docker network rm "$NET" >/dev/null 2>&1 || true + } + trap cleanup EXIT - stage('Lint — Frontend') { - steps { - sh ''' - set -euo pipefail - C="a76-lint-fe-${BUILD_NUMBER}" - cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } - trap cleanup EXIT + docker network rm "$NET" >/dev/null 2>&1 || true + docker network create "$NET" - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity - docker exec "$C" mkdir -p /workspace - docker cp "$WORKSPACE/." "$C:/workspace" + docker rm -f "$DB_C" >/dev/null 2>&1 || true + docker run -d --name "$DB_C" \ + --network "$NET" \ + -e POSTGRES_DB="$DB_NAME" \ + -e POSTGRES_USER="$DB_USER" \ + -e POSTGRES_PASSWORD="$DB_PASS" \ + postgres:16-alpine - docker exec -w /workspace/frontend "$C" bash -lc ' - set -euxo pipefail - npm install -g pnpm@9 --quiet - pnpm install --frozen-lockfile - pnpm run lint - pnpm run i18n:compile - pnpm run check - ' - ''' - } - } + # Esperar a que Postgres esté listo + READY=0 + for i in $(seq 1 30); do + if docker exec "$DB_C" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then + READY=1; break + fi + sleep 2 + done + if [ "$READY" != "1" ]; then + echo "ERROR: Postgres no quedó listo en 60s." + exit 1 + fi - // ── Paso 5b — Tests ─────────────────────────────────────────────────── - stage('Test — Backend') { - steps { - sh ''' - set -euo pipefail - C="a76-test-py-${BUILD_NUMBER}" - DB_C="a76-test-db-${BUILD_NUMBER}" - NET="a76-test-net-${BUILD_NUMBER}" - DB_NAME="anexo76_test" - DB_USER="anexo76" - DB_PASS="anexo76" - TEST_DB_URL="postgresql://${DB_USER}:${DB_PASS}@${DB_C}:5432/${DB_NAME}" + docker exec "$DB_C" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 -c " + CREATE SCHEMA IF NOT EXISTS core; + CREATE SCHEMA IF NOT EXISTS a24; + CREATE SCHEMA IF NOT EXISTS a76; + CREATE SCHEMA IF NOT EXISTS public; + " - cleanup() { - docker rm -f "$C" >/dev/null 2>&1 || true - docker rm -f "$DB_C" >/dev/null 2>&1 || true - docker network rm "$NET" >/dev/null 2>&1 || true + docker rm -f "$C" >/dev/null 2>&1 || true + docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity + docker cp "$WORKSPACE/backend/." "$C:/app" + + docker exec \ + -e TEST_DATABASE_URL="$TEST_DB_URL" \ + -w /app "$C" bash -lc ' + set -euxo pipefail + pip install --quiet -r requirements.txt + + # Prueba de migraciones: upgrade → downgrade → upgrade + # Verifica que todos los down() sean reversibles + echo "--- alembic upgrade head ---" + alembic upgrade head + echo "--- alembic downgrade base ---" + alembic downgrade base + echo "--- alembic upgrade head (restaurar para tests) ---" + alembic upgrade head + + pytest tests/ \ + --cov=. \ + --cov-fail-under=80 \ + --cov-report=term-missing \ + --cov-report=xml \ + --junitxml=test-results.xml \ + -v -ra + ' + docker cp "$C:/app/test-results.xml" "$WORKSPACE/backend/test-results.xml" + docker cp "$C:/app/coverage.xml" "$WORKSPACE/backend/coverage.xml" + ''' + } + post { + always { + junit allowEmptyResults: true, testResults: 'backend/test-results.xml' + archiveArtifacts artifacts: 'backend/coverage.xml', allowEmptyArchive: true + } } - trap cleanup EXIT - - docker network rm "$NET" >/dev/null 2>&1 || true - docker network create "$NET" - - docker rm -f "$DB_C" >/dev/null 2>&1 || true - docker run -d --name "$DB_C" \ - --network "$NET" \ - -e POSTGRES_DB="$DB_NAME" \ - -e POSTGRES_USER="$DB_USER" \ - -e POSTGRES_PASSWORD="$DB_PASS" \ - postgres:16-alpine - - # Esperar a que Postgres esté listo - READY=0 - for i in $(seq 1 30); do - if docker exec "$DB_C" pg_isready -U "$DB_USER" -d "$DB_NAME" >/dev/null 2>&1; then - READY=1; break - fi - sleep 2 - done - if [ "$READY" != "1" ]; then - echo "ERROR: Postgres no quedó listo en 60s." - exit 1 - fi - - docker exec "$DB_C" psql -U "$DB_USER" -d "$DB_NAME" -v ON_ERROR_STOP=1 -c " - CREATE SCHEMA IF NOT EXISTS core; - CREATE SCHEMA IF NOT EXISTS a24; - CREATE SCHEMA IF NOT EXISTS a76; - CREATE SCHEMA IF NOT EXISTS public; - " - - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" --network "$NET" "$PYTHON_IMAGE" sleep infinity - docker cp "$WORKSPACE/backend/." "$C:/app" - - docker exec \ - -e TEST_DATABASE_URL="$TEST_DB_URL" \ - -w /app "$C" bash -lc ' - set -euxo pipefail - pip install --quiet -r requirements.txt - alembic upgrade head - pytest tests/ \ - --cov=. \ - --cov-fail-under=80 \ - --cov-report=term-missing \ - --cov-report=xml \ - --junitxml=test-results.xml \ - -v -ra - ' - docker cp "$C:/app/test-results.xml" "$WORKSPACE/backend/test-results.xml" - docker cp "$C:/app/coverage.xml" "$WORKSPACE/backend/coverage.xml" - ''' - } - post { - always { - junit allowEmptyResults: true, testResults: 'backend/test-results.xml' - archiveArtifacts artifacts: 'backend/coverage.xml', allowEmptyArchive: true } - } - } - stage('Test — Frontend') { - steps { - sh ''' - set -euo pipefail - C="a76-test-fe-${BUILD_NUMBER}" - cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } - trap cleanup EXIT + stage('Test — Frontend') { + stages { - docker rm -f "$C" >/dev/null 2>&1 || true - docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity - docker exec "$C" mkdir -p /workspace - docker cp "$WORKSPACE/." "$C:/workspace" + stage('Unit Tests (Vitest)') { + steps { + sh ''' + set -euo pipefail + C="a76-test-fe-${BUILD_NUMBER}" + cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } + trap cleanup EXIT + + docker rm -f "$C" >/dev/null 2>&1 || true + docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity + docker exec "$C" mkdir -p /workspace + docker cp "$WORKSPACE/." "$C:/workspace" + + docker exec \ + -e CI=true \ + -e NODE_ENV=test \ + -e "JENKINS_URL=${JENKINS_URL}" \ + -e JENKINS_VITEST_FULL=1 \ + -w /workspace/frontend \ + "$C" bash -lc ' + set -euxo pipefail + npm install -g pnpm@9 --quiet + pnpm install --frozen-lockfile + pnpm run i18n:compile + pnpm run test:unit -- --run + ' + ''' + } + } + + stage('E2E (Playwright)') { + steps { + // El dev server (localhost:5173) se conecta al Keycloak/API de test + // via las credenciales inyectadas. El frontend corre en el contenedor; + // Keycloak y la API son externos (misma instancia que dev). + withCredentials([ + string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL') + ]) { + sh ''' + set -euo pipefail + C="a76-test-e2e-${BUILD_NUMBER}" + cleanup() { docker rm -f "$C" >/dev/null 2>&1 || true; } + trap cleanup EXIT + + docker rm -f "$C" >/dev/null 2>&1 || true + docker run -d --name "$C" "$PLAYWRIGHT_IMAGE" sleep infinity + docker exec "$C" mkdir -p /workspace + docker cp "$WORKSPACE/." "$C:/workspace" + + docker exec \ + -e CI=true \ + -e NODE_ENV=test \ + -e "JENKINS_URL=${JENKINS_URL}" \ + -e PLAYWRIGHT_TEST_BASE_URL=http://localhost:5173 \ + -e "VITE_API_URL=${A76_URL}/api/" \ + -e "INTERNAL_API_URL=${A76_URL}/api/" \ + -e "VITE_KEYCLOAK_URL=${A76_URL}/kcauth" \ + -e "KEYCLOAK_URL=${A76_URL}/kcauth" \ + -e VITE_KEYCLOAK_REALM=master \ + -e KEYCLOAK_REALM=master \ + -e VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend \ + -e KEYCLOAK_CLIENT_ID=anexo76-frontend \ + -e ORIGIN=http://localhost:5173 \ + -w /workspace/frontend \ + "$C" bash -lc ' + set -euxo pipefail + npm install -g pnpm@9 --quiet + pnpm install --frozen-lockfile + pnpm run i18n:compile + + # Arrancar dev server con las vars ya inyectadas + pnpm run dev & + DEV_PID=$! + + # Esperar a que el servidor esté listo (máx 60s) + READY=0 + for i in $(seq 1 30); do + if curl -sf http://localhost:5173 >/dev/null 2>&1; then + READY=1; break + fi + sleep 2 + done + if [ "$READY" != "1" ]; then + echo "ERROR: Dev server no arrancó en 60s." + kill "$DEV_PID" 2>/dev/null || true + exit 1 + fi + + # Ejecutar E2E; capturar exit code para no perder el reporte + E2E_EXIT=0 + PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ + pnpm exec playwright test --reporter=junit,html || E2E_EXIT=$? + + kill "$DEV_PID" 2>/dev/null || true + exit "$E2E_EXIT" + ' + docker cp "$C:/workspace/frontend/playwright-results.xml" \ + "$WORKSPACE/frontend/playwright-results.xml" 2>/dev/null || true + docker cp "$C:/workspace/frontend/playwright-report" \ + "$WORKSPACE/frontend/playwright-report" 2>/dev/null || true + ''' + } + } + post { + always { + junit allowEmptyResults: true, testResults: 'frontend/playwright-results.xml' + archiveArtifacts artifacts: 'frontend/playwright-report/**', allowEmptyArchive: true + } + } + } + + } + } - docker exec \ - -e CI=true \ - -e NODE_ENV=test \ - -e "JENKINS_URL=${JENKINS_URL}" \ - -e JENKINS_VITEST_FULL=1 \ - -w /workspace/frontend \ - "$C" bash -lc ' - set -euxo pipefail - npm install -g pnpm@9 --quiet - pnpm install --frozen-lockfile - pnpm run i18n:compile - pnpm run test:unit -- --run - ' - ''' } }