From 7e40b57193c7955062f700ebdce0b7c89da6f6a5 Mon Sep 17 00:00:00 2001 From: Joker Date: Thu, 3 Sep 2026 20:21:17 +0000 Subject: [PATCH] fix(e2e): creds de login via env E2E_AUTH_* + docs test production-aware (card 64763175) - app.spec.ts: login tests leen E2E_AUTH_USERNAME/E2E_AUTH_PASSWORD y hacen skip con motivo claro si no estan; fuera las creds hardcodeadas de era railway (Joker/Joker123! no existen en la BD de prod). - Test de docs: con E2E_ENV=production afirma el 404 (docs_url=None en settings.is_production, main.py PR #147) en vez de esperar 200. - e2e.yml: job e2e-prod-smoke inyecta E2E_ENV=production y los secrets E2E_AUTH_* (skip explicito hasta que se configuren). - Full login flow (UI) tambien usa env creds: sin creds, skip. Verificado contra el stack prod real (127.0.0.1:3010/8010): 5 passed, 3 skipped. --- .github/workflows/e2e.yml | 9 ++++++- dashboard/frontend/e2e/app.spec.ts | 38 +++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 15833b8..445960a 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -78,7 +78,11 @@ jobs: # Card 4eb58505: smoke de prod en pushes a main — el stack real self-hosted # (Coolify en jokerserver: frontend :3010, backend :8010). El runner # coolify-deploy comparte host con el stack, por eso 127.0.0.1. Aquí sí - # corren los tests de API (E2E_API_URL): la BD real tiene el usuario Joker. + # corren los tests de API (E2E_API_URL). + # Card 64763175: E2E_ENV=production hace que el test de docs afirme el 404 + # (docs deshabilitados en prod). Login tests usan E2E_AUTH_USERNAME/ + # E2E_AUTH_PASSWORD: si los secrets no están configurados, los tests hacen + # skip explícito en vez de fallar con credenciales inexistentes. # Los PRs se saltan este job (el preview E2E vive en pr-deploy-coolify.yml). e2e-prod-smoke: name: E2E Prod Smoke (self-hosted) @@ -88,6 +92,9 @@ jobs: env: E2E_BASE_URL: http://127.0.0.1:3010 E2E_API_URL: http://127.0.0.1:8010 + E2E_ENV: production + E2E_AUTH_USERNAME: ${{ secrets.E2E_AUTH_USERNAME }} + E2E_AUTH_PASSWORD: ${{ secrets.E2E_AUTH_PASSWORD }} steps: - name: Checkout repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/dashboard/frontend/e2e/app.spec.ts b/dashboard/frontend/e2e/app.spec.ts index e74ad24..e433671 100644 --- a/dashboard/frontend/e2e/app.spec.ts +++ b/dashboard/frontend/e2e/app.spec.ts @@ -8,9 +8,26 @@ import { test, expect } from '@playwright/test'; // skipped otherwise: fresh preview backends boot with an empty database // (create_all, no seed), so login-dependent checks only run against the // real prod stack in the `e2e-prod-smoke` job. +// +// Card 64763175: no hardcoded credentials either. +// - Login-dependent tests read E2E_AUTH_USERNAME / E2E_AUTH_PASSWORD from +// the environment and SKIP with a clear reason when they are absent. +// The old 'Joker'/'Joker123!' literals were preview/staging-era values +// that do not exist in the production database. +// - E2E_ENV=production flips the docs test to expect 404: the backend +// disables /api/v1/docs when settings.is_production (main.py, PR #147), +// and asserting that response is the point of the test in prod. const BACKEND_URL = process.env.E2E_API_URL ?? ''; const hasApi = BACKEND_URL !== ''; +const E2E_USERNAME = process.env.E2E_AUTH_USERNAME ?? ''; +const E2E_PASSWORD = process.env.E2E_AUTH_PASSWORD ?? ''; +const hasAuth = E2E_USERNAME !== '' && E2E_PASSWORD !== ''; +const AUTH_SKIP_REASON = + 'E2E_AUTH_USERNAME / E2E_AUTH_PASSWORD not set — configure them (CI secrets or local env) to run login tests against this environment'; + +const IS_PRODUCTION = (process.env.E2E_ENV ?? '') === 'production'; + test.describe('QA-FRAMEWORK E2E Tests', () => { test('Backend health check', async ({ request }) => { @@ -24,14 +41,21 @@ test.describe('QA-FRAMEWORK E2E Tests', () => { test('Backend API docs accessible', async ({ request }) => { test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend'); const response = await request.get(`${BACKEND_URL}/api/v1/docs`); - expect(response.ok()).toBeTruthy(); + if (IS_PRODUCTION) { + // Production deliberately disables the docs endpoints + // (docs_url=None if settings.is_production) — 404 IS the secure posture. + expect(response.status()).toBe(404); + } else { + expect(response.ok()).toBeTruthy(); + } }); test('Login API works', async ({ request }) => { test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend'); + test.skip(!hasAuth, AUTH_SKIP_REASON); const response = await request.post(`${BACKEND_URL}/api/v1/auth/login`, { headers: { 'Content-Type': 'application/json' }, - data: { username: 'Joker', password: 'Joker123!' } + data: { username: E2E_USERNAME, password: E2E_PASSWORD } }); expect(response.ok()).toBeTruthy(); const data = await response.json(); @@ -41,10 +65,11 @@ test.describe('QA-FRAMEWORK E2E Tests', () => { test('Get user info with token', async ({ request }) => { test.skip(!hasApi, 'E2E_API_URL not set — requires a deployed backend'); + test.skip(!hasAuth, AUTH_SKIP_REASON); // First login const loginResponse = await request.post(`${BACKEND_URL}/api/v1/auth/login`, { headers: { 'Content-Type': 'application/json' }, - data: { username: 'Joker', password: 'Joker123!' } + data: { username: E2E_USERNAME, password: E2E_PASSWORD } }); const loginData = await loginResponse.json(); const token = loginData.access_token; @@ -55,7 +80,7 @@ test.describe('QA-FRAMEWORK E2E Tests', () => { }); expect(meResponse.ok()).toBeTruthy(); const userData = await meResponse.json(); - expect(userData.username).toBe('Joker'); + expect(userData.username).toBe(E2E_USERNAME); }); test('Frontend loads', async ({ page }) => { @@ -73,13 +98,14 @@ test.describe('QA-FRAMEWORK E2E Tests', () => { }); test('Full login flow', async ({ page }) => { + test.skip(!hasAuth, AUTH_SKIP_REASON); await page.goto('/login'); await page.waitForLoadState('networkidle'); // Fill login form using placeholder or type const inputs = page.locator('input'); - await inputs.nth(0).fill('Joker'); - await inputs.nth(1).fill('Joker123!'); + await inputs.nth(0).fill(E2E_USERNAME); + await inputs.nth(1).fill(E2E_PASSWORD); // Submit await page.click('button:has-text("Login")');