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(); } } }); });