academia/src/frontend/e2e/smoke.spec.ts

172 lines
5.7 KiB
TypeScript

import { test, expect } from '@playwright/test';
/**
* Smoke Tests E2E para CI/CD
*
* Estos tests verifican funcionalidades críticas contra el servidor real.
* Se ejecutan después del deploy para validar que el sistema funciona.
*
* Ejecutar: BASE_URL=https://academia.ingeniumcodex.com npx playwright test smoke.spec.ts
*/
test.describe('Smoke Tests - Páginas Públicas', () => {
test('página de login carga correctamente', async ({ page }) => {
await page.goto('/login');
// Verificar que la página cargó
await expect(page).toHaveTitle(/estudiante|academia|login/i);
// Verificar elementos básicos del formulario
await expect(page.locator('input[type="text"], input[name="username"]').first()).toBeVisible();
await expect(page.locator('input[type="password"]').first()).toBeVisible();
await expect(page.locator('button[type="submit"]').first()).toBeVisible();
});
test('página de registro carga correctamente', async ({ page }) => {
await page.goto('/register');
// Verificar elementos del formulario de registro
await expect(page.locator('input').first()).toBeVisible();
await expect(page.locator('button[type="submit"]').first()).toBeVisible();
});
test('página de activación acepta código en URL', async ({ page }) => {
await page.goto('/activate?code=TEST123');
// Debe mostrar algún contenido (error o formulario)
await expect(page.locator('body')).not.toBeEmpty();
});
test('redirección a login si no autenticado', async ({ page }) => {
// Limpiar storage
await page.goto('/');
await page.evaluate(() => localStorage.clear());
// Intentar acceder a ruta protegida
await page.goto('/dashboard');
// Debe redirigir a login
await page.waitForURL(/\/login/, { timeout: 10000 });
expect(page.url()).toContain('/login');
});
});
test.describe('Smoke Tests - API GraphQL', () => {
test('endpoint GraphQL responde', async ({ request, baseURL }) => {
const response = await request.post(`${baseURL?.replace(':4200', ':5000') || 'http://localhost:5000'}/graphql`, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ __typename }',
},
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.data).toBeDefined();
});
test('query de materias retorna datos', async ({ request, baseURL }) => {
const apiUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/graphql'
: baseURL?.replace(/\/$/, '') + '/graphql';
const response = await request.post(apiUrl, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ subjects { id name credits } }',
},
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.data?.subjects).toBeDefined();
expect(body.data.subjects.length).toBeGreaterThan(0);
});
test('query de profesores retorna datos', async ({ request, baseURL }) => {
const apiUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/graphql'
: baseURL?.replace(/\/$/, '') + '/graphql';
const response = await request.post(apiUrl, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ professors { id name } }',
},
});
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.data?.professors).toBeDefined();
expect(body.data.professors.length).toBe(5); // 5 profesores según reglas de negocio
});
});
test.describe('Smoke Tests - Health Check', () => {
test('health endpoint responde healthy', async ({ request, baseURL }) => {
const healthUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/health'
: baseURL?.replace(/\/$/, '') + '/health';
const response = await request.get(healthUrl);
expect(response.ok()).toBeTruthy();
const body = await response.json();
expect(body.status).toBe('Healthy');
});
});
test.describe('Smoke Tests - Validaciones de Negocio', () => {
test('debe existir exactamente 10 materias', async ({ request, baseURL }) => {
const apiUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/graphql'
: baseURL?.replace(/\/$/, '') + '/graphql';
const response = await request.post(apiUrl, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ subjects { id } }',
},
});
const body = await response.json();
expect(body.data.subjects.length).toBe(10); // 10 materias según reglas
});
test('cada materia debe tener 3 créditos', async ({ request, baseURL }) => {
const apiUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/graphql'
: baseURL?.replace(/\/$/, '') + '/graphql';
const response = await request.post(apiUrl, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ subjects { credits } }',
},
});
const body = await response.json();
const allThreeCredits = body.data.subjects.every((s: { credits: number }) => s.credits === 3);
expect(allThreeCredits).toBeTruthy();
});
test('cada profesor debe tener exactamente 2 materias', async ({ request, baseURL }) => {
const apiUrl = baseURL?.includes('localhost:4200')
? 'http://localhost:5000/graphql'
: baseURL?.replace(/\/$/, '') + '/graphql';
const response = await request.post(apiUrl, {
headers: { 'Content-Type': 'application/json' },
data: {
query: '{ professors { id subjects { id } } }',
},
});
const body = await response.json();
const allTwoSubjects = body.data.professors.every(
(p: { subjects: unknown[] }) => p.subjects.length === 2
);
expect(allTwoSubjects).toBeTruthy();
});
});