fix(e2e): usar usuario de prueba existente en lugar de Admin API

El usuario en a76-e2e-credentials no tiene permisos de KC admin (403).
En lugar de crear usuario efímero vía Admin API:
- Eliminar global-setup.ts, global-teardown.ts, fixtures/keycloak.ts
- Quitar globalSetup/globalTeardown de playwright.config.ts
- auth.setup.ts ya navega a /login → Workspace → llena form → /dashboard
- a76-e2e-credentials = usuario de prueba existente en Workspace (E2E_USER/E2E_PASS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:10:59 -05:00
parent 155ee07173
commit 4a9d4cfcd1
5 changed files with 7 additions and 215 deletions

View File

@@ -1,108 +0,0 @@
/**
* Helpers para gestionar usuarios de prueba en Keycloak vía Admin REST API.
* Se usan en globalSetup / globalTeardown de tests E2E.
* Patrón idéntico al de aduanasoft-hub.
*/
export interface KeycloakAdminConfig {
url: string; // ej. https://workspace.aduanasoft.com/kcauth
realm: string; // ej. master
adminUser: string;
adminPassword: string;
}
async function getAdminToken(cfg: KeycloakAdminConfig): Promise<string> {
const url = `${cfg.url}/realms/master/protocol/openid-connect/token`;
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'password',
client_id: 'admin-cli',
username: cfg.adminUser,
password: cfg.adminPassword
})
});
if (!res.ok) throw new Error(`Keycloak admin token failed: ${res.status} ${await res.text()}`);
const data = await res.json();
return data.access_token;
}
export async function createTestUser(
cfg: KeycloakAdminConfig,
user: { username: string; password: string; email: string }
): Promise<string> {
const token = await getAdminToken(cfg);
const res = await fetch(`${cfg.url}/admin/realms/${cfg.realm}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
username: user.username,
email: user.email,
enabled: true,
emailVerified: true,
credentials: [{ type: 'password', value: user.password, temporary: false }]
})
});
// 409 = ya existe — idempotente
if (!res.ok && res.status !== 409) {
throw new Error(`createTestUser failed: ${res.status} ${await res.text()}`);
}
const searchRes = await fetch(
`${cfg.url}/admin/realms/${cfg.realm}/users?username=${encodeURIComponent(user.username)}&exact=true`,
{ headers: { Authorization: `Bearer ${token}` } }
);
const users = await searchRes.json();
if (!users.length) throw new Error(`Usuario ${user.username} no encontrado tras creación`);
return users[0].id;
}
export async function assignRoles(
cfg: KeycloakAdminConfig,
userId: string,
roleNames: string[]
): Promise<void> {
const token = await getAdminToken(cfg);
// Resolver los IDs de los roles por nombre
const roles: { id: string; name: string }[] = [];
for (const name of roleNames) {
const res = await fetch(
`${cfg.url}/admin/realms/${cfg.realm}/roles/${encodeURIComponent(name)}`,
{ headers: { Authorization: `Bearer ${token}` } }
);
if (!res.ok) throw new Error(`Rol '${name}' no encontrado: ${res.status}`);
const role = await res.json();
roles.push({ id: role.id, name: role.name });
}
const assignRes = await fetch(
`${cfg.url}/admin/realms/${cfg.realm}/users/${userId}/role-mappings/realm`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify(roles)
}
);
if (!assignRes.ok) {
throw new Error(`assignRoles failed: ${assignRes.status} ${await assignRes.text()}`);
}
}
export async function deleteTestUser(cfg: KeycloakAdminConfig, userId: string): Promise<void> {
const token = await getAdminToken(cfg);
const res = await fetch(`${cfg.url}/admin/realms/${cfg.realm}/users/${userId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` }
});
// 404 = ya fue borrado — se ignora
if (!res.ok && res.status !== 404) {
throw new Error(`deleteTestUser failed: ${res.status} ${await res.text()}`);
}
}

View File

@@ -1,50 +0,0 @@
/**
* Playwright globalSetup — se ejecuta UNA vez antes de todos los tests E2E.
*
* Crea el usuario de prueba en Keycloak desde cero vía Admin REST API.
* No depende de que el usuario exista previamente — funciona con KC vacío
* siempre que el realm y los clientes estén configurados.
*
* Patrón idéntico al de aduanasoft-hub.
*/
import { writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { createTestUser, assignRoles } from './fixtures/keycloak.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const KC_CFG = {
url: process.env.KC_URL ?? 'https://workspace.aduanasoft.com/kcauth',
realm: process.env.KEYCLOAK_REALM ?? 'master',
adminUser: process.env.E2E_KC_ADMIN_USER ?? 'admin',
adminPassword: process.env.E2E_KC_ADMIN_PASSWORD ?? ''
};
const TEST_USER = {
username: process.env.E2E_TEST_USER ?? 'e2e-anexo76',
password: process.env.E2E_TEST_PASSWORD ?? '',
email: process.env.E2E_TEST_EMAIL ?? 'e2e-anexo76@test.local'
};
export default async function globalSetup() {
console.log('\n[E2E setup] Creando usuario de prueba en Keycloak...');
if (!KC_CFG.adminPassword) {
throw new Error('E2E_KC_ADMIN_PASSWORD no está configurado');
}
if (!TEST_USER.password) {
throw new Error('E2E_TEST_PASSWORD no está configurado');
}
const userId = await createTestUser(KC_CFG, TEST_USER);
// Roles de Keycloak necesarios para acceder a Anexo76
await assignRoles(KC_CFG, userId, ['admin']);
// Guardar el ID para que globalTeardown pueda borrar el usuario
const statePath = join(__dirname, '..', '.e2e-state.json');
writeFileSync(statePath, JSON.stringify({ userId, username: TEST_USER.username }));
console.log(`[E2E setup] Usuario '${TEST_USER.username}' listo (id: ${userId})`);
}

View File

@@ -1,37 +0,0 @@
/**
* Playwright globalTeardown — se ejecuta UNA vez después de todos los tests E2E.
* Elimina el usuario de prueba creado en globalSetup.
*
* Patrón idéntico al de aduanasoft-hub.
*/
import { readFileSync, unlinkSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { deleteTestUser } from './fixtures/keycloak.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
const KC_CFG = {
url: process.env.KC_URL ?? 'https://workspace.aduanasoft.com/kcauth',
realm: process.env.KEYCLOAK_REALM ?? 'master',
adminUser: process.env.E2E_KC_ADMIN_USER ?? 'admin',
adminPassword: process.env.E2E_KC_ADMIN_PASSWORD ?? ''
};
export default async function globalTeardown() {
const statePath = join(__dirname, '..', '.e2e-state.json');
if (!existsSync(statePath)) {
console.log('[E2E teardown] Sin estado guardado — nada que limpiar');
return;
}
const { userId, username } = JSON.parse(readFileSync(statePath, 'utf8'));
console.log(`\n[E2E teardown] Eliminando usuario '${username}' de Keycloak...`);
await deleteTestUser(KC_CFG, userId);
unlinkSync(statePath);
console.log(`[E2E teardown] Usuario '${username}' eliminado`);
}