diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9694a53a1..dd95eb0d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -58,6 +58,26 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} EMPATHY_API_KEY: ${{ secrets.EMPATHY_API_KEY }} + + e2e-tests: + needs: [unit-tests] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - run: npm --prefix users/authservice install + - run: npm --prefix users/userservice install + - run: npm --prefix users/groupservice install + - run: npm --prefix llmservice install + - run: npm --prefix mathGame install + - run: npm --prefix apiservice install + - run: npm --prefix gatewayservice install + - run: npm --prefix webapp install + - run: npm --prefix wikidata install + - run: npm --prefix webapp run build + - run: npm --prefix webapp run test:e2e docker-push-webapp: name: Push webapp Docker Image to GitHub Packages @@ -65,7 +85,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -91,7 +111,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -114,7 +134,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -137,7 +157,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -160,7 +180,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -187,7 +207,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -210,7 +230,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -233,7 +253,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 @@ -256,7 +276,7 @@ jobs: permissions: contents: read packages: write - needs: [] + needs: [e2e-tests] if: github.event_name == 'push' steps: - uses: actions/checkout@v4 diff --git a/docker-compose.yml b/docker-compose.yml index 68311db60..65f6b82f5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,6 @@ services: - "27017:27017" networks: - mynetwork - mathgame: container_name: mathgame-wichat_es2a image: ghcr.io/arquisoft/wichat_es2a/mathgame:latest @@ -26,6 +25,7 @@ services: environment: DEPLOY_HOST: ${DEPLOY_HOST:-localhost} WEBAPP_PORT: ${WEBAPP_PORT:-3000} + MATHGAME_PORT: 3002 restart: always authservice: diff --git a/mathGame/app.js b/mathGame/app.js new file mode 100644 index 000000000..80abad9c4 --- /dev/null +++ b/mathGame/app.js @@ -0,0 +1,22 @@ +const express = require('express'); +const cors = require('cors'); +const mathGameRoutes = require('./mathGameRoutes'); + +// Crear la aplicación Express +const app = express(); + +// Middleware +app.use(cors()); +app.use(express.json()); +app.disable('x-powered-by'); // Para mayor seguridad + +// Usar las rutas del mathGame +app.use('/mathgame', mathGameRoutes); + +// Ruta de salud para verificar que el servicio esté funcionando +app.get('/health', (req, res) => { + res.status(200).json({ status: 'OK', message: 'MathGame service is running' }); +}); + +// Exportamos la aplicación Express +module.exports = app; diff --git a/mathGame/mathGameRoutes.js b/mathGame/mathGameRoutes.js index d9046fa65..1c4771222 100644 --- a/mathGame/mathGameRoutes.js +++ b/mathGame/mathGameRoutes.js @@ -1,9 +1,9 @@ const express = require('express'); -const app = express(); -app.disable('x-powered-by'); +const router = express.Router(); const service = require('./service/mathGameService'); -app.use(express.json()); +// Configuración básica +router.use(express.json()); @@ -23,7 +23,7 @@ app.use(express.json()); * correct: 90 * } */ -app.get('/mathgame/question', (req, res) => { +router.get('/question', (req, res) => { try { const raw = req.query.base; const base = raw != null && !Number.isNaN(parseInt(raw, 10)) @@ -55,7 +55,7 @@ app.get('/mathgame/question', (req, res) => { * isCorrect: boolean * } */ -app.post('/mathgame/verify', (req, res) => { +router.post('/verify', (req, res) => { try { const { choice, correct } = req.body; const isCorrect = Number(choice) === Number(correct); @@ -66,4 +66,4 @@ app.post('/mathgame/verify', (req, res) => { } }); -module.exports = app; +module.exports = router; diff --git a/mathGame/mathGameRoutes.test.js b/mathGame/mathGameRoutes.test.js index 03faa5f6f..295e003be 100644 --- a/mathGame/mathGameRoutes.test.js +++ b/mathGame/mathGameRoutes.test.js @@ -1,7 +1,11 @@ const request = require('supertest'); -const app = require('./mathGameRoutes'); -const service = require('./service/mathGameService'); const express = require('express'); +const router = require('./mathGameRoutes'); +const service = require('./service/mathGameService'); + +// Crear una aplicación Express para los tests +const app = express(); +app.use('/mathgame', router); jest.mock('./service/mathGameService'); diff --git a/mathGame/package-lock.json b/mathGame/package-lock.json index 09c5c3093..8ca708e95 100644 --- a/mathGame/package-lock.json +++ b/mathGame/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "cors": "^2.8.5", "express": "^5.1.0", "mathgame": "file:" }, @@ -1651,6 +1652,19 @@ "dev": true, "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -3573,6 +3587,15 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/mathGame/package.json b/mathGame/package.json index 5a77b8f40..46e31ebde 100644 --- a/mathGame/package.json +++ b/mathGame/package.json @@ -22,6 +22,7 @@ "supertest": "^7.0.0" }, "dependencies": { + "cors": "^2.8.5", "express": "^5.1.0", "mathgame": "file:" } diff --git a/webapp/e2e/jest.config.js b/webapp/e2e/jest.config.js index bf51e18cf..635ca93c4 100644 --- a/webapp/e2e/jest.config.js +++ b/webapp/e2e/jest.config.js @@ -1,5 +1,5 @@ module.exports = { testMatch: ["**/steps/*.js"], - testTimeout: 120000, + testTimeout: 1000000, setupFilesAfterEnv: ["expect-puppeteer"] } \ No newline at end of file diff --git a/webapp/e2e/steps/game-category.steps.js b/webapp/e2e/steps/game-category.steps.js index 01077750f..de3aa4b9f 100644 --- a/webapp/e2e/steps/game-category.steps.js +++ b/webapp/e2e/steps/game-category.steps.js @@ -1,5 +1,5 @@ const puppeteer = require('puppeteer'); -const { defineFeature, loadFeature }=require('jest-cucumber'); +const { defineFeature, loadFeature } = require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/game-category.feature'); @@ -7,11 +7,33 @@ let page; let browser; defineFeature(feature, test => { - + beforeAll(async () => { + // 1. Crear usuario de test si no existe + try { + await fetch(`http://localhost:8000/adduser`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: 'NataliaBB', + password: 'Contrasena$2', + confirmPassword: 'Contrasena$2', + avatarOptions: { + hair: "short", + eyes: "happy", + mouth: "smile", + hairColor: "brown", + skinColor: "light" + } + }) + }); + } catch (e) { + console.warn("⚠️ El usuario ya puede existir o hubo un error al crearlo:", e.message); + } + browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch({headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox']}) - : await puppeteer.launch({ headless: false, slowMo: 100 }); + ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) + : await puppeteer.launch({ headless: false, slowMo: 10 }); page = await browser.newPage(); //Way of setting up the timeout setDefaultOptions({ timeout: 60000 }) @@ -20,69 +42,66 @@ defineFeature(feature, test => { .goto("http://localhost:3000", { waitUntil: "networkidle0", }) - .catch(() => {}); + .catch(() => { }); }); - test('User choose a category and start game', ({given,when,then}) => { - + test('User choose a category and start game', ({ given, when, then }) => { + let username; let password; let category; let dificulty; - given('Registered user login' , async () => { + given('Registered user login', async () => { // Definimos los datos de usuario y contraseña - username = "NataliaBA" - password = "Contrasena$1" + username = "NataliaBB"; + + password = "Contrasena$2"; // Definimos la categoría a seleccionar - category = "Lugares" + category = "Banderas" dificulty = "medio" // Introduces los datos de usuario y contraseña - await expect(page).toFill('[data-testid="username-field"] input', username); - await expect(page).toFill('[data-testid="password-field"] input', password); + await page.waitForSelector('[data-testid="username-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="username-field"] input', username, { timeout: 60000 }); - await expect(page).toClick("button", { text: "Login" }); + await page.waitForSelector('[data-testid="password-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="password-field"] input', password, { timeout: 60000 }); + + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick("button", { text: "Login", timeout: 60000 }); }); when('User choose category and press start button', async () => { - - // Abre el Select para escoger la categoría - await expect(page).toClick('[aria-labelledby="category-select-label"]'); - - // Escoge la categoría del menú desplegable - await page.click('li[data-value="'+category+'"]'); - - // Abre el Select para escoger la dificultad - await expect(page).toClick('[aria-labelledby="level-select-label"]'); + await page.waitForSelector('[data-testid="category-select"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('[data-testid="category-select"]', { timeout: 60000 }); - // Escoge la dificultad "Medio" del menú desplegable - await page.click('li[data-value="'+dificulty+'"]'); + await page.waitForSelector(`[data-testid="category-option-${category}"`, { visible: true, timeout: 60000 }); + await page.click(`[data-testid="category-option-${category}"`, { timeout: 60000 }); - // Finalmente, hacer clic en el botón para comenzar el juego. - await expect(page).toClick('button', { text: 'Comenzar a jugar' }); + await page.waitForSelector('[data-testid="level-select"]', { visible: true, timeout: 500000 }); + await expect(page).toClick('[data-testid="level-select"]', { timeout: 500000 }); - + await page.waitForSelector(`[data-testid="level-option-${dificulty}"]`, { visible: true, timeout: 500000 }); + await page.click(`[data-testid="level-option-${dificulty}"]`, { timeout: 500000 }); + await page.waitForSelector('[data-testid="start-game-button"]', { visible: true, timeout: 500000 }); + await expect(page).toClick('[data-testid="start-game-button"]', { text: 'Comenzar a jugar', timeout: 500000 }); }); then('Game start in this category', async () => { - - // Espera a que el div que contiene el texto sea visible. - await page.waitForSelector('div', { text: '¿A qué lugar corresponde la siguiente foto?' }); - - // Verifica que el texto esté presente en el div. - // Se comprueba el texto de la pregunta que se muestra en el juego. - // Ya que cada categoría tiene su propia pregunta. - await expect(page).toMatchElement('div', { text: '¿A qué lugar corresponde la siguiente foto?' }); - + await page.waitForFunction( + () => document.body.innerText.includes('¿De qué país es esta bandera?'), + { timeout: 500000 } + ); + await expect(page).toMatchElement('div', { text: '¿De qué país es esta bandera?', timeout: 500000 }); }); }) - afterAll(async ()=>{ + afterAll(async () => { browser.close() }) diff --git a/webapp/e2e/steps/game-play.steps.js b/webapp/e2e/steps/game-play.steps.js index 7f2e1a492..26f945e37 100644 --- a/webapp/e2e/steps/game-play.steps.js +++ b/webapp/e2e/steps/game-play.steps.js @@ -9,9 +9,31 @@ let browser; defineFeature(feature, test => { beforeAll(async () => { + // 1. Crear usuario de test si no existe + try { + await fetch(`http://localhost:8000/adduser`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: 'NataliaBC', + password: 'Contrasena$3', + confirmPassword: 'Contrasena$3', + avatarOptions: { + hair: "short", + eyes: "happy", + mouth: "smile", + hairColor: "brown", + skinColor: "light" + } + }) + }); + } catch (e) { + console.warn("⚠️ El usuario ya puede existir o hubo un error al crearlo:", e.message); + } + browser = process.env.GITHUB_ACTIONS ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) - : await puppeteer.launch({ headless: false, slowMo: 100 }); + : await puppeteer.launch({ headless: false, slowMo: 10 }); page = await browser.newPage(); //Way of setting up the timeout setDefaultOptions({ timeout: 60000 }) @@ -33,49 +55,53 @@ defineFeature(feature, test => { given('Registered user login', async () => { // Definimos los datos de usuario y contraseña - username = "NataliaBA" - password = "Contrasena$1" + username = "NataliaBC" + password = "Contrasena$3" // Definimos la categoría a seleccionar category = "Banderas" - dificulty = "facil" - + dificulty = "medio" + // Introduces los datos de usuario y contraseña - await expect(page).toFill('[data-testid="username-field"] input', username); - await expect(page).toFill('[data-testid="password-field"] input', password); + await page.waitForSelector('[data-testid="username-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="username-field"] input', username, { timeout: 60000 }); + await page.waitForSelector('[data-testid="password-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="password-field"] input', password, { timeout: 60000 }); - await expect(page).toClick("button", { text: "Login" }); + await page.waitForSelector("button", { visible: true, timeout: 60000 }); + await expect(page).toClick("button", { text: "Login", timeout: 60000 }); }); when('User choose category, press start button and play', async () => { - // Abre el Select para escoger la categoría (Futbolistas) - await expect(page).toClick('[aria-labelledby="category-select-label"]'); + await page.waitForSelector('[data-testid="category-select"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('[data-testid="category-select"]', { timeout: 60000 }); - // Escoge la categoría "Futbolistas" del menú desplegable - await page.click('li[data-value="'+category+'"]'); + await page.waitForSelector(`[data-testid="category-option-${category}"`, { visible: true, timeout: 60000 }); + await page.click(`[data-testid="category-option-${category}"`, { timeout: 60000 }); - // Abre el Select para escoger la dificultad - await expect(page).toClick('[aria-labelledby="level-select-label"]'); + await page.waitForSelector('[data-testid="level-select"]', { visible: true, timeout: 500000 }); + await expect(page).toClick('[data-testid="level-select"]', { timeout: 500000 }); - // Escoge la dificultad "Medio" del menú desplegable - await page.click('li[data-value="'+dificulty+'"]'); + await page.waitForSelector(`[data-testid="level-option-${dificulty}"]`, { visible: true, timeout: 500000 }); + await page.click(`[data-testid="level-option-${dificulty}"]`, { timeout: 500000 }); - // Finalmente, hacer clic en el botón para comenzar el juego. - await expect(page).toClick('button', { text: 'Comenzar a jugar' }); + await page.waitForSelector('[data-testid="start-game-button"]', { visible: true, timeout: 500000 }); + await expect(page).toClick('[data-testid="start-game-button"]', { text: 'Comenzar a jugar', timeout: 500000 }); + // Realiza 10 respuestas durante el juego for (let i = 0; i < 10; i++) { // Esperamos que se cargue la imagen - await page.waitForSelector('img[alt="Imagen del juego"]', { visible: true }); + await page.waitForSelector('[data-testid="image-game"]', { visible: true, timeout: 500000 }); // Esperamos a que las opciones estén disponibles - await page.waitForSelector(`[data-testid^="respuesta-"]`, { visible: true }); + await page.waitForSelector(`[data-testid^="respuesta-"]`, { visible: true, timeout: 500000 }); // Seleccionamos todas las opciones disponibles const opciones = await page.$$(`[data-testid^="respuesta-"]`); if (opciones.length > 0) { // Hacer clic en la primera opción disponible - await opciones[0].click(); + await opciones[0].click({ timeout: 500000 }); } // Esperar un poco antes de continuar @@ -84,7 +110,7 @@ defineFeature(feature, test => { }); then('User sees the game summary', async () => { - await page.waitForSelector('h4', { text: 'Resumen del Juego', timeout: 10000 }); + await page.waitForSelector('[data-testid="resumenJuego"]', { text: 'Resumen del Juego', timeout: 500000 }); const resumenTexto = await page.$eval('h4', el => el.textContent); expect(resumenTexto).toContain('Resumen del Juego'); }); diff --git a/webapp/e2e/steps/history.steps.js b/webapp/e2e/steps/history.steps.js index 10ef81dd5..7752a60ea 100644 --- a/webapp/e2e/steps/history.steps.js +++ b/webapp/e2e/steps/history.steps.js @@ -1,5 +1,6 @@ const puppeteer = require('puppeteer'); const { defineFeature, loadFeature } = require('jest-cucumber'); +const { response } = require('../../../wikidata/src/wikidataRoutes'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; const feature = loadFeature('./features/history.feature'); @@ -9,10 +10,74 @@ let browser; defineFeature(feature, test => { beforeAll(async () => { + let userId = null; + + // 1. Crear usuario de test si no existe + try { + const res = await fetch(`http://localhost:8000/adduser`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: 'NataliaBA', + password: 'Contrasena$1', + confirmPassword: 'Contrasena$1', + avatarOptions: { + hair: "short", + eyes: "happy", + mouth: "smile", + hairColor: "brown", + skinColor: "light" + } + }) + }); + + const user = await res.json(); + userId = user._id; // Guardar el ID del usuario creado + } catch (e) { + console.warn("⚠️ El usuario ya puede existir o hubo un error al crearlo:", e.message); + + // Si ya existe, recuperamos su ID con un GET (asumiendo que hay endpoint) + const res = await fetch(`http://localhost:8000/user/username/NataliaBA`); + const user = await res.json(); + userId = user._id; + } + + // 2. Insertar historial (partida de prueba) + if (userId) { + try { + await fetch(`http://localhost:3001/game/start`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ userId }) + }); + + await fetch(`http://localhost:3001/game/end`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId, + username: 'NataliaBA', + duration: 20, + correct: 9, + wrong: 1, + isCompleted: true, + category: 'Ciencia', + level: 'Difícil', + totalQuestions: 10, + answered: 10, + points: 90 + }) + }); + } catch (e) { + console.error("❌ Error al insertar partida de prueba:", e.message); + } + } + browser = process.env.GITHUB_ACTIONS ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) - : await puppeteer.launch({ headless: false, slowMo: 100, defaultViewport: { width: 1920, height: 1080 } }); + : await puppeteer.launch({ headless: false, slowMo: 10, defaultViewport: { width: 1920, height: 1080 } }); page = await browser.newPage(); + await page.setViewport({ width: 1200, height: 800 }); setDefaultOptions({ timeout: 60000 }); await page @@ -28,20 +93,24 @@ defineFeature(feature, test => { let password; given('Registered user login', async () => { - username = "NataliaBA" - password = "Contrasena$1" + username = "NataliaBA"; + password = "Contrasena$1"; - await expect(page).toFill('[data-testid="username-field"] input', username); - await expect(page).toFill('[data-testid="password-field"] input', password); - await expect(page).toClick("button", { text: "Login" }); + await page.waitForSelector('[data-testid="username-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="username-field"] input', username, { timeout: 60000 }); + await page.waitForSelector('[data-testid="password-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="password-field"] input', password, { timeout: 60000 }); + await page.waitForSelector("button", { text: "Login", timeout: 60000 }); + await expect(page).toClick("button", { text: "Login", timeout: 60000 }); }); when('User navigates to the history page', async () => { - await expect(page).toClick('a', { text: 'Historial' }); + await page.waitForSelector('[data-testid="nav-history"]', { text: "Historial", timeout: 500000 }); + await expect(page).toClick('[data-testid="nav-history"]'); }); then('User sees a list of past games', async () => { - await page.waitForSelector('h4', { text: 'Historial de Partidas', timeout: 10000 }); + await page.waitForSelector('[data-testid="history-title"]', { text: 'Historial de Partidas', timeout: 500000 }); const historyHeader = await page.$eval('h4', el => el.textContent); expect(historyHeader).toContain('Historial de Partidas'); }); diff --git a/webapp/e2e/steps/login-form.steps.js b/webapp/e2e/steps/login-form.steps.js index 21d4100c5..4a70e7770 100644 --- a/webapp/e2e/steps/login-form.steps.js +++ b/webapp/e2e/steps/login-form.steps.js @@ -10,10 +10,32 @@ const webappIp = process.env.DEPLOY_HOST || 'localhost'; const webappPort = process.env.WEBAPP_PORT || '3000'; defineFeature(feature, test => { - + beforeAll(async () => { + // 1. Crear usuario de test si no existe + try { + await fetch(`http://localhost:8000/adduser`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: 'NataliaBA', + password: 'Contrasena$1', + confirmPassword: 'Contrasena$1', + avatarOptions: { + hair: "short", + eyes: "happy", + mouth: "smile", + hairColor: "brown", + skinColor: "light" + } + }) + }); + } catch (e) { + console.warn("⚠️ El usuario ya puede existir o hubo un error al crearlo:", e.message); + } + browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch({headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox']}) + ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) : await puppeteer.launch({ headless: false, slowMo: 100 }); page = await browser.newPage(); setDefaultOptions({ timeout: 10000 }); @@ -22,26 +44,36 @@ defineFeature(feature, test => { .goto(`http://${webappIp}:${webappPort}`, { waitUntil: "networkidle0", }) - .catch(() => {}); + .catch(() => { }); }); - test('The user has an account and wants to log in', ({given, when, then}) => { - - username = "NataliaBA" - password = "Contrasena$1" + test('The user has an account and wants to log in', ({ given, when, then }) => { + + const username = "NataliaBA"; + const password = "Contrasena$1"; given('A registered user', async () => { - await expect(page).toMatchElement('h1', { text: "Log in to your account" }); + await page.waitForSelector('h1', { visible: true, timeout: 60000 }); + await expect(page).toMatchElement('h1', { text: "Log in to your account", timeout: 60000 }); }); when('I fill the login credentials and press submit', async () => { - await expect(page).toFill('[data-testid="username-field"] input', username); - await expect(page).toFill('[data-testid="password-field"] input', password); - await expect(page).toClick('button', { text: 'Login' }); + await page.waitForSelector('[data-testid="username-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="username-field"] input', username, { timeout: 60000 }); + + await page.waitForSelector('[data-testid="password-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="password-field"] input', password, { timeout: 60000 }); + + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'Login', timeout: 60000 }); }); then('I should be logged in successfully', async () => { - await expect(page).toMatchElement('h1', { text: "WICHAT" }); + await page.waitForFunction( + () => document.body.innerText.includes('WICHAT'), + { timeout: 60000 } + ); + await expect(page).toMatchElement('h1', { text: "WICHAT", timeout: 60000 }); }); }); diff --git a/webapp/e2e/steps/logout-session.steps.js b/webapp/e2e/steps/logout-session.steps.js index 437213c98..3d0b04e89 100644 --- a/webapp/e2e/steps/logout-session.steps.js +++ b/webapp/e2e/steps/logout-session.steps.js @@ -9,6 +9,28 @@ let browser; defineFeature(feature, test => { beforeAll(async () => { + // 1. Crear usuario de test si no existe + try { + await fetch(`http://localhost:8000/adduser`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + username: 'NataliaBA', + password: 'Contrasena$1', + confirmPassword: 'Contrasena$1', + avatarOptions: { + hair: "short", + eyes: "happy", + mouth: "smile", + hairColor: "brown", + skinColor: "light" + } + }) + }); + } catch (e) { + console.warn("⚠️ El usuario ya puede existir o hubo un error al crearlo:", e.message); + } + browser = process.env.GITHUB_ACTIONS ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) : await puppeteer.launch({ headless: false, slowMo: 100 }); @@ -28,11 +50,16 @@ defineFeature(feature, test => { let password; given('User in home view', async () => { - username = "NataliaBA" - password = "Contrasena$1" - await expect(page).toFill('[data-testid="username-field"] input', username); - await expect(page).toFill('[data-testid="password-field"] input', password); - await expect(page).toClick("button", { text: "Login" }); + username = "NataliaBD" + password = "Contrasena$4" + await page.waitForSelector('[data-testid="username-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="username-field"] input', username, { timeout: 60000 }); + + await page.waitForSelector('[data-testid="password-field"] input', { visible: true, timeout: 60000 }); + await expect(page).toFill('[data-testid="password-field"] input', password, { timeout: 60000 }); + + await page.waitForSelector("button", { visible: true, timeout: 60000 }); + await expect(page).toClick("button", { text: "Login", timeout: 60000 }); }); @@ -40,16 +67,16 @@ defineFeature(feature, test => { // 1. Click en el botón de menú (usando tu XPath original) const [menuButton] = await page.$x('//*[@id="root"]/div/header/div/div/div[3]/button'); if (!menuButton) throw new Error('Botón de menú no encontrado'); - await menuButton.click(); - + await menuButton.click({ timeout: 60000 }); + // 2. Esperar y hacer clic en el
  • que contiene un

    con el texto exacto const [logoutItem] = await page.$x('//li[.//p[normalize-space()="Cerrar Sesión"]]'); if (!logoutItem) throw new Error('Opción de logout no encontrada'); - await logoutItem.click(); + await logoutItem.click({ timeout: 60000 }); }); then('User logs out and sees the login view', async () => { - await page.waitForSelector('h1', { text: 'Log in to your account', timeout: 10000 }); + await page.waitForSelector('h1', { visible: true, text: 'Log in to your account', timeout: 60000 }); const titulo = await page.$eval('h1', el => el.textContent); expect(titulo).toContain('Log in to your account'); }); diff --git a/webapp/e2e/steps/register-and-avatar.steps.js b/webapp/e2e/steps/register-and-avatar.steps.js index 68ba6f545..418030811 100644 --- a/webapp/e2e/steps/register-and-avatar.steps.js +++ b/webapp/e2e/steps/register-and-avatar.steps.js @@ -1,5 +1,5 @@ const puppeteer = require('puppeteer'); -const { defineFeature, loadFeature }=require('jest-cucumber'); +const { defineFeature, loadFeature } = require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/register-and-avatar.feature'); @@ -7,10 +7,10 @@ let page; let browser; defineFeature(feature, test => { - + beforeAll(async () => { browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch({headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox']}) + ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) : await puppeteer.launch({ headless: false, slowMo: 100 }); page = await browser.newPage(); //Way of setting up the timeout @@ -20,51 +20,70 @@ defineFeature(feature, test => { .goto("http://localhost:3000", { waitUntil: "networkidle0", }) - .catch(() => {}); + .catch(() => { }); }); - test('User is not registered in the site', ({given,when,then}) => { - + test('User is not registered in the site', ({ given, when, then }) => { + let username; let password; given('An unregistered user', async () => { - username = "prueba" + Math.floor(Math.random() * 1000); + username = "prueba1" + Math.floor(Math.random() * 1000); password = "EstoEsUnaPassDePrueba123." - await expect(page).toClick("a", { text: "Sign up" }); - + await page.waitForSelector("a", { text: "Sing up", timeout: 60000 }); + await expect(page).toClick("a", { text: "Sign up", timeout: 60000 }); + }); when('I fill in the data in the form, edit my avatar, and press send.', async () => { - await expect(page).toFill('input[name="username"]', username); - await expect(page).toFill('input[name="password"]', password); - await expect(page).toFill('input[name="confirmPassword"]', password); + await page.waitForSelector('input[name="username"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="username"]', username, { timeout: 60000 }); + + await page.waitForSelector('input[name="password"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="password"]', password, { timeout: 60000 }); + + await page.waitForSelector('input[name="confirmPassword"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="confirmPassword"]', password, { timeout: 60000 }); // Piel - await expect(page).toClick('button[aria-label="skin"]'); - await expect(page).toClick('button', { text: 'Bronze' }); + await page.waitForSelector('button[aria-label="skin"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('button[aria-label="skin"]', { timeout: 60000 }); + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'Bronze', timeout: 60000 }); // Pelo - await expect(page).toClick('button[aria-label="hair"]'); - await expect(page).toClick('button', { text: 'Blonde' }); - await expect(page).toClick('button', { text: 'curlyShortHair' }); + await page.waitForSelector('button[aria-label="hair"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('button[aria-label="hair"]', { timeout: 60000 }); + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'Blonde', timeout: 60000 }); + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'curlyShortHair', timeout: 60000 }); // Ojos - await expect(page).toClick('button[aria-label="eyes"]'); - await expect(page).toClick('button', { text: 'starstruck' }); + await page.waitForSelector('button[aria-label="eyes"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('button[aria-label="eyes"]', { timeout: 60000 }); + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'starstruck', timeout: 60000 }); // Boca - await expect(page).toClick('button[aria-label="mouth"]'); - await expect(page).toClick('button', { text: 'unimpressed' }); - await expect(page).toClick('button', { text: 'Sign up' }) + await page.waitForSelector('button[aria-label="mouth"]', { visible: true, timeout: 60000 }); + await expect(page).toClick('button[aria-label="mouth"]', { timeout: 60000 }); + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'unimpressed', timeout: 60000 }); + + // Enviar el formulario + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'Sign up', timeout: 60000 }); }); then('The user should be redirected to the Login page', async () => { - await expect(page).toMatchElement("h1", { text: "Log in to your account" }); + await page.waitForSelector("h1", { visible: true, timeout: 60000 }); + await expect(page).toMatchElement("h1", { text: "Log in to your account", timeout: 60000 }); }); }) - afterAll(async ()=>{ + afterAll(async () => { browser.close() }) diff --git a/webapp/e2e/steps/register-form.steps.js b/webapp/e2e/steps/register-form.steps.js index 1b79570a5..ab939d641 100644 --- a/webapp/e2e/steps/register-form.steps.js +++ b/webapp/e2e/steps/register-form.steps.js @@ -1,5 +1,5 @@ const puppeteer = require('puppeteer'); -const { defineFeature, loadFeature }=require('jest-cucumber'); +const { defineFeature, loadFeature } = require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/register-form.feature'); @@ -7,48 +7,56 @@ let page; let browser; defineFeature(feature, test => { - + beforeAll(async () => { browser = process.env.GITHUB_ACTIONS - ? await puppeteer.launch({headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox']}) + ? await puppeteer.launch({ headless: "new", args: ['--no-sandbox', '--disable-setuid-sandbox'] }) : await puppeteer.launch({ headless: false, slowMo: 100 }); page = await browser.newPage(); //Way of setting up the timeout - setDefaultOptions({ timeout: 10000 }) + setDefaultOptions({ timeout: 60000 }) await page .goto("http://localhost:3000", { waitUntil: "networkidle0", }) - .catch(() => {}); + .catch(() => { }); }); - test('The user is not registered in the site', ({given,when,then}) => { - + test('The user is not registered in the site', ({ given, when, then }) => { + let username; let password; given('An unregistered user', async () => { - username = "prueba" + Math.floor(Math.random() * 1000); - password = "EstoEsUnaPassDePrueba123." - await expect(page).toClick("a", { text: "Sign up" }); - + username = "prueba2" + Math.floor(Math.random() * 1000); + password = "EstoEsUnaPassDePrueba123-" + await page.waitForSelector("a", { text: "Sign up", timeout: 60000 }); + await expect(page).toClick("a", { text: "Sign up", timeout: 60000 }); + }); when('I fill the data in the form and press submit', async () => { - await expect(page).toFill('input[name="username"]', username); - await expect(page).toFill('input[name="password"]', password); - await expect(page).toFill('input[name="confirmPassword"]', password); + await page.waitForSelector('input[name="username"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="username"]', username, { timeout: 60000 }); + + await page.waitForSelector('input[name="password"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="password"]', password, { timeout: 60000 }); + + await page.waitForSelector('input[name="confirmPassword"]', { visible: true, timeout: 60000 }); + await expect(page).toFill('input[name="confirmPassword"]', password, { timeout: 60000 }); - await expect(page).toClick('button', { text: 'Sign up' }) + await page.waitForSelector('button', { visible: true, timeout: 60000 }); + await expect(page).toClick('button', { text: 'Sign up', timeout: 60000 }); }); then('The user should be redirected to the Login page', async () => { - await expect(page).toMatchElement("h1", { text: "Log in to your account" }); + await page.waitForSelector("h1", { visible: true, timeout: 60000 }); + await expect(page).toMatchElement("h1", { text: "Log in to your account", timeout: 60000 }); }); }) - afterAll(async ()=>{ + afterAll(async () => { browser.close() }) diff --git a/webapp/e2e/test-environment-setup.js b/webapp/e2e/test-environment-setup.js index de2db6c24..96497ca3a 100644 --- a/webapp/e2e/test-environment-setup.js +++ b/webapp/e2e/test-environment-setup.js @@ -17,7 +17,12 @@ async function startServer() { authservice = await require("../../users/authservice/auth-service"); llmservice = await require("../../llmservice/llm-service"); gatewayservice = await require("../../gatewayservice/gateway-service"); - wikidataservice = await require("../../wikidata/src/wikidataRoutes"); + const wikidataApp = require("../../wikidata/src/wikidataRoutes"); + const wikidataServer = wikidataApp.listen(3001, () => { + console.log("📚 Wikidata Service listening at http://localhost:3001"); + }); + process.env.WIKIDATA_PORT = 3001; + groupservice = await require("../../users/groupservice/group-service"); } diff --git a/webapp/package-lock.json b/webapp/package-lock.json index ce2a7dde2..af1963f67 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -74,14 +74,14 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" @@ -369,18 +369,18 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -410,25 +410,25 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", - "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", - "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.3" + "@babel/types": "^7.27.1" }, "bin": { "parser": "bin/babel-parser.js" @@ -1993,26 +1993,23 @@ } }, "node_modules/@babel/runtime": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", - "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", + "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2037,13 +2034,13 @@ } }, "node_modules/@babel/types": { - "version": "7.26.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", - "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -5850,9 +5847,9 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -11230,9 +11227,9 @@ } }, "node_modules/http-proxy-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz", - "integrity": "sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", @@ -21263,12 +21260,6 @@ "node": ">=4" } }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/regenerator-transform": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", diff --git a/webapp/src/components/AddUser.js b/webapp/src/components/AddUser.js index f6f93f5bd..b01316db4 100644 --- a/webapp/src/components/AddUser.js +++ b/webapp/src/components/AddUser.js @@ -1,5 +1,5 @@ // src/components/AddUser.js -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import axios from 'axios'; import { Container, Grid, Typography, TextField, @@ -25,7 +25,6 @@ const AddUser = () => { const [showPassword, setShowPassword] = useState(false); // mostrar contraseña const [showConfirmPassword, setShowConfirmPassword] = useState(false); // mostrar confirmar contraseña // Para el avatar - const [avatarUrl, setAvatarUrl] = useState(''); const [avatarOptions, setAvatarOptions] = useState({ hairColor: '3a1a00', eyes: 'cheery', @@ -36,13 +35,6 @@ const AddUser = () => { const navigate = useNavigate(); - // Según vayamos cambiando el avatar, se va mostrando - useEffect(() => { - const { hairColor, eyes, hair, mouth, skinColor } = avatarOptions; - const newAvatarUrl = `https://api.dicebear.com/9.x/big-smile/svg?hairColor=${hairColor}&eyes=${eyes}&hair=${hair}&mouth=${mouth}&skinColor=${skinColor}`; - setAvatarUrl(newAvatarUrl); - }, [avatarOptions]); - const addUser = async () => { try { await axios.post(`${apiEndpoint}/adduser`, { diff --git a/webapp/src/components/Contact.jsx b/webapp/src/components/Contact.jsx index 4d0377bf2..f603a82b3 100644 --- a/webapp/src/components/Contact.jsx +++ b/webapp/src/components/Contact.jsx @@ -11,7 +11,7 @@ const teamMembers = [ { name: 'Marcos Llanos Vega', role: 'Desarrollador Frontend', email: 'UO218982@uniovi.com', github: 'https://github.com/softwaremarcos', avatar: imageM }, { name: 'David Covián Gómez', role: 'Desarrollador Backend', email: 'UO295168@uniovi.com', github: 'https://github.com/DavidCG-27', avatar: imageM }, { name: 'Darío Cristóbal González', role: 'Diseñador Backend', email: 'UO294401@uniovi.com', github: 'https://github.com/daariio92', avatar: imageM }, - { name: 'Hugo Fernández Rogríguez', role: 'Diseñador LLM y despliegue', email: 'UO289157@uniovi.com', github: 'https://github.com/hugo-fdez', avatar: imageM }, + { name: 'Hugo Fernández Rodríguez', role: 'Diseñador LLM y despliegue', email: 'UO289157@uniovi.com', github: 'https://github.com/hugo-fdez', avatar: imageM }, { name: 'Hugo Prendes Menéndez', role: 'Diseñador LLM', email: 'UO288294@uniovi.com', github: 'https://github.com/prendess', avatar: imageM }, ]; @@ -59,7 +59,7 @@ const Contact = () => { }}> {/* Avatar con borde redondeado */} - User's avatar { 'David Covián Gómez', 'Marcos Llanos Vega', 'Darío Cristóbal González', - 'Hugo Fernández Rogríguez', + 'Hugo Fernández Rodríguez', 'Hugo Prendes Menéndez' ]; const roles = [ diff --git a/webapp/src/components/FriendList.jsx b/webapp/src/components/FriendList.jsx index afc7a264c..bd3b69251 100644 --- a/webapp/src/components/FriendList.jsx +++ b/webapp/src/components/FriendList.jsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import profilePic from '../media/fotousuario.png'; import { Box, Typography, List, ListItem, ListItemAvatar, Avatar, ListItemText, IconButton, Dialog, DialogActions, DialogContent, DialogTitle, Button } from '@mui/material'; import { useTheme } from '@mui/material/styles'; import axios from 'axios'; @@ -10,8 +9,6 @@ import HistoryIcon from '@mui/icons-material/History'; const apiEndpoint = process.env.REACT_APP_GATEWAY_URL || 'http://localhost:8000'; -const apiEndpoint2 = process.env.USER_SERVICE_ENDPOINT || 'http://localhost:8001'; - // Función para construir la URL del avatar de DiceBear desde avatarOptions const getAvatarUrl = (options) => { if (!options) return ''; diff --git a/webapp/src/components/GameHistoryTable.jsx b/webapp/src/components/GameHistoryTable.jsx index ed43d2e7f..64affa3fe 100644 --- a/webapp/src/components/GameHistoryTable.jsx +++ b/webapp/src/components/GameHistoryTable.jsx @@ -22,7 +22,7 @@ const GameHistoryTable = ({ gameHistory = [], theme }) => { const usedTheme = theme || defaultTheme; return ( - + Historial de Partidas diff --git a/webapp/src/components/GameHistoryUI.jsx b/webapp/src/components/GameHistoryUI.jsx index c77561dea..f2756a453 100644 --- a/webapp/src/components/GameHistoryUI.jsx +++ b/webapp/src/components/GameHistoryUI.jsx @@ -70,7 +70,7 @@ const GameHistoryUI = () => { if (!Array.isArray(gameHistory) || gameHistory.length === 0) { return ( - Historial de Partidas + Historial de Partidas No hay partidas registradas aún. ); diff --git a/webapp/src/components/GamePanel.jsx b/webapp/src/components/GamePanel.jsx index 45868009c..55ba126da 100644 --- a/webapp/src/components/GamePanel.jsx +++ b/webapp/src/components/GamePanel.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import { Box, Grid, Paper, Typography, Button, CircularProgress } from '@mui/material'; import { MessageCircle } from 'lucide-react'; import ChatPanel from './ChatPanel'; @@ -54,16 +54,16 @@ const GamePanel = () => { const [isAnswered, setIsAnswered] = useState(false); - const getQuestions = async () => { + const getQuestions = useCallback(async () => { try { const response = await axios.get(`${apiEndpoint}/wikidata/question/`+category+`/`+TOTAL_QUESTIONS); const data = response.data; if (data && data.length === TOTAL_QUESTIONS) {data.map(question => ({...question,userCategory: category}));setQuestions(data); } else {getQuestions();} - } catch (error) {}}; + } catch (error) {}}, [category]); - const chooseQuestion = async () => { + const chooseQuestion = useCallback(async () => { if (questions.length === 0) {await getQuestions();return;} const randomIndex = Math.floor(Math.random() * questions.length); const question = questions[randomIndex]; @@ -71,7 +71,7 @@ const GamePanel = () => { let options = question.options || []; if (!options.includes(question.answer)) {options.push(question.answer);} options = options.sort(() => Math.random() - 0.5); - setImageLoaded(false);setQuestionData({question: question.statements,image: question.image,options: options,correctAnswer: question.answer,userCategory: category});}; + setImageLoaded(false);setQuestionData({question: question.statements,image: question.image,options: options,correctAnswer: question.answer,userCategory: category});}, [questions, category, getQuestions]); const resetCountdownTime = () => {setCountdownKey(prev => prev + 1);} @@ -126,29 +126,29 @@ const getUserData = () => { return { userId, username }; } catch {return {};}}; - const startGame = async () => { + const startGame = useCallback(async () => { try { const userId = getUserId(); await axios.post(`${apiEndpoint}/game/start`, { userId }); } catch (error) { console.error('Error al iniciar el juego:', error); } -}; +}, []); const endGame = async () => { try { const userId = getUserId(); - const { id, username } = getUserData(); + const { username } = getUserData(); if (userId) {await axios.post(`${apiEndpoint}/game/end`, {userId, username,category: category,level: level,totalQuestions: TOTAL_QUESTIONS,answered: numberOfQuestionsAnswered,correct: correctCount, wrong: incorrectCount,points: scorePoints});} } catch (error) {}}; -useEffect(() => {startGame();}, []); +useEffect(() => {startGame();}, [startGame]); - useEffect(() => {getQuestions();}, []); + useEffect(() => {getQuestions();}, [getQuestions]); useEffect(() => { if (questionData.question === '' && questions.length > 0 && !gameEnded) {chooseQuestion();} - }, [questions, gameEnded, questionData.question]); + }, [questions, gameEnded, questionData.question, chooseQuestion]); useEffect(() => { if (questionData.question !== '' && imageLoaded && initialLoading) {setInitialLoading(false);} @@ -179,7 +179,7 @@ useEffect(() => {startGame();}, []); if (gameEnded) {endGame();const performanceMessage = correctCount >= TOTAL_QUESTIONS / 2 ? "¡Buen trabajo!" : "¡Sigue intentando!"; return ( - Resumen del Juego + Resumen del Juego Preguntas totales de la partida: {TOTAL_QUESTIONS} Preguntas contestadas: {numberOfQuestionsAnswered} Respuestas correctas: {correctCount} @@ -197,7 +197,7 @@ useEffect(() => {startGame();}, []); {questionData.question} - {!imageLoaded && (Cargando pregunta...)} setImageLoaded(true)} onError={() => setImageLoaded(true)}style={{width: '100%',maxWidth: '70vw',maxHeight: '40vh', height: '50%', objectFit: 'contain', opacity: imageLoaded ? 1 : 0.7,}}/> + {!imageLoaded && (Cargando pregunta...)} setImageLoaded(true)} onError={() => setImageLoaded(true)}style={{width: '100%',maxWidth: '70vw',maxHeight: '40vh', height: '50%', objectFit: 'contain', opacity: imageLoaded ? 1 : 0.7,}}/> {questionData.options.map((respuesta, index) => ())} {showChat && ()} diff --git a/webapp/src/components/GlobalRanking.jsx b/webapp/src/components/GlobalRanking.jsx index a62f1193a..a6f89337a 100644 --- a/webapp/src/components/GlobalRanking.jsx +++ b/webapp/src/components/GlobalRanking.jsx @@ -18,25 +18,25 @@ import { useTheme } from '@mui/material/styles'; const apiEndpoint = process.env.REACT_APP_GATEWAY_URL || 'http://localhost:8000'; const GlobalRanking = () => { - const getUserId = () => { - try { - const userDataStr = window.localStorage.getItem('user'); - if (!userDataStr) return null; + // const getUserId = () => { + // try { + // const userDataStr = window.localStorage.getItem('user'); + // if (!userDataStr) return null; - const userData = JSON.parse(userDataStr); - const parsedToken = userData?.token; + // const userData = JSON.parse(userDataStr); + // const parsedToken = userData?.token; - if (parsedToken) { - const decoded = JSON.parse(atob(parsedToken.split('.')[1])); - return decoded?.userId || null; - } + // if (parsedToken) { + // const decoded = JSON.parse(atob(parsedToken.split('.')[1])); + // return decoded?.userId || null; + // } - return null; - } catch (error) { - console.error("Error al recuperar userId:", error); - return null; - } - }; + // return null; + // } catch (error) { + // console.error("Error al recuperar userId:", error); + // return null; + // } + // }; const theme = useTheme(); // const [gameHistory, setGameHistory] = useState([]); const [globalRanking, setGlobalRanking] = useState([]); diff --git a/webapp/src/components/GroupChat.jsx b/webapp/src/components/GroupChat.jsx index 8689c3236..60a29f848 100644 --- a/webapp/src/components/GroupChat.jsx +++ b/webapp/src/components/GroupChat.jsx @@ -31,7 +31,7 @@ const GroupChat = ({ groupName, onClose }) => { const [error, setError] = useState(null); const chatEndRef = useRef(null); const { username } = getUserData(); - const [polling, setPolling] = useState(true); + const [polling] = useState(true); useEffect(() => { let interval; diff --git a/webapp/src/components/Home.jsx b/webapp/src/components/Home.jsx index a1f9d683f..632713c3a 100644 --- a/webapp/src/components/Home.jsx +++ b/webapp/src/components/Home.jsx @@ -86,6 +86,7 @@ const Home = () => { Seleccionar categoría { displayEmpty > {Object.entries(gameLevels).map(([key, label]) => ( - + {label} ))} @@ -133,6 +135,7 @@ const Home = () => { @@ -125,7 +133,7 @@ const Nav = () => { {/* Icons */} - setAnchorElUser(e.currentTarget)} color="inherit"> + setAnchorElUser(e.currentTarget)} color="inherit" data-testid="nav-mobile-menu-button"> diff --git a/webapp/src/components/PrivateChat.jsx b/webapp/src/components/PrivateChat.jsx index 21ff5f855..2b3ef67eb 100644 --- a/webapp/src/components/PrivateChat.jsx +++ b/webapp/src/components/PrivateChat.jsx @@ -54,7 +54,8 @@ function PrivateChat() { fetchMessages(); const interval = setInterval(fetchMessages, 3000); // actualización cada 3 segundos return () => clearInterval(interval); - }, [username, friendUsername]); + // }, [username, friendUsername]); + }); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); diff --git a/webapp/src/components/Profile.jsx b/webapp/src/components/Profile.jsx index 11abdc09b..ccc93733a 100644 --- a/webapp/src/components/Profile.jsx +++ b/webapp/src/components/Profile.jsx @@ -59,7 +59,7 @@ const ProfilePage = () => { mouth: options.mouth, hairColor: options.hairColor, skinColor: options.skinColor - + }); return `${base}?${params.toString()}`; } @@ -104,6 +104,47 @@ const ProfilePage = () => { navigate('/login'); }; + // Datos para el gráfico de estadísticas + let correctAnswers = 0; + let wrongAnswers = 0; + + if (Array.isArray(gameHistory)) { + correctAnswers = gameHistory.reduce((total, game) => total + game.correct, 0); + wrongAnswers = gameHistory.reduce((total, game) => total + game.wrong, 0); + } + + + const data = { + labels: ['Correctas', 'Erróneas'], + datasets: [ + { + label: 'Estadísticas de Juego', + data: [correctAnswers, wrongAnswers], + backgroundColor: [ + theme.palette.green.main, + theme.palette.red.main + + ], + hoverBackgroundColor: [ + theme.palette.green.dark, + theme.palette.red.dark + ], + }, + ], + }; + + const options = { + responsive: true, + plugins: { + legend: { + position: 'top', + } + }, + maintainAspectRatio: false, + } + + + if (loading) { return ( @@ -138,9 +179,9 @@ const ProfilePage = () => { {/* Columna derecha: Información del usuario */} - Aquí puedes ver y editar tu perfil. @@ -168,38 +209,8 @@ const ProfilePage = () => { ) } - // Datos para el gráfico de estadísticas - const correctAnswers = gameHistory.reduce((total, game) => total + game.correct, 0); - const wrongAnswers = gameHistory.reduce((total, game) => total + game.wrong, 0); - - const data = { - labels: ['Correctas', 'Erróneas'], - datasets: [ - { - label: 'Estadísticas de Juego', - data: [correctAnswers, wrongAnswers], - backgroundColor: [ - theme.palette.green.main, - theme.palette.red.main - ], - hoverBackgroundColor: [ - theme.palette.green.dark, - theme.palette.red.dark - ], - }, - ], - }; - const options = { - responsive: true, - plugins: { - legend: { - position: 'top', - } - }, - maintainAspectRatio: false, - } return ( @@ -234,8 +245,8 @@ const ProfilePage = () => { {/* Columna derecha: Información del usuario */} - Aquí puedes ver y editar tu perfil. diff --git a/webapp/src/components/Score.jsx b/webapp/src/components/Score.jsx index 5dc6cd5ca..2ef298d93 100644 --- a/webapp/src/components/Score.jsx +++ b/webapp/src/components/Score.jsx @@ -64,6 +64,9 @@ const Score = forwardRef(({ currentQuestion, scoreLevel, answered, trues, false case 'dificil': truesPoints = 700; break; + default: + truesPoints = 500; + break; } // Puntos por fallo por nivel @@ -78,6 +81,9 @@ const Score = forwardRef(({ currentQuestion, scoreLevel, answered, trues, false case 'dificil': falsesPoints = -300; break; + default: + falsesPoints = -200; + break; } // Se quita 10 puntos por cada segundo que se ha tardado en responder diff --git a/webapp/src/components/UserGroups.jsx b/webapp/src/components/UserGroups.jsx index 28c5864c5..2683b5275 100644 --- a/webapp/src/components/UserGroups.jsx +++ b/webapp/src/components/UserGroups.jsx @@ -42,7 +42,7 @@ const UserGroups = () => { useEffect(() => { fetchGroups(); - }, []); + }); const fetchGroups = async () => { try {