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:
20
Jenkinsfile
vendored
20
Jenkinsfile
vendored
@@ -169,21 +169,18 @@ pipeline {
|
|||||||
|
|
||||||
stage('E2E (Playwright)') {
|
stage('E2E (Playwright)') {
|
||||||
steps {
|
steps {
|
||||||
// Patrón idéntico al de aduanasoft-hub:
|
// auth.setup.ts navega a /login → Workspace → llena form → /dashboard
|
||||||
// globalSetup crea usuario de prueba en KC vía Admin API
|
// y guarda storageState para los tests dependientes.
|
||||||
// auth.setup.ts navega a /login → Workspace → llena form → /dashboard
|
|
||||||
// globalTeardown elimina el usuario al finalizar
|
|
||||||
//
|
//
|
||||||
// Credenciales requeridas en Jenkins:
|
// Credenciales requeridas en Jenkins:
|
||||||
// a76-public-url-dev : URL pública base de Anexo76
|
// a76-public-url-dev : URL pública base de Anexo76
|
||||||
// a76-e2e-credentials : Username with password — admin de Keycloak
|
// a76-e2e-credentials : Username with password — usuario de prueba en Workspace
|
||||||
// (para que globalSetup pueda crear/eliminar usuarios)
|
|
||||||
withCredentials([
|
withCredentials([
|
||||||
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'),
|
string(credentialsId: 'a76-public-url-dev', variable: 'A76_URL'),
|
||||||
usernamePassword(
|
usernamePassword(
|
||||||
credentialsId: 'a76-e2e-credentials',
|
credentialsId: 'a76-e2e-credentials',
|
||||||
usernameVariable: 'E2E_KC_ADMIN_USER',
|
usernameVariable: 'E2E_USER',
|
||||||
passwordVariable: 'E2E_KC_ADMIN_PASS'
|
passwordVariable: 'E2E_PASS'
|
||||||
)
|
)
|
||||||
]) {
|
]) {
|
||||||
sh '''
|
sh '''
|
||||||
@@ -212,11 +209,8 @@ pipeline {
|
|||||||
-e KEYCLOAK_CLIENT_ID=hub-frontend \
|
-e KEYCLOAK_CLIENT_ID=hub-frontend \
|
||||||
-e ORIGIN=http://localhost:5173 \
|
-e ORIGIN=http://localhost:5173 \
|
||||||
-e "KC_URL=${KC_URL}" \
|
-e "KC_URL=${KC_URL}" \
|
||||||
-e "E2E_KC_ADMIN_USER=${E2E_KC_ADMIN_USER}" \
|
-e "E2E_TEST_USER=${E2E_USER}" \
|
||||||
-e "E2E_KC_ADMIN_PASSWORD=${E2E_KC_ADMIN_PASS}" \
|
-e "E2E_TEST_PASSWORD=${E2E_PASS}" \
|
||||||
-e E2E_TEST_USER=e2e-anexo76 \
|
|
||||||
-e E2E_TEST_PASSWORD=E2eAnexo76Test! \
|
|
||||||
-e E2E_TEST_EMAIL=e2e-anexo76@test.local \
|
|
||||||
-w /workspace/frontend \
|
-w /workspace/frontend \
|
||||||
"$C" bash -lc '
|
"$C" bash -lc '
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|||||||
@@ -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()}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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})`);
|
|
||||||
}
|
|
||||||
@@ -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`);
|
|
||||||
}
|
|
||||||
@@ -9,17 +9,11 @@ const authStatePath = path.join(__dirname, 'e2e/.auth/user.json');
|
|||||||
const inCI = process.env.CI === 'true' || Boolean(process.env.JENKINS_URL);
|
const inCI = process.env.CI === 'true' || Boolean(process.env.JENKINS_URL);
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
// Crea usuario de prueba en KC antes de los tests; lo elimina al finalizar
|
|
||||||
// Patrón idéntico al de aduanasoft-hub
|
|
||||||
globalSetup: './e2e/global-setup.ts',
|
|
||||||
globalTeardown: './e2e/global-teardown.ts',
|
|
||||||
|
|
||||||
// En CI, falla si queda un .only; en local no
|
// En CI, falla si queda un .only; en local no
|
||||||
forbidOnly: inCI,
|
forbidOnly: inCI,
|
||||||
timeout: 60_000,
|
timeout: 60_000,
|
||||||
use: {
|
use: {
|
||||||
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:5173',
|
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:5173',
|
||||||
// Sin display en agentes de integración: obligatorio headless
|
|
||||||
headless: inCI ? true : false
|
headless: inCI ? true : false
|
||||||
},
|
},
|
||||||
workers: 1,
|
workers: 1,
|
||||||
@@ -28,7 +22,6 @@ export default defineConfig({
|
|||||||
{
|
{
|
||||||
name: 'setup',
|
name: 'setup',
|
||||||
testMatch: '**/auth.setup.ts',
|
testMatch: '**/auth.setup.ts',
|
||||||
// Navegador limpio: user.json se genera aquí y no debe existir al primer run / en CI
|
|
||||||
use: { storageState: { cookies: [], origins: [] } }
|
use: { storageState: { cookies: [], origins: [] } }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user