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

408 lines
13 KiB
TypeScript

import { test, expect, Page } from '@playwright/test';
/**
* E2E Tests: Flujo de Activación de Estudiantes
* Verifica el flujo completo de activación por código cuando un admin crea un estudiante.
*/
// Helper para simular sesión de admin
async function setAdminSession(page: Page) {
const mockToken = 'mock.admin.jwt.token';
const mockUser = {
id: 1,
username: 'admin',
role: 'Admin',
studentId: null,
studentName: null,
};
await page.evaluate(
({ token, user }) => {
localStorage.setItem('auth_token', token);
localStorage.setItem('user', JSON.stringify(user));
},
{ token: mockToken, user: mockUser }
);
}
test.describe('Activación - Admin Crea Estudiante', () => {
const timestamp = Date.now();
test.beforeEach(async ({ page }) => {
await page.goto('/');
await setAdminSession(page);
});
test('admin debe ver formulario de nuevo estudiante', async ({ page }) => {
await page.goto('/students/new');
await expect(
page.getByRole('heading', { name: /nuevo estudiante/i })
).toBeVisible({ timeout: 10000 });
await expect(page.getByLabel(/nombre/i)).toBeVisible();
await expect(page.getByLabel(/email/i)).toBeVisible();
});
test('debe crear estudiante y mostrar modal de activación', async ({ page }) => {
await page.goto('/students/new');
// Llenar formulario
await page.getByLabel(/nombre/i).fill(`Test Student ${timestamp}`);
await page.getByLabel(/email/i).fill(`test_${timestamp}@example.com`);
// Enviar
await page.getByRole('button', { name: /crear|guardar/i }).click();
// Debe mostrar modal con código de activación
await expect(
page.getByText(/código.*activación|activation.*code/i)
).toBeVisible({ timeout: 15000 });
// Debe mostrar el código generado
await expect(
page.locator('[data-testid="activation-code"]').or(
page.locator('code, .activation-code, .code-display')
)
).toBeVisible();
});
test('modal debe mostrar URL de activación', async ({ page }) => {
await page.goto('/students/new');
await page.getByLabel(/nombre/i).fill(`Test URL ${timestamp}`);
await page.getByLabel(/email/i).fill(`testurl_${timestamp}@example.com`);
await page.getByRole('button', { name: /crear|guardar/i }).click();
// Esperar modal
await expect(
page.getByText(/código.*activación/i)
).toBeVisible({ timeout: 15000 });
// Debe mostrar URL de activación
await expect(
page.getByText(/activate\?code=|activar\?codigo=/i)
).toBeVisible();
});
test('modal debe mostrar fecha de expiración', async ({ page }) => {
await page.goto('/students/new');
await page.getByLabel(/nombre/i).fill(`Test Expiry ${timestamp}`);
await page.getByLabel(/email/i).fill(`testexp_${timestamp}@example.com`);
await page.getByRole('button', { name: /crear|guardar/i }).click();
await expect(
page.getByText(/código.*activación/i)
).toBeVisible({ timeout: 15000 });
// Debe mostrar expiración
await expect(
page.getByText(/expira|expiración|válido hasta/i)
).toBeVisible();
});
test('debe poder copiar código al portapapeles', async ({ page }) => {
await page.goto('/students/new');
await page.getByLabel(/nombre/i).fill(`Test Copy ${timestamp}`);
await page.getByLabel(/email/i).fill(`testcopy_${timestamp}@example.com`);
await page.getByRole('button', { name: /crear|guardar/i }).click();
await expect(
page.getByText(/código.*activación/i)
).toBeVisible({ timeout: 15000 });
// Buscar botón de copiar
const copyButton = page.getByRole('button', { name: /copiar/i }).or(
page.locator('[data-testid="copy-code"]')
);
if (await copyButton.isVisible()) {
await copyButton.click();
// Debe mostrar confirmación de copiado
await expect(
page.getByText(/copiado|copied/i)
).toBeVisible({ timeout: 5000 }).catch(() => {
// Puede que el feedback sea diferente
});
}
});
test('debe cerrar modal y volver al listado', async ({ page }) => {
await page.goto('/students/new');
await page.getByLabel(/nombre/i).fill(`Test Close ${timestamp}`);
await page.getByLabel(/email/i).fill(`testclose_${timestamp}@example.com`);
await page.getByRole('button', { name: /crear|guardar/i }).click();
await expect(
page.getByText(/código.*activación/i)
).toBeVisible({ timeout: 15000 });
// Cerrar modal
const closeButton = page.getByRole('button', { name: /cerrar|entendido|aceptar|continuar/i });
await closeButton.click();
// Debe redirigir al listado o mostrar el listado
await expect(page).toHaveURL(/\/students|\/admin/);
});
});
test.describe('Activación - Página de Activación', () => {
test('debe mostrar página de activación con código válido', async ({ page }) => {
// Navegar a página de activación con código de prueba
await page.goto('/activate?code=TESTCODE123');
// Debe mostrar formulario de activación o error de código inválido
await expect(
page.getByText(/activar.*cuenta|bienvenido|código.*inválido|expirado/i)
).toBeVisible({ timeout: 10000 });
});
test('debe mostrar error con código inválido', async ({ page }) => {
await page.goto('/activate?code=INVALID');
// Debe mostrar error
await expect(
page.getByText(/inválido|expirado|no encontrado|error/i)
).toBeVisible({ timeout: 10000 });
});
test('formulario de activación debe tener campos requeridos', async ({ page }) => {
await page.goto('/activate?code=TESTCODE123');
// Si el código es válido, debe mostrar formulario
const usernameField = page.getByLabel(/usuario|username/i);
const passwordField = page.getByLabel(/contraseña|password/i).first();
// Si los campos existen (código válido), verificar
if (await usernameField.isVisible()) {
await expect(usernameField).toBeVisible();
await expect(passwordField).toBeVisible();
// Puede haber campo de confirmar contraseña
const confirmField = page.getByLabel(/confirmar/i);
if (await confirmField.isVisible()) {
await expect(confirmField).toBeVisible();
}
}
});
test('debe validar usuario único en activación', async ({ page }) => {
await page.goto('/activate?code=TESTCODE123');
const usernameField = page.getByLabel(/usuario|username/i);
if (await usernameField.isVisible()) {
// Usar un usuario que probablemente exista
await usernameField.fill('admin');
await page.getByLabel(/contraseña|password/i).first().fill('Test123!');
const confirmField = page.getByLabel(/confirmar/i);
if (await confirmField.isVisible()) {
await confirmField.fill('Test123!');
}
await page.getByRole('button', { name: /activar|crear/i }).click();
// Debe mostrar error de usuario existente
await expect(
page.getByText(/usuario.*existe|ya está en uso|username.*taken/i)
).toBeVisible({ timeout: 10000 });
}
});
test('debe validar contraseña mínima en activación', async ({ page }) => {
await page.goto('/activate?code=TESTCODE123');
const passwordField = page.getByLabel(/contraseña|password/i).first();
if (await passwordField.isVisible()) {
await passwordField.fill('123');
await page.getByLabel(/usuario|username/i).focus();
await expect(
page.getByText(/al menos 6 caracteres|mínimo.*6/i)
).toBeVisible();
}
});
test('debe mostrar código de recuperación después de activar', async ({ page }) => {
const timestamp = Date.now();
await page.goto('/activate?code=TESTCODE123');
const usernameField = page.getByLabel(/usuario|username/i);
if (await usernameField.isVisible()) {
await usernameField.fill(`newuser_${timestamp}`);
await page.getByLabel(/contraseña|password/i).first().fill('Test123!');
const confirmField = page.getByLabel(/confirmar/i);
if (await confirmField.isVisible()) {
await confirmField.fill('Test123!');
}
await page.getByRole('button', { name: /activar|crear/i }).click();
// Si la activación es exitosa, debe mostrar código de recuperación
// o redirigir al dashboard
await expect(
page.getByText(/código.*recuperación|recovery.*code|bienvenido|dashboard/i)
).toBeVisible({ timeout: 15000 }).catch(() => {
// Puede redirigir directamente
});
}
});
});
test.describe('Activación - Expiración de Código', () => {
test('debe mostrar error si código expiró', async ({ page }) => {
// Código expirado de prueba
await page.goto('/activate?code=EXPIRED123');
await expect(
page.getByText(/expirado|expired|inválido|invalid/i)
).toBeVisible({ timeout: 10000 });
});
test('admin puede regenerar código para estudiante', async ({ page }) => {
await page.goto('/');
await setAdminSession(page);
// Ir al listado de estudiantes
await page.goto('/students');
// Buscar botón de regenerar código (si existe)
const regenerateButton = page.getByRole('button', { name: /regenerar.*código|nuevo.*código/i });
if (await regenerateButton.first().isVisible()) {
await regenerateButton.first().click();
// Debe mostrar nuevo código
await expect(
page.getByText(/nuevo código|código regenerado/i)
).toBeVisible({ timeout: 10000 });
}
});
});
test.describe('Activación - Flujo Completo', () => {
test('estudiante activado puede iniciar sesión', async ({ page }) => {
// Este test asume que hay un estudiante ya activado
await page.goto('/login');
// Verificar que el formulario de login funciona
await expect(page.getByLabel(/usuario/i)).toBeVisible();
await expect(page.getByLabel(/contraseña/i)).toBeVisible();
await expect(page.getByRole('button', { name: /iniciar sesión/i })).toBeVisible();
});
test('estudiante activado ve su dashboard', async ({ page }) => {
await page.goto('/');
// Simular sesión de estudiante activado
const mockUser = {
id: 2,
username: 'activated_student',
role: 'Student',
studentId: 10,
studentName: 'Estudiante Activado',
};
await page.evaluate(
({ token, user }) => {
localStorage.setItem('auth_token', token);
localStorage.setItem('user', JSON.stringify(user));
},
{ token: 'mock.jwt.token', user: mockUser }
);
await page.goto('/dashboard');
// Debe ver su nombre en el dashboard
await expect(
page.getByText(/bienvenido|estudiante/i)
).toBeVisible({ timeout: 10000 });
});
test('estudiante activado puede inscribirse en materias', async ({ page }) => {
await page.goto('/');
const mockUser = {
id: 2,
username: 'activated_student',
role: 'Student',
studentId: 10,
studentName: 'Estudiante Activado',
};
await page.evaluate(
({ token, user }) => {
localStorage.setItem('auth_token', token);
localStorage.setItem('user', JSON.stringify(user));
},
{ token: 'mock.jwt.token', user: mockUser }
);
await page.goto('/enrollment/10');
// Debe ver página de inscripción
await expect(
page.getByText(/materias|inscripción/i)
).toBeVisible({ timeout: 10000 });
});
});
test.describe('Activación - Seguridad', () => {
test('código de activación debe ser de un solo uso', async ({ page }) => {
// Intentar usar un código ya usado
await page.goto('/activate?code=USED_CODE_123');
await expect(
page.getByText(/ya fue usado|inválido|expirado|no encontrado/i)
).toBeVisible({ timeout: 10000 });
});
test('página de activación no requiere autenticación', async ({ page }) => {
// Limpiar sesión
await page.goto('/');
await page.evaluate(() => localStorage.clear());
// Acceder a página de activación
await page.goto('/activate?code=ANYCODE');
// No debe redirigir a login
await expect(page).toHaveURL(/\/activate/);
});
test('código de recuperación solo se muestra una vez', async ({ page }) => {
// Este es más un test de UI - verificar que hay advertencia
const timestamp = Date.now();
await page.goto('/activate?code=TESTCODE123');
const usernameField = page.getByLabel(/usuario|username/i);
if (await usernameField.isVisible()) {
await usernameField.fill(`secuser_${timestamp}`);
await page.getByLabel(/contraseña|password/i).first().fill('Test123!');
const confirmField = page.getByLabel(/confirmar/i);
if (await confirmField.isVisible()) {
await confirmField.fill('Test123!');
}
await page.getByRole('button', { name: /activar|crear/i }).click();
// Si se muestra código de recuperación, debe haber advertencia
const recoverySection = page.getByText(/código.*recuperación/i);
if (await recoverySection.isVisible()) {
await expect(
page.getByText(/solo.*vez|guarda.*código|importante|advertencia|warning/i)
).toBeVisible();
}
}
});
});