Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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: la BD real NO tiene el usuario Joker con el password del
# repo (user real: 'joker', password propio) y docs está off en production
# por diseño (2972521c). Auth via secrets E2E_AUTH_* (hasta configurarlos,
# los auth tests se saltan — skip honesto, no rojo) y docs espera 404.
# Los PRs se saltan este job (el preview E2E vive en pr-deploy-coolify.yml).
e2e-prod-smoke:
name: E2E Prod Smoke (self-hosted)
Expand All @@ -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_DOCS_DISABLED: '1'
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
Expand Down
36 changes: 28 additions & 8 deletions dashboard/frontend/e2e/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,19 @@ 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: los credenciales hardcodeados de la era railway no existen
// en la BD real de prod (usuario 'joker' con password propio — verificado
// 2026-09-03: login 401 con ambos casings) y /api/v1/docs está deshabilitado
// en production POR DISEÑO (main.py: docs_url=None si is_production —
// PR #147 / card 2972521c). Los auth tests pasan a E2E_AUTH_USERNAME /
// E2E_AUTH_PASSWORD (skip si ausentes) y el test de docs afirma la postura
// de seguridad (404) cuando E2E_DOCS_DISABLED=1.
const BACKEND_URL = process.env.E2E_API_URL ?? '';
const hasApi = BACKEND_URL !== '';
const AUTH_USERNAME = process.env.E2E_AUTH_USERNAME ?? '';
const AUTH_PASSWORD = process.env.E2E_AUTH_PASSWORD ?? '';
const hasAuth = AUTH_USERNAME !== '' && AUTH_PASSWORD !== '';
const docsDisabled = process.env.E2E_DOCS_DISABLED === '1';

test.describe('QA-FRAMEWORK E2E Tests', () => {

Expand All @@ -24,14 +35,20 @@ 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 (docsDisabled) {
// Card 2972521c (PR #147): docs_url=None si is_production — afirmar la
// postura de seguridad en production en vez de exigir docs accesibles.
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(!hasApi || !hasAuth, 'requires E2E_API_URL and E2E_AUTH_USERNAME/E2E_AUTH_PASSWORD (card 64763175: creds de la era railway no válidos en prod)');
const response = await request.post(`${BACKEND_URL}/api/v1/auth/login`, {
headers: { 'Content-Type': 'application/json' },
data: { username: 'Joker', password: 'Joker123!' }
data: { username: AUTH_USERNAME, password: AUTH_PASSWORD }
});
expect(response.ok()).toBeTruthy();
const data = await response.json();
Expand All @@ -40,11 +57,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(!hasApi || !hasAuth, 'requires E2E_API_URL and E2E_AUTH_USERNAME/E2E_AUTH_PASSWORD (card 64763175)');
// 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: AUTH_USERNAME, password: AUTH_PASSWORD }
});
const loginData = await loginResponse.json();
const token = loginData.access_token;
Expand All @@ -55,7 +72,9 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {
});
expect(meResponse.ok()).toBeTruthy();
const userData = await meResponse.json();
expect(userData.username).toBe('Joker');
// Login puede ser case-insensitive mientras /me devuelve el canonical:
// comparar sin casing evita falsos rojos por 'Joker' vs 'joker'.
expect(userData.username.toLowerCase()).toBe(AUTH_USERNAME.toLowerCase());
});

test('Frontend loads', async ({ page }) => {
Expand All @@ -73,13 +92,14 @@ test.describe('QA-FRAMEWORK E2E Tests', () => {
});

test('Full login flow', async ({ page }) => {
test.skip(!hasAuth, 'requires E2E_AUTH_USERNAME/E2E_AUTH_PASSWORD — BD sin seed en previews y creds de era railway no válidos en prod (card 64763175)');
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(AUTH_USERNAME);
await inputs.nth(1).fill(AUTH_PASSWORD);

// Submit
await page.click('button:has-text("Login")');
Expand Down
Loading