408 lines
13 KiB
TypeScript
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();
|
|
}
|
|
}
|
|
});
|
|
});
|