diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1167a28f..3b79addb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: Build on: push: - branches: [master, develop, develop-deploy, develop-teresa] + branches: [master, develop, develop-deploy, develop-teresa, develop_samuel] pull_request: types: [opened, synchronize, reopened] diff --git a/docker-compose.yml b/docker-compose.yml index c431c1c6..57e27250 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,7 @@ services: - "27017:27017" networks: - mynetwork + restart: always mariadb: container_name: mariadb @@ -24,6 +25,7 @@ services: - "3306:3306" networks: - mynetwork + restart: always users: container_name: users @@ -38,6 +40,7 @@ services: - mynetwork environment: DATABASE_URL: mariadb:3306/userdb + restart: always questions: container_name: questions @@ -53,6 +56,7 @@ services: environment: DATABASE_URI: mongodb://mongodb:27017/questionDB TEST_DATABASE_URI: mongodb://mongodb:27017/test + restart: always gatewayservice: container_name: gatewayservice @@ -69,6 +73,7 @@ services: environment: USER_SERVICE_URL: http://users:8001 QUESTION_SERVICE_URL: http://questions:8010 + restart: always webapp: container_name: webapp @@ -85,6 +90,7 @@ services: environment: REACT_APP_API_ENDPOINT: http://gatewayservice:8000 REACT_APP_MULTIPLAYER_ENDPOINT: http://multiplayer:5010 + restart: always multiplayer: container_name: multiplayer @@ -100,6 +106,7 @@ services: environment: WEBAPP_ENDPOINT: http://webapp:3000 GATEWAY_SERVICE_ENDPOINT: http://gatewayservice:8000 + restart: always prometheus: image: prom/prometheus @@ -114,6 +121,7 @@ services: - "9090:9090" depends_on: - gatewayservice + restart: always grafana: image: grafana/grafana @@ -133,6 +141,7 @@ services: - "9091:9091" depends_on: - prometheus + restart: always volumes: mariadb_data: diff --git a/gatewayservice/__tests/gateway-service.test.js b/gatewayservice/__tests/gateway-service.test.js index df726b7a..40eee923 100644 --- a/gatewayservice/__tests/gateway-service.test.js +++ b/gatewayservice/__tests/gateway-service.test.js @@ -15,7 +15,7 @@ describe('Routes Tests', () => { process.exit(0); }, 5000); - it('should respond with status 200 for /user/questionsRecord/:username/:gameMode endpoint', async () => { + it('should respond with status 200 for /questionsRecord/:username/:gameMode endpoint', async () => { const mockUsername = 'testuser'; const mockGameMode = 'testMode'; const mockUserData = { username: mockUsername, gameMode: mockGameMode, questions: ['question1', 'question2'] }; @@ -23,7 +23,7 @@ describe('Routes Tests', () => { axios.get.mockResolvedValueOnce({ data: mockUserData }); const response = await request(app) - .get(`/user/questionsRecord/${mockUsername}/${mockGameMode}`); + .get(`/questionsRecord/${mockUsername}/${mockGameMode}`); expect(axios.get).toHaveBeenCalledWith( expect.stringContaining(`/user/questionsRecord/${mockUsername}/${mockGameMode}`), {} @@ -32,14 +32,14 @@ describe('Routes Tests', () => { expect(response.body).toEqual(mockUserData); }); - it('should respond with status 200 for /user/questionsRecord endpoint', async () => { + it('should respond with status 200 for /questionsRecord endpoint', async () => { const mockRequestData = { userId: 'testuser', questions: ['question1', 'question2'] }; const mockResponseData = { success: true }; axios.post.mockResolvedValueOnce({ data: mockResponseData }); const response = await request(app) - .post('/user/questionsRecord') + .put('/questionsRecord') .send(mockRequestData); expect(axios.post).toHaveBeenCalledWith( @@ -50,13 +50,13 @@ describe('Routes Tests', () => { expect(response.body).toEqual(mockResponseData); }); - it('should handle /user/questionsRecord errors and respond with appropriate status and message', async () => { + it('should handle /questionsRecord errors and respond with appropriate status and message', async () => { const mockRequestData = { userId: 'testuser', questions: ['question1', 'question2'] }; const errorMessage = 'Error updating questions record'; axios.post.mockRejectedValueOnce(new Error(errorMessage)); const response = await request(app) - .post('/user/questionsRecord') + .put('/questionsRecord') .send(mockRequestData); expect(axios.post).toHaveBeenCalledWith( @@ -117,22 +117,22 @@ describe('Routes Tests', () => { expect(response.body.error).toBe('Error al obtener la sesión del usuario'); }); - it('should respond with status 200 for /user/ranking endpoint', async () => { + it('should respond with status 200 for /ranking endpoint', async () => { const mockRankingData = [{ username: 'user1', score: 100 }, { username: 'user2', score: 90 }]; axios.get.mockResolvedValueOnce({ data: mockRankingData }); - const response = await request(app).get('/user/ranking'); + const response = await request(app).get('/ranking'); expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('/user/ranking')); expect(response.status).toBe(200); expect(response.body).toEqual(mockRankingData); }); - it('should handle /user/ranking errors and respond with appropriate status and message', async () => { + it('should handle /ranking errors and respond with appropriate status and message', async () => { const errorMessage = 'Error fetching ranking data'; axios.get.mockRejectedValueOnce(new Error(errorMessage)); - const response = await request(app).get('/user/ranking'); + const response = await request(app).get('/ranking'); expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('/user/ranking')); expect(response.status).toBe(500); @@ -146,7 +146,7 @@ describe('Routes Tests', () => { const response = await request(app).get(`/user/${username}`); - expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`/user/${username}`), {}); + expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`/user/${username}`)); expect(response.status).toBe(200); expect(response.body).toEqual(mockUserData); }); @@ -158,7 +158,7 @@ describe('Routes Tests', () => { const response = await request(app).get(`/user/${username}`); - expect(axios.get).toHaveBeenCalledWith(`http://localhost:8001/user/${username}`, {}); + expect(axios.get).toHaveBeenCalledWith(`http://localhost:8001/user/${username}`); expect(response.status).toBe(500); expect(response.body.error).toBe('Error fetching user data'); }); @@ -214,7 +214,7 @@ describe('Routes Tests', () => { axios.post.mockResolvedValueOnce({ data: mockUserData }); const requestBody = { username: 'testuser', statistics: { points: 100 } }; - const response = await request(app).post('/statistics').send(requestBody); + const response = await request(app).put('/statistics').send(requestBody); expect(axios.post).toHaveBeenCalledWith(expect.stringContaining('/statistics'), requestBody); expect(response.status).toBe(200); @@ -226,18 +226,18 @@ describe('Routes Tests', () => { axios.post.mockRejectedValueOnce(new Error(errorMessage)); const requestBody = { username: 'testuser', statistics: { points: 100 } }; - const response = await request(app).post('/statistics').send(requestBody); + const response = await request(app).put('/statistics').send(requestBody); expect(axios.post).toHaveBeenCalledWith(expect.stringContaining('/statistics'), requestBody); expect(response.status).toBe(500); expect(response.body.error).toBe(errorMessage); }); - it('should respond with status 200 for /user/statistics/:username endpoint', async () => { + it('should respond with status 200 for /statistics/:username endpoint', async () => { const mockUserData = { username: 'testuser', statistics: { points: 100 } }; axios.get.mockResolvedValueOnce({ data: mockUserData }); - const response = await request(app).get('/user/statistics/testuser').query({ loggedUser: 'testuser' }); + const response = await request(app).get('/statistics/testuser').query({ loggedUser: 'testuser' }); expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('/user/statistics/testuser'), { params: { loggedUser: 'testuser' } @@ -246,11 +246,11 @@ describe('Routes Tests', () => { expect(response.body).toEqual(mockUserData); }); - it('should handle errors for /user/statistics/:username endpoint and respond with appropriate status and message', async () => { + it('should handle errors for /statistics/:username endpoint and respond with appropriate status and message', async () => { const errorMessage = 'Error retrieving user statistics'; axios.get.mockRejectedValueOnce(new Error(errorMessage)); - const response = await request(app).get('/user/statistics/testuser').query({ loggedUser: 'testuser' }); + const response = await request(app).get('/statistics/testuser').query({ loggedUser: 'testuser' }); expect(axios.get).toHaveBeenCalledWith(expect.stringContaining('/user/statistics/testuser'), { params: { loggedUser: 'testuser' } @@ -307,12 +307,12 @@ describe('Routes Tests', () => { }); it('should respond with status 200 for /group/:name endpoint', async () => { - const groupName = 'Test Group'; + const groupName = 'TestGroup'; const mockUserData = { username: 'testuser', groupName: groupName }; axios.post.mockResolvedValueOnce({ data: mockUserData }); const requestBody = { username: 'testuser' }; - const response = await request(app).post(`/group/${groupName}`).send(requestBody); + const response = await request(app).put(`/group/${groupName}`).send(requestBody); expect(axios.post).toHaveBeenCalledWith(expect.stringContaining(`/group/${groupName}`), requestBody); expect(response.status).toBe(200); @@ -320,12 +320,12 @@ describe('Routes Tests', () => { }); it('should handle errors for /group/:name endpoint and respond with appropriate status and message', async () => { - const groupName = 'Test Group'; + const groupName = 'TestGroup'; const errorMessage = 'Error joining group'; axios.post.mockRejectedValueOnce({ response: { status: 400, data: { error: errorMessage } } }); const requestBody = { username: 'testuser' }; - const response = await request(app).post(`/group/${groupName}`).send(requestBody); + const response = await request(app).put(`/group/${groupName}`).send(requestBody); expect(axios.post).toHaveBeenCalledWith(expect.stringContaining(`/group/${groupName}`), requestBody); expect(response.status).toBe(400); @@ -333,7 +333,7 @@ describe('Routes Tests', () => { }); it('should respond with status 200 for /group/:name endpoint', async () => { - const groupName = 'Test Group'; + const groupName = 'TestGroup'; const username = 'user1'; const mockGroupData = { name: groupName, members: ['user1', 'user2'] }; axios.get.mockResolvedValueOnce({ data: mockGroupData }); @@ -346,7 +346,7 @@ describe('Routes Tests', () => { }); it('should handle errors for /group/:name endpoint and respond with appropriate status and message', async () => { - const groupName = 'Test Group'; + const groupName = 'TestGroup'; const username = 'user1'; const errorMessage = 'Error retrieving group data'; axios.get.mockRejectedValueOnce(new Error(errorMessage)); @@ -370,7 +370,7 @@ describe('Routes Tests', () => { axios.get.mockResolvedValue({ data: mockUserData }); - const response = await request(app).get('/user/profile').query({ username }); + const response = await request(app).get('/profile').query({ username }); expect(axios.get).toHaveBeenCalledWith( expect.stringContaining(`/user/profile`), @@ -399,7 +399,7 @@ describe('Routes Tests', () => { axios.post.mockResolvedValue({ data: mockResponseData }); - const response = await request(app).post(`/user/profile/${username}`).send(mockUpdateData); + const response = await request(app).put(`/profile/${username}`).send(mockUpdateData); expect(axios.post).toHaveBeenCalledWith( expect.stringContaining(`/user/profile/${username}`), @@ -420,9 +420,9 @@ describe('Routes Tests', () => { axios.get.mockResolvedValue({ data: mockRankingData }); - const response = await request(app).get('/user/group/ranking'); + const response = await request(app).get('/group/ranking'); - expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`/user/group/ranking`)); + expect(axios.get).toHaveBeenCalledWith(expect.stringContaining(`/group/ranking`)); expect(response.status).toBe(200); expect(response.body).toEqual(mockRankingData); @@ -435,7 +435,7 @@ describe('Routes Tests', () => { axios.post.mockResolvedValue({ data: mockResponseData }); - const response = await request(app).post(`/group/${groupName}/exit`).send(mockRequestBody); + const response = await request(app).put(`/group/${groupName}/exit`).send(mockRequestBody); expect(axios.post).toHaveBeenCalledWith( expect.stringContaining(`/user/group/${groupName}/exit`), diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index fdc17f78..60733906 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -7,15 +7,13 @@ const app = express(); const port = 8000; const userServiceUrl = process.env.USER_SERVICE_URL || 'http://localhost:8001'; - const questionGenerationServiceUrl = process.env.QUESTION_SERVICE_URL || 'http://localhost:8010'; app.use(cors()); app.use(express.json()); -//Prometheus configuration -//It uses prometheus middleware whenever a petition happens -const metricsMiddleware = promBundle({includeMethod: true}); +// Prometheus configuration +const metricsMiddleware = promBundle({ includeMethod: true }); app.use(metricsMiddleware); const handleErrors = (res, error) => { @@ -33,9 +31,10 @@ app.get('/health', (_req, res) => { res.json({ status: 'OK' }); }); -app.get('/user/ranking', async (req, res) => { +app.get('/ranking', async (req, res) => { try { - const response = await axios.get(`${userServiceUrl}/user/ranking`); + const rankingUrL = new URL(`/user/ranking`, userServiceUrl) + const response = await axios.get(rankingUrL.href); res.json(response.data); // Send just the response data } catch (error) { console.error("Error al obtener la sesión del usuario:", error); @@ -43,50 +42,56 @@ app.get('/user/ranking', async (req, res) => { } }); -app.get('/user/profile', async (req, res) => { +app.get('/profile', async (req, res) => { try { const username = req.query.username; - const response = await axios.get(`${userServiceUrl}/user/profile`, {params: {username: username }}); + const profileUrl = new URL(`/user/profile`, userServiceUrl); + const response = await axios.get(profileUrl.href, {params: {username: username }}); res.json(response.data.user); } catch (error) { handleErrors(res, error); -}}); + } +}); -app.post('/user/profile/:username', async (req, res) => { +app.put('/profile/:username', async (req, res) => { try { - const username = req.params.username; - const response = await axios.post(`${userServiceUrl}/user/profile/`+username, req.body); + const username = encodeURIComponent(req.params.username); + const profileUrl = new URL(`/user/profile/${username}`, userServiceUrl); + const response = await axios.post(profileUrl.href, req.body); res.json(response.data); } catch (error) { handleErrors(res, error); -}}); + } +}); app.post('/login', async (req, res) => { try { // Forward the login request to the authentication service - const authResponse = await axios.post(`${userServiceUrl}/login`, req.body); + const loginUrl = new URL(`/login`, userServiceUrl); + const authResponse = await axios.post(loginUrl.href, req.body); res.json(authResponse.data); } catch (error) { handleErrors(res, error); } }); -app.get('/user/questionsRecord/:username/:gameMode', async (req, res) => { +app.get('/questionsRecord/:username/:gameMode', async (req, res) => { try { - console.log(1) - const username = req.params.username; - const gameMode = req.params.gameMode; + const username = encodeURIComponent(req.params.username); + const gameMode = encodeURIComponent(req.params.gameMode); + const questionsRecordUrl = new URL(`/user/questionsRecord/${username}/${gameMode}`, userServiceUrl); // Forward the user statics edit request to the user service - const userResponse = await axios.get(`${userServiceUrl}/user/questionsRecord/${username}/${gameMode}`, req.body); + const userResponse = await axios.get(questionsRecordUrl.href, req.body); res.json(userResponse.data); } catch (error) { handleErrors(res, error); } }); -app.post('/user/questionsRecord', async (req, res) => { +app.put('/questionsRecord', async (req, res) => { try { - const response = await axios.post(userServiceUrl+`/user/questionsRecord`, req.body); + const questionsRecordUrl = new URL(`/user/questionsRecord`, userServiceUrl); + const response = await axios.post(questionsRecordUrl.href, req.body); res.json(response.data); } catch (error) { res.status(500).json({ error: "Error al actualizar el historial de preguntas" }); @@ -96,7 +101,8 @@ app.post('/user/questionsRecord', async (req, res) => { app.get('/user/group', async (req, res) => { try { const username = req.query.username; - const userResponse = await axios.get(userServiceUrl + '/user/group',{params: {username: username }}); + const userGroupUrl = new URL(`/user/group`, userServiceUrl); + const userResponse = await axios.get(userGroupUrl.href, { params: { username: username } }); res.json(userResponse.data); } catch (error) { handleErrors(res, error); @@ -105,9 +111,9 @@ app.get('/user/group', async (req, res) => { app.get('/user/:username', async (req, res) => { try { - const username = req.params.username; - // Forward the user statics edit request to the user service - const userResponse = await axios.get(userServiceUrl+'/user/'+username, req.body); + const username = encodeURIComponent(req.params.username); + const userUrl = new URL(`/user/${username}`, userServiceUrl); + const userResponse = await axios.get(userUrl.href); res.json(userResponse.data); } catch (error) { handleErrors(res, error); @@ -116,7 +122,8 @@ app.get('/user/:username', async (req, res) => { app.get('/user', async (req, res) => { try { - const response = await axios.get(`${userServiceUrl}/user`); + const userUrl = new URL(`/user/`, userServiceUrl); + const response = await axios.get(userUrl.href); res.json(response.data); // Send just the response data } catch (error) { res.status(500).json({ error: "Error al obtener la sesión del usuario" }); @@ -125,8 +132,8 @@ app.get('/user', async (req, res) => { app.post('/user', async (req, res) => { try { - // Forward the add user request to the user service - const userResponse = await axios.post(`${userServiceUrl}/user`, req.body); + const userUrl = new URL(`/user/`, userServiceUrl); + const userResponse = await axios.post(userUrl.href, req.body); res.json(userResponse.data); } catch (error) { handleErrors(res, error); @@ -136,7 +143,8 @@ app.post('/user', async (req, res) => { app.get('/questions/:lang', async (req, res) => { try { const language = encodeURIComponent(req.params.lang); - const questionsResponse = await axios.get(`${questionGenerationServiceUrl}/questions/${language}`); + const questionsUrl = new URL(`/questions/${language}`, questionGenerationServiceUrl); + const questionsResponse = await axios.get(questionsUrl.href); res.json(questionsResponse.data); } catch (error) { res.status(error.response).json({ error: error.response }); @@ -147,39 +155,40 @@ app.get('/questions/:lang/:category', async (req, res) => { try { const category = encodeURIComponent(req.params.category); const language = encodeURIComponent(req.params.lang); - const questionsResponse = await axios.get(`${questionGenerationServiceUrl}/questions/getQuestionsFromDb/1/${category}/${language}`); + const questionsUrl = new URL(`/questions/getQuestionsFromDb/1/${category}/${language}`, questionGenerationServiceUrl); + const questionsResponse = await axios.get(questionsUrl.href); res.json(questionsResponse.data); } catch (error) { res.status(error.response).json({ error: error.response }); } }); - -app.post('/statistics', async (req, res) => { +app.put('/statistics', async (req, res) => { try { - // Forward the user statics edit request to the user service - const userResponse = await axios.post(`${userServiceUrl}/user/statistics`, req.body); + const statisticsUrl = new URL(`/user/statistics`, userServiceUrl); + const userResponse = await axios.post(statisticsUrl.href, req.body); res.json(userResponse.data); } catch (error) { handleErrors(res, error); } }); -app.get('/user/statistics/:username', async (req, res) => { +app.get('/statistics/:username', async (req, res) => { try { - const username = req.params.username; - const loggedUser = req.query.loggedUser; - // Forward the user statics edit request to the user service - const userResponse = await axios.get(`${userServiceUrl}/user/statistics/${username}`,{params: {loggedUser: loggedUser }}); + const username = encodeURIComponent(req.params.username); + const loggedUser = encodeURIComponent(req.query.loggedUser); + const statisticsUrl = new URL(`/user/statistics/${username}`, userServiceUrl); + const userResponse = await axios.get(statisticsUrl.href, { params: { loggedUser: loggedUser } }); res.json(userResponse.data); } catch (error) { handleErrors(res, error); } }); -app.get('/user/group/ranking', async (req, res) => { +app.get('/group/ranking', async (req, res) => { try { - const groupResponse = await axios.get(`${userServiceUrl}/user/group/ranking`); + const groupRankingUrl = new URL(`/user/group/ranking`, userServiceUrl); + const groupResponse = await axios.get(groupRankingUrl.href); res.json(groupResponse.data); } catch (error) { handleErrors(res, error); @@ -188,12 +197,13 @@ app.get('/user/group/ranking', async (req, res) => { app.post('/group', async (req, res) => { try { - const userResponse = await axios.post(`${userServiceUrl}/user/group`, req.body); + const groupUrl = new URL(`/user/group`, userServiceUrl); + const userResponse = await axios.post(groupUrl.href, req.body); res.json(userResponse.data); } catch (error) { if (error.response && error.response.status === 400) { res.status(400).json({ error: error.response.data.error }); - }else{ + } else { handleErrors(res, error); } } @@ -201,38 +211,41 @@ app.post('/group', async (req, res) => { app.get('/group/:name', async (req, res) => { try { - const { name } = req.params; - const username = req.query.username; - const userResponse = await axios.get(`${userServiceUrl}/user/group/${name}`,{params: {username: username }}); + const name = encodeURIComponent(req.params.name); + const username = encodeURIComponent(req.query.username); + const groupUrl = new URL(`/user/group/${name}`, userServiceUrl); + const userResponse = await axios.get(groupUrl.href, { params: { username: username } }); res.json(userResponse.data); } catch (error) { handleErrors(res, error); } }); -app.post('/group/:name', async (req, res) => { +app.put('/group/:name', async (req, res) => { try { - const { name } = req.params; - const userResponse = await axios.post(`${userServiceUrl}/user/group/${name}`, req.body); + const name = encodeURIComponent(req.params.name); + const groupUrl = new URL(`/user/group/${name}`, userServiceUrl); + const userResponse = await axios.post(groupUrl.href, req.body); res.json(userResponse.data); } catch (error) { if (error.response && error.response.status === 400) { res.status(400).json({ error: error.response.data.error }); - }else{ + } else { handleErrors(res, error); } } }); -app.post('/group/:name/exit', async (req, res) => { +app.put('/group/:name/exit', async (req, res) => { try { - const { name } = req.params; - const userResponse = await axios.post(`${userServiceUrl}/user/group/${name}/exit`, req.body); + const name = encodeURIComponent(req.params.name); + const groupExitUrl = new URL(`/user/group/${name}/exit`, userServiceUrl); + const userResponse = await axios.post(groupExitUrl.href, req.body); res.json(userResponse.data); } catch (error) { if (error.response && error.response.status === 400) { res.status(400).json({ error: error.response.data.error }); - }else{ + } else { handleErrors(res, error); } } @@ -243,4 +256,4 @@ const server = app.listen(port, () => { console.log(`Gateway Service listening at http://localhost:${port}`); }); -module.exports = server +module.exports = server; \ No newline at end of file diff --git a/gatewayservice/monitoring/grafana/provisioning/dashboards/example-service-dashboard.json b/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json similarity index 97% rename from gatewayservice/monitoring/grafana/provisioning/dashboards/example-service-dashboard.json rename to gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json index 61da159b..28caba1c 100644 --- a/gatewayservice/monitoring/grafana/provisioning/dashboards/example-service-dashboard.json +++ b/gatewayservice/monitoring/grafana/provisioning/dashboards/wiq-es04-dashboard.json @@ -80,9 +80,9 @@ "steppedLine": false, "targets": [ { - "expr": "sum(increase(http_request_duration_seconds_count[1m]))", + "expr": "sum(increase(http_request_duration_seconds_count[1h]))", "interval": "", - "legendFormat": "Requests per minute", + "legendFormat": "Requests per hour", "refId": "A" } ], @@ -90,7 +90,7 @@ "timeFrom": null, "timeRegions": [], "timeShift": null, - "title": "Rate (R): Number of requests per minute", + "title": "Rate (R): Number of requests per hour", "tooltip": { "shared": true, "sort": 0, @@ -189,7 +189,7 @@ "steppedLine": false, "targets": [ { - "expr": "sum(increase(http_request_duration_seconds_count{code=~\"5.*\"}[1m]))", + "expr": "sum(increase(http_request_duration_seconds_count{status_code=~\"4.*\"}[1m]))", "interval": "", "legendFormat": "", "refId": "A" @@ -369,7 +369,7 @@ ] }, "timezone": "", - "title": "Example Service Dashboard", + "title": "Wiq_es04 Dashboard", "uid": "1DYaynomMk", "version": 2 } diff --git a/gatewayservice/monitoring/prometheus/prometheus.yml b/gatewayservice/monitoring/prometheus/prometheus.yml index 3093655b..10930610 100644 --- a/gatewayservice/monitoring/prometheus/prometheus.yml +++ b/gatewayservice/monitoring/prometheus/prometheus.yml @@ -1,6 +1,6 @@ global: scrape_interval: 5s scrape_configs: - - job_name: "example-nodejs-app" + - job_name: "wiq-es04-nodejs-app" static_configs: - targets: ["gatewayservice:8000"] \ No newline at end of file diff --git a/questions/__tests/routes/question-routes.test.js b/questions/__tests/routes/question-routes.test.js index 6baea678..73ed7dec 100644 --- a/questions/__tests/routes/question-routes.test.js +++ b/questions/__tests/routes/question-routes.test.js @@ -8,6 +8,7 @@ const bodyParser = require('body-parser'); let mongoServer; let questionFunctions; let questionRoutes; +let generateQuestionsService; //let mongoServer; let app = express(); @@ -21,11 +22,11 @@ const questionData1 = { }; const questionData2 = { - question: "Which is the capital of UK?", + question: "Which is the capital of Spain?", options: ["Madrid", "Barcelona", "Paris", "London"], correctAnswer: "London", categories: ["Geography"], - language: "es" + language: "en" }; async function addingQuestion(questionData) { @@ -40,6 +41,12 @@ async function addingQuestion(questionData) { await questionFunctions.addQuestion(newQuestion); } +async function addingQuestions() { + for(var i = 0; i < 24; i++) { + await addingQuestion(questionData1); + await addingQuestion(questionData2); + } +} beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); @@ -48,19 +55,17 @@ beforeAll(async () => { await mongoose.connect(mongoURI); questionFunctions = require('../../services/question-data-service'); questionRoutes = require('../../routes/question-routes.js'); + generateQuestionsService = require('../../services/generate-questions-service'); + jest.mock('../../services/generate-questions-service'); app.use(bodyParser.json()); app.use('/questions', questionRoutes); - }); beforeEach(async () => { //Load database with initial conditions //await mongoose.connection.dropDatabase(); await Question.deleteMany({}); - for(var i = 0; i < 100; i++) { - await addingQuestion(questionData1); - await addingQuestion(questionData2); - } + await addingQuestions(); }); afterAll(async () => { @@ -71,25 +76,92 @@ beforeEach(async () => { describe('Question routes', function() { - it('It should get a question from the database', async function() { + + describe('Get a question from the database', function() { + it('get question when 0 questions', async function() { + await Question.deleteMany({}); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/es'); + await expect(response.status).toBe(200); + await expect(response.body.question).toBe(undefined); + }); + + it('get question when less than 50 questions', async function() { + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); const response = await request(app).get('/questions/en'); await expect(response.status).toBe(200); await expect(response.body.question).toBe('Which is the capital of Spain?'); }); - - it('It should get n questions from the database', async function() { - const response = await request(app).get('/questions/getQuestionsFromDb/3/en'); + it('get question when less than 100 questions', async function() { + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/en'); await expect(response.status).toBe(200); - await expect(response.body.length).toBe(3); - }); + await expect(response.body.question).toBe('Which is the capital of Spain?'); + }); + + it('get question when more than 100 questions', async function() { + await addingQuestions(); + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/en'); + await expect(response.status).toBe(200); + await expect(response.body.question).toBe('Which is the capital of Spain?'); + }); + }); + describe('Get n questions from the database', function() { + it('get question when less than 50 questions', async function() { + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/getQuestionsFromDb/3/en'); + await expect(response.status).toBe(200); + await expect(response.body.length).toBe(3); + }); + + it('get question when less than 100 questions', async function() { + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/getQuestionsFromDb/3/en'); + await expect(response.status).toBe(200); + await expect(response.body.length).toBe(3); + }); + + it('get question when more than 100 questions', async function() { + await addingQuestions(); + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/getQuestionsFromDb/3/en'); + await expect(response.status).toBe(200); + await expect(response.body.length).toBe(3); + }); it('It should not get n questions from the database', async function() { const response = await request(app).get('/questions/getQuestionsFromDb/-1/en'); await expect(response.status).toBe(400); }); - - it('It should get n questions of certain category from the database', async function() { + }); + describe('Get n questions from the database filtered by category', function() { + + it('get question when less than 50 questions', async function() { + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/getQuestionsFromDb/2/Geography/en'); + await expect(response.status).toBe(200); + await expect(response.body.length).toBe(2); + await expect(response.body[0].categories[0]).toBe("Geography"); + }); + it('get question when less than 100 questions', async function() { + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); + const response = await request(app).get('/questions/getQuestionsFromDb/2/Geography/en'); + await expect(response.status).toBe(200); + await expect(response.body.length).toBe(2); + await expect(response.body[0].categories[0]).toBe("Geography"); + }); + + it('get question when more than 100 questions', async function() { + await addingQuestions(); + await addingQuestions(); + await generateQuestionsService.generateQuestions.mockResolvedValue({"response":{"status":"200"}}); const response = await request(app).get('/questions/getQuestionsFromDb/2/Geography/en'); await expect(response.status).toBe(200); await expect(response.body.length).toBe(2); @@ -106,4 +178,5 @@ describe('Question routes', function() { it('should connect to the MongoDB server in memory', async () => { expect(mongoose.connection.readyState).toBe(1); // 1 means connected }); - }); \ No newline at end of file + }); + }); \ No newline at end of file diff --git a/questions/__tests/services/question-generation-service.test.js b/questions/__tests/services/question-generation-service.test.js index 2024e0dd..a999966c 100644 --- a/questions/__tests/services/question-generation-service.test.js +++ b/questions/__tests/services/question-generation-service.test.js @@ -52,7 +52,7 @@ describe('Question generation', function() { dbService.addQuestion.mockResolvedValue(); // Llama a la función que deseas probar - await generator.generateQuestions(1); + await generator.generateQuestions(1,"en","Geography"); // Verifica que la función haya realizado las operaciones esperadas expect(dbService.addQuestion).toHaveBeenCalledTimes(1); @@ -76,7 +76,7 @@ describe('Question generation', function() { console.error = jest.fn(); // Llama a la función que deseas probar - await generator.generateQuestions(1); + await generator.generateQuestions(1,"en"); // Verifica que la función haya realizado las operaciones esperadas expect(dbService.addQuestion).toHaveBeenCalledTimes(0); diff --git a/questions/routes/question-routes.js b/questions/routes/question-routes.js index 28b36f58..bcb15c5a 100644 --- a/questions/routes/question-routes.js +++ b/questions/routes/question-routes.js @@ -42,25 +42,48 @@ router.get('/:lang', async (req, res) => { router.get('/getQuestionsFromDb/:n/:lang', async(_req, res) => { const n = parseInt(_req.params.n, 10); const language = _req.params.lang; + const questionCount = await dbService.getQuestionCount(language); //Verify is n is a correct number if (isNaN(n) || n <= 0) { return res.status(400).json({ error: 'Parameter "n" must be > 0.' }); } - if (await dbService.getQuestionCount(language) < n) { - //Must generate n questions - await generateQuestionsService.generateQuestions(n + 1, language); + // 0: Await till it creates 2, creates 50 async and do not delete + if (questionCount == 0) { + await generateQuestionsService.generateQuestions(2, language); + generateQuestionsService.generateQuestions(50, language); + const questions = await dbService.getRandomQuestions(n, language); + res.json(questions); + + // < 50: async creates 10 and do not delete + } else if (questionCount < 50) { //Do not wait to generate the others - generateQuestionsService.generateQuestions(n * 5, language); + generateQuestionsService.generateQuestions(10, language); + const questions = await dbService.getRandomQuestions(n, language); + res.json(questions); + + // < 100: async creates 5 and delete + } else if (questionCount < 100) { + generateQuestionsService.generateQuestions(10, language); + const questions = await dbService.getRandomQuestions(n, language); + res.json(questions); + if(questions != null) { + for(var i = 0; i < questions.length; i++) { + dbService.deleteQuestionById(questions[i]._id); + } + } + // >= 100: do not create and delete + } else { + const questions = await dbService.getRandomQuestions(n, language); + res.json(questions); + if(questions != null) { + for(var i = 0; i < questions.length; i++) { + dbService.deleteQuestionById(questions[i]._id); + } + } } - questions = await dbService.getRandomQuestions(n, language); - if(questions != null) - questions.map(question => { - dbService.deleteQuestionById(question._id); - }) - res.json(questions); }); //Get random questions from db with category filter: http://localhost:8010/questions/getQuestionsFromDb/2/Geografía @@ -68,26 +91,48 @@ router.get('/getQuestionsFromDb/:n/:category/:lang', async(_req, res) => { const n = parseInt(_req.params.n, 10); const category = _req.params.category; const language = _req.params.lang; + const questionCount = await dbService.getQuestionCountByCategory(category, language); //Verify is n is a correct number if (isNaN(n) || n <= 0) { return res.status(400).json({ error: 'Parameter "n" must be > 0.' }); } - if (await dbService.getQuestionCountByCategory(category, language) < n) { - //Must generate n questions (n because some can fail at generation at the moment 18/04) - await generateQuestionsService.generateQuestions(n + 2, language, category); + // 0: Await till it creates 2, creates 50 async and do not delete + if (questionCount == 0) { + await generateQuestionsService.generateQuestions(2, language, category); + generateQuestionsService.generateQuestions(50, language, category); + questions = await dbService.getRandomQuestionsByCategory(n, category, language); + res.json(questions); + + // < 50: async creates 10 and do not delete + } else if (questionCount < 50) { //Do not wait to generate the others - generateQuestionsService.generateQuestions(n, language, category); - } - questions = await dbService.getRandomQuestionsByCategory(n, category, language); - if(questions != null) { - for(var i = 0; i < questions.length; i++) { - dbService.deleteQuestionById(questions[i]._id); + generateQuestionsService.generateQuestions(10, language, category); + questions = await dbService.getRandomQuestionsByCategory(n, category, language); + res.json(questions); + + // < 100: async creates 5 and delete + } else if (questionCount < 100) { + generateQuestionsService.generateQuestions(10, language, category); + questions = await dbService.getRandomQuestionsByCategory(n, category, language); + res.json(questions); + if(questions != null) { + for(var i = 0; i < questions.length; i++) { + dbService.deleteQuestionById(questions[i]._id); + } + } + + // >= 100: do not create and delete + } else { + questions = await dbService.getRandomQuestionsByCategory(n, category, language); + res.json(questions); + if(questions != null) { + for(var i = 0; i < questions.length; i++) { + dbService.deleteQuestionById(questions[i]._id); + } } } - res.json(questions); - }); module.exports = router; \ No newline at end of file diff --git a/questions/services/question-data-service.js b/questions/services/question-data-service.js index 9eb956d5..ef6a054e 100644 --- a/questions/services/question-data-service.js +++ b/questions/services/question-data-service.js @@ -103,7 +103,7 @@ module.exports = { }, - // Get random questions TODO: refactor to use common code with get questions by category + // Get random questions getRandomQuestions : async function(n, wantedLanguage) { try { // Obtain total number of questions in database diff --git a/users/data/icons.js b/users/data/icons.js new file mode 100644 index 00000000..b7af50e6 --- /dev/null +++ b/users/data/icons.js @@ -0,0 +1,10 @@ +const getRandomPic = () => { + + const pics = ["teresaIcon.jpg","barreroIcon.jpg","samuIcon.jpg","and1naIcon.jpg", + "wiffoIcon.jpg","bertinIcon.jpg","hugoIcon.jpg"] + + + return pics[Math.floor(Math.random() * pics.length)]; //NOSONAR +}; + +module.exports = { getRandomPic }; \ No newline at end of file diff --git a/users/routes/auth-routes.js b/users/routes/auth-routes.js index 52fa874a..c581a95c 100644 --- a/users/routes/auth-routes.js +++ b/users/routes/auth-routes.js @@ -27,11 +27,8 @@ router.post('/', async (req, res) => { // Check if the user exists and verify the password if (user && user.username === username && await bcrypt.compare(password, user.password)) { - // TODO: check why this makes the test fail - // req.session.username = user.username; - // Respond with the user information - return res.status(200).json({ username, createdAt: user.createdAt }); + return res.status(200).json({ username, createdAt: user.createdAt, avatar: user.imageUrl }); } else { return res.status(401).json({ error: 'Invalid credentials' }); diff --git a/users/routes/user-routes.js b/users/routes/user-routes.js index 1534ff7a..663759c5 100644 --- a/users/routes/user-routes.js +++ b/users/routes/user-routes.js @@ -2,6 +2,7 @@ const express = require('express'); const router = express.Router(); const bcrypt = require('bcrypt'); const { User, Statistics, Group, UserGroup, QuestionsRecord, sequelize } = require('../services/user-model'); +const { getRandomPic } = require("../data/icons"); // Getting the list of groups in the database router.get('/group', async (req, res) => { @@ -205,7 +206,6 @@ router.post('/questionsRecord', async (req, res) => { }); // Getting a questions record by username router.get('/questionsRecord/:username/:gameMode', async (req, res) => { - console.log(2) try { const username = req.params.username; const gameMode = req.params.gameMode; @@ -227,7 +227,7 @@ router.get('/questionsRecord/:username/:gameMode', async (req, res) => { return res.status(400).json({ error: error.message }); } }); -// Route for add a user +// Route to add a user router.post('/', async (req, res) => { try { const { username, password, name, surname } = req.body; @@ -267,17 +267,20 @@ router.post('/', async (req, res) => { // Hash the password const hashedPassword = await bcrypt.hash(password, 10); + const imageUrl = getRandomPic(); + // Create the user in the database using Sequelize const newUser = await User.create({ username, password: hashedPassword, name, - surname + surname, + imageUrl }); - // Create the user statics + // Create the user statistics await Statistics.create({ - username, + username }) res.json(newUser); diff --git a/webapp/e2e/features/theChallangeGame.feauture b/webapp/e2e/features/theChallangeGame.feauture new file mode 100644 index 00000000..6a63bd96 --- /dev/null +++ b/webapp/e2e/features/theChallangeGame.feauture @@ -0,0 +1,11 @@ +Feature: Answer a question + +Scenario: Answering a question correctly + Given A question + When I click on the correct answer button + Then The button turns green + +Scenario: Answering a question incorrectly + Given A question + When I click on an incorrect answer button + Then The button turns red \ No newline at end of file diff --git a/webapp/e2e/steps/discoveringCitiesGame.steps.js b/webapp/e2e/steps/discoveringCitiesGame.steps.js index 379bd386..5c1103b1 100644 --- a/webapp/e2e/steps/discoveringCitiesGame.steps.js +++ b/webapp/e2e/steps/discoveringCitiesGame.steps.js @@ -5,7 +5,7 @@ const feature = loadFeature('./features/discoveringCitiesGame.feature'); let page; let browser; - + defineFeature(feature, test => { beforeAll(async () => { @@ -29,7 +29,7 @@ defineFeature(feature, test => { question: 'Which is the capital of Spain?', options: ['Madrid', 'Barcelona', 'Paris', 'London'], correctAnswer: 'Madrid', - categories: ['Geography'], + categories: ['Cities'], language: 'en' }]) }); @@ -39,8 +39,6 @@ defineFeature(feature, test => { }); //Way of setting up the timeout setDefaultOptions({ timeout: 10000 }) - - }); beforeEach(async () => { diff --git a/webapp/e2e/steps/game.steps.js b/webapp/e2e/steps/game.steps.js index f15c626b..7886f189 100644 --- a/webapp/e2e/steps/game.steps.js +++ b/webapp/e2e/steps/game.steps.js @@ -1,11 +1,12 @@ const puppeteer = require('puppeteer'); const { defineFeature, loadFeature }=require('jest-cucumber'); +const { expect } = require('expect-puppeteer'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions const feature = loadFeature('./features/game.feature'); let page; let browser; - + defineFeature(feature, test => { beforeAll(async () => { @@ -45,11 +46,12 @@ defineFeature(feature, test => { beforeEach(async () => { await page - .goto("http://localhost:3000/game", { + .goto("http://localhost:3000/discoveringCitiesGame", { waitUntil: "networkidle0", }) .catch(() => {}); + //"mock" login await page.evaluate(() => { localStorage.clear(); diff --git a/webapp/e2e/steps/theCallange.steps.js b/webapp/e2e/steps/theCallange.steps.js new file mode 100644 index 00000000..8b3d7d59 --- /dev/null +++ b/webapp/e2e/steps/theCallange.steps.js @@ -0,0 +1,139 @@ +const puppeteer = require('puppeteer'); +const { defineFeature, loadFeature }=require('jest-cucumber'); +const setDefaultOptions = require('expect-puppeteer').setDefaultOptions +const feature = loadFeature('./features/theChallangeGame.feauture'); + +let page; +let browser; + +defineFeature(feature, test => { + + beforeAll(async () => { + + browser = process.env.GITHUB_ACTIONS + ? await puppeteer.launch() + : await puppeteer.launch({ headless: false, slowMo: 40 }); + page = await browser.newPage(); + + await page.setRequestInterception(true); + + page.on('request', (req) => { + if(req.url().endsWith('/Geography')) { + req.respond({ + status: 200, + headers: { + 'Access-Control-Allow-Origin': '*' + }, + contentType: 'application/json', + body: JSON.stringify([{ + question: 'Which is the capital of Spain?', + options: ['Madrid', 'Barcelona', 'Paris', 'London'], + correctAnswer: 'Madrid', + categories: ['Geography'], + language: 'en' + }]) + }); + } else { + req.continue(); + } + }); + //Way of setting up the timeout + setDefaultOptions({ timeout: 10000 }) + + + }); + + beforeEach(async () => { + await page + .goto("http://localhost:3000/theChallengeGame", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + + //"mock" login + await page.evaluate(() => { + localStorage.clear(); + localStorage.setItem('sessionId', 'fictitiousSessionId12345'); + }); + + await page + .goto("http://localhost:3000/theChallengeGame", { + waitUntil: "networkidle0", + }) + .catch(() => {}); + }); + + + test('Answering a question correctly', ({given,when,then}) => { + + given('A question', async () => { + const button = await page.$('[data-testid="start-button"]'); + await button.click(); + + //await expect(page.findByText('Which is the capital of Spain?')); + const question = await page.$['data-testid="question"']; + await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'.toUpperCase()}); + expect(question).not.toBeNull(); + + const answers = await page.$x('//*[contains(@data-testid, "success") or contains(@data-testid, "failure") or contains(@data-testid, "answer")]'); + expect(answers.length).toBe(4); + }); + + when('I click on the correct answer button', async () => { + const answers = await page.$x('//*[contains(@data-testid, "success") or contains(@data-testid, "failure") or contains(@data-testid, "answer")]'); + await answers[0].click(); + + }); + + then('The button turns green', async () => { + /*const answerButton = await page.$x('(//*[@data-testid="answer"])[1]'); + const textoBoton1 = await page.evaluate(button => button.innerText, answerButton[0]); + const textoBoton2 = await page.evaluate(button => button.innerText, answerButton[1]); + if(textoBoton1 === "Madrid") { + await expect(textoBoton1).toMatch(/Madrid/i); + } else { + await expect(textoBoton2).toMatch(/Madrid/i); + }*/ + await expect(page).toMatchElement("button", { style: { color: 'green' } }); + }); + }) + + test('Answering a question incorrectly', ({given,when,then}) => { + + given('A question', async () => { + const button = await page.$('[data-testid="start-button"]'); + await button.click(); + + //await expect(page.findByText('Which is the capital of Spain?')); + const question = await page.$['data-testid="question"']; + await expect(page).toMatchElement("div", { text: 'Which is the capital of Spain?'.toUpperCase()}); + expect(question).not.toBeNull(); + + const answers = await page.$x('//*[contains(@data-testid, "success") or contains(@data-testid, "failure") or contains(@data-testid, "answer")]'); + expect(answers.length).toBe(4); + }); + + when('I click on an incorrect answer button', async () => { + const answers = await page.$x('//*[contains(@data-testid, "success") or contains(@data-testid, "failure") or contains(@data-testid, "answer")]'); + await answers[1].click(); + }); + + then('The button turns red', async () => { + /*const answerButton = await page.$x('(//*[@data-testid="answer"])[2]'); + const textoBoton1 = await page.evaluate(button => button.innerText, answerButton[0]); + const textoBoton2 = await page.evaluate(button => button.innerText, answerButton[1]); + if(textoBoton1 !== "Madrid") { + await expect(textoBoton1).not.toMatch(/Madrid/i); + } else { + await expect(textoBoton2).toMatch(/Madrid/i); + }*/ + await expect(page).toMatchElement("button", { style: { color: 'red' } }); + await expect(page).toMatchElement("button", { style: { color: 'green' } }); + }); + }) + + afterAll(async ()=>{ + browser.close() + }) + +}); \ No newline at end of file diff --git a/webapp/e2e/steps/wiseMenStackGame.steps.js b/webapp/e2e/steps/wiseMenStackGame.steps.js index 22f11b35..2c59525a 100644 --- a/webapp/e2e/steps/wiseMenStackGame.steps.js +++ b/webapp/e2e/steps/wiseMenStackGame.steps.js @@ -39,8 +39,6 @@ defineFeature(feature, test => { }); //Way of setting up the timeout setDefaultOptions({ timeout: 10000 }) - - }); beforeEach(async () => { @@ -49,7 +47,7 @@ defineFeature(feature, test => { waitUntil: "networkidle0", }) .catch(() => {}); - + //"mock" login await page.evaluate(() => { localStorage.clear(); diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 8e61c028..b85d1ed4 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -21,6 +21,7 @@ "babel": "^6.23.0", "cross-env": "^7.0.3", "express": "^4.19.2", + "get-uri": "^6.0.3", "i18next": "^23.11.0", "pupeteer": "^0.0.1", "react": "^18.2.0", @@ -30,7 +31,7 @@ "react-i18next": "^14.1.0", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", - "sequelize": "^6.37.2", + "sequelize": "^6.37.3", "socket.io-client": "^4.7.5", "sqlite3": "^5.1.7", "web-vitals": "^3.5.1" @@ -7589,7 +7590,6 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, "engines": { "node": ">=10.0.0" } @@ -9438,7 +9438,6 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, "engines": { "node": ">= 14" } @@ -12214,7 +12213,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", - "dev": true, "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -12229,7 +12227,6 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -25548,9 +25545,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/sequelize": { - "version": "6.37.2", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.2.tgz", - "integrity": "sha512-bnb7swGANONXCTrVyebpOOZssLwQrVkYX2tcC6qOIvH+P+OhsoMBi7c3GXI5bC+Z4b4tOl+kQy6yeqLCZ1YQAQ==", + "version": "6.37.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.3.tgz", + "integrity": "sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==", "funding": [ { "type": "opencollective", @@ -34722,8 +34719,7 @@ "basic-ftp": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==" }, "batch": { "version": "0.6.1", @@ -36042,8 +36038,7 @@ "data-uri-to-buffer": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==" }, "data-urls": { "version": "2.0.0", @@ -38100,7 +38095,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", - "dev": true, "requires": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", @@ -38112,7 +38106,6 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", - "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -47937,9 +47930,9 @@ } }, "sequelize": { - "version": "6.37.2", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.2.tgz", - "integrity": "sha512-bnb7swGANONXCTrVyebpOOZssLwQrVkYX2tcC6qOIvH+P+OhsoMBi7c3GXI5bC+Z4b4tOl+kQy6yeqLCZ1YQAQ==", + "version": "6.37.3", + "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.37.3.tgz", + "integrity": "sha512-V2FTqYpdZjPy3VQrZvjTPnOoLm0KudCRXfGWp48QwhyPPp2yW8z0p0sCYZd/em847Tl2dVxJJ1DR+hF+O77T7A==", "requires": { "@types/debug": "^4.1.8", "@types/validator": "^13.7.17", diff --git a/webapp/package.json b/webapp/package.json index 442dc637..3ccc8398 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -16,6 +16,7 @@ "babel": "^6.23.0", "cross-env": "^7.0.3", "express": "^4.19.2", + "get-uri": "^6.0.3", "i18next": "^23.11.0", "pupeteer": "^0.0.1", "react": "^18.2.0", @@ -25,7 +26,7 @@ "react-i18next": "^14.1.0", "react-router-dom": "^6.22.1", "react-scripts": "5.0.1", - "sequelize": "^6.37.2", + "sequelize": "^6.37.3", "socket.io-client": "^4.7.5", "sqlite3": "^5.1.7", "web-vitals": "^3.5.1" diff --git a/webapp/src/SessionContext.js b/webapp/src/SessionContext.js index 041b4779..5f9391b0 100644 --- a/webapp/src/SessionContext.js +++ b/webapp/src/SessionContext.js @@ -8,6 +8,7 @@ const SessionProvider = ({ children }) => { const [sessionId, setSessionId] = useState(''); const [username, setUsername] = useState(''); const [isLoggedIn, setIsLoggedIn] = useState(false); + const [avatar, setAvatar] = useState('/default_user.jpg'); //This hook recovers user data if available in localstorage when the sessprovider is created useEffect(() => { @@ -21,6 +22,11 @@ const SessionProvider = ({ children }) => { if (storedUsername) { setUsername(storedUsername); } + + const storedAvatar = localStorage.getItem('avatar'); + if (storedAvatar) { + setAvatar(storedAvatar); + } } }, []); @@ -31,19 +37,26 @@ const SessionProvider = ({ children }) => { setIsLoggedIn(true); localStorage.setItem('sessionId', newSessionId); localStorage.setItem('username', username); + localStorage.setItem('avatar', '/default_user.jpg'); }; const destroySession = () => { localStorage.removeItem('sessionId'); localStorage.removeItem('username'); setSessionId(''); - setIsLoggedIn(false); setUsername(''); + setIsLoggedIn(false); + setAvatar('/default_user.jpg'); + }; + + const updateAvatar = (newAvatar) => { + setAvatar(newAvatar); + localStorage.setItem('avatar', newAvatar); }; return ( // This values are the props we can access from the child objects - + {children} ); diff --git a/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js b/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js index 3c21224d..ff54afdc 100644 --- a/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js +++ b/webapp/src/__tests__/pages/DiscoveringCitiesGame.test.js @@ -23,8 +23,8 @@ describe('Game component', () => { }] ); - mockAxios.onPost('http://localhost:8000/statistics').reply(200, { success: true }); - mockAxios.onPost('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/questionsRecord').reply(200, { success: true }); }); diff --git a/webapp/src/__tests__/pages/Game.test.js b/webapp/src/__tests__/pages/Game.test.js index 183e4f85..c339aa55 100644 --- a/webapp/src/__tests__/pages/Game.test.js +++ b/webapp/src/__tests__/pages/Game.test.js @@ -23,8 +23,8 @@ describe('Game component', () => { } ); - mockAxios.onPost('http://localhost:8000/statistics').reply(200, { success: true }); - mockAxios.onPost('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/questionsRecord').reply(200, { success: true }); render( diff --git a/webapp/src/__tests__/pages/GroupDetails.test.js b/webapp/src/__tests__/pages/GroupDetails.test.js index 48d01a99..8add14e0 100644 --- a/webapp/src/__tests__/pages/GroupDetails.test.js +++ b/webapp/src/__tests__/pages/GroupDetails.test.js @@ -42,9 +42,9 @@ describe('GroupDetails component', () => { await waitFor(() => { expect(getByText(groupInfo.name)).toBeInTheDocument(); - expect(getByText(`Creator: ${groupInfo.creator}`)).toBeInTheDocument(); - expect(getByText(`Created in: ${new Date(groupInfo.createdAt).toLocaleDateString()}`)).toBeInTheDocument(); - expect(getByText(`Members (${groupInfo.users.length}/20):`)).toBeInTheDocument(); + expect(getByText(`${groupInfo.creator}`)).toBeInTheDocument(); + expect(getByText(`${new Date(groupInfo.createdAt).toLocaleDateString()}`)).toBeInTheDocument(); + expect(getByText(`${groupInfo.users.length}/20`)).toBeInTheDocument(); groupInfo.users.forEach(user => { expect(getByText(user)).toBeInTheDocument(); }); diff --git a/webapp/src/__tests__/pages/Groups.test.js b/webapp/src/__tests__/pages/Groups.test.js index d7335fb7..1b131899 100644 --- a/webapp/src/__tests__/pages/Groups.test.js +++ b/webapp/src/__tests__/pages/Groups.test.js @@ -33,6 +33,7 @@ describe('Groups component', () => { it('should render groups list and creation elements', async () => { // It mocks a succesful request getting two groups from the database. + mockAxios.onGet('http://localhost:8000/group').reply(200, { groups: [{ name: 'Group 1' }, { name: 'Group 2' }] }); mockAxios.onGet('http://localhost:8000/user/group').reply(200, { groups: [{ name: 'Group 1' }, { name: 'Group 2' }] }); renderGroupsComponent(); @@ -132,7 +133,7 @@ describe('Groups component', () => { it('should successfully add user to a group', async () => { // Simulates a request response including the unjoined group data mockAxios.onGet('http://localhost:8000/user/group').reply(200, { groups: [{ name: 'Group1', isMember: false, isFull: false }] }); - mockAxios.onPost('http://localhost:8000/group/Group1').reply(200); + mockAxios.onPut('http://localhost:8000/group/Group1').reply(200); renderGroupsComponent(); @@ -156,11 +157,11 @@ describe('Groups component', () => { // FROM HERE, LOW TIMEOUTS NEED TO BE USED INSTEAD OF WAITFORS - + it('should display error message when group is already full', async () => { // Simulates a request response including the unjoined group data mockAxios.onGet('http://localhost:8000/user/group').reply(200, { groups: [{ name: 'Group1', isMember: false, isCreator: false , isFull: false }] }); - mockAxios.onPost('http://localhost:8000/group/Group1').reply(400, { error: 'Group is already full' }); + mockAxios.onPut('http://localhost:8000/group/Group1').reply(400, { error: 'Group is already full' }); renderGroupsComponent(); diff --git a/webapp/src/__tests__/pages/Home.test.js b/webapp/src/__tests__/pages/Home.test.js index 21fe3cfc..2e6dd511 100644 --- a/webapp/src/__tests__/pages/Home.test.js +++ b/webapp/src/__tests__/pages/Home.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import Home from '../../pages/Home'; +import '../../localize/i18n'; // Hacemos un mock del módulo '@mui/material' y su hook useMediaQuery jest.mock('@mui/material/useMediaQuery', () => ({ diff --git a/webapp/src/__tests__/pages/Homepage.test.js b/webapp/src/__tests__/pages/Homepage.test.js index c9f4992b..b607699c 100644 --- a/webapp/src/__tests__/pages/Homepage.test.js +++ b/webapp/src/__tests__/pages/Homepage.test.js @@ -1,6 +1,7 @@ import React from 'react'; import { render, fireEvent, screen } from '@testing-library/react'; import Homepage from '../../pages/Homepage'; +import '../../localize/i18n'; // Hacemos un mock del módulo '@mui/material' y su hook useMediaQuery jest.mock('@mui/material/useMediaQuery', () => ({ @@ -16,7 +17,7 @@ describe('Homepage', () => { it('renders video and play button', () => { render(); expect(screen.getByTestId('video')).toBeInTheDocument(); - expect(screen.getByText('PLAY')).toBeInTheDocument(); + expect(screen.getByText("PLAY")).toBeInTheDocument(); }); it('loads game buttons dynamically based on data', async () => { diff --git a/webapp/src/__tests__/pages/Instructions.test.js b/webapp/src/__tests__/pages/Instructions.test.js index e217269f..c5d779c5 100644 --- a/webapp/src/__tests__/pages/Instructions.test.js +++ b/webapp/src/__tests__/pages/Instructions.test.js @@ -2,32 +2,33 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import Instructions from '../../pages/Instructions'; import { BrowserRouter as Router } from 'react-router-dom'; +import '../../localize/i18n'; jest.mock('../../data/gameInfo.json', () => ({ __esModule: true, default: [ { - nombre: "WISE MEN STACK", + nombre: "Wise Men Stack", descripcion: "The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they win €20; otherwise, they move on to the next question (as the correct answer would be the other option). If the time runs out before the question is fully asked and both possible answers are provided, the contestant may still answer it; however, if the statement hasn't been completed (or the options weren't provided), they cannot answer.", foto: "../gameImg/foto0.png" }, { - nombre: "WARM QUESTION", + nombre: "Warm Question", descripcion: "It consists of ten topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly.", foto: "../gameImg/foto1.jpg" }, { - nombre: "DISCOVERING CITIES", + nombre: "Discovering Cities", descripcion: "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test.", foto: "../gameImg/foto2.png" }, { - nombre: "THE CHALLENGE", + nombre: "Challenge", descripcion: "The 'Challenge' game mode is the quintessential game mode, as it allows you to customize the match to your liking. This game mode is tailored for those who wish to practice certain game formats before engaging in our various other game modes.", foto: "../gameImg/foto3.jpg" }, { - nombre: "ONLINE MODE", + nombre: "Multiplayer", descripcion: "Create a room for other player to join and play 1vs1", foto: "../gameImg/foto3.jpg" } @@ -55,10 +56,10 @@ describe('Instructions component', () => { fireEvent.click(screen.getAllByRole('button')[0]); // Click the first game button - const gameNames = await screen.findAllByText("WISE MEN STACK"); //Look for the text "WISE MEN STACK" + const gameNames = await screen.findAllByText("Wise Men Stack"); //Look for the text "WISE MEN STACK" expect(gameNames).toHaveLength(2); // Check the expected number of matches - expect(gameNames[0]).toHaveTextContent("WISE MEN STACK"); + expect(gameNames[0]).toHaveTextContent("Wise Men Stack"); }); it('Hides game information when the same game button is clicked again', async () => { @@ -71,12 +72,12 @@ describe('Instructions component', () => { const gameButton = screen.getAllByRole('button')[0]; //Selecciona el primer boton fireEvent.click(gameButton); // Hace click en el boton indicado - const gameNames = await screen.findAllByText("WISE MEN STACK"); //Finds all components that have the indicated text + const gameNames = await screen.findAllByText("Wise Men Stack"); //Finds all components that have the indicated text expect(gameNames).toHaveLength(2); // Check the expected number of matches fireEvent.click(gameButton); // Hide info - const gameNames2 = await screen.findAllByText("WISE MEN STACK"); //Finds all components that have the indicated text + const gameNames2 = await screen.findAllByText("Wise Men Stack"); //Finds all components that have the indicated text expect(gameNames2).toHaveLength(1); // Check the expected number of matches await waitFor(() => { @@ -93,12 +94,12 @@ describe('Instructions component', () => { fireEvent.click(screen.getAllByRole('button')[0]); // Display first game info - const gameNames = await screen.findAllByText("WISE MEN STACK"); //Finds all components that have the indicated text + const gameNames = await screen.findAllByText("Wise Men Stack"); //Finds all components that have the indicated text expect(gameNames).toHaveLength(2); // Check the expected number of matches fireEvent.click(screen.getAllByRole('button')[1]); // Switch to second game info - const gameName = await screen.findAllByText("WARM QUESTION"); //Finds all components that have the indicated text + const gameName = await screen.findAllByText("Warm Question"); //Finds all components that have the indicated text expect(gameName).toHaveLength(2); // Check the expected number of matches await waitFor(() => { @@ -109,27 +110,27 @@ describe('Instructions component', () => { const mockGameData = [ { - nombre: "WISE MEN STACK", + nombre: "Wise Men Stack", descripcion: "The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they win €20; otherwise, they move on to the next question (as the correct answer would be the other option). If the time runs out before the question is fully asked and both possible answers are provided, the contestant may still answer it; however, if the statement hasn't been completed (or the options weren't provided), they cannot answer.", foto: "../gameImg/foto0.png" }, { - nombre: "WARM QUESTION", + nombre: "Warm Question", descripcion: "It consists of ten topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly.", foto: "../gameImg/foto1.jpg" }, { - nombre: "DISCOVERING CITIES", + nombre: "Discovering Cities", descripcion: "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test.", foto: "../gameImg/foto2.png" }, { - nombre: "THE CHALLENGE", + nombre: "Challenge", descripcion: "The 'Challenge' game mode is the quintessential game mode, as it allows you to customize the match to your liking. This game mode is tailored for those who wish to practice certain game formats before engaging in our various other game modes.", foto: "../gameImg/foto3.jpg" }, { - nombre: "ONLINE MODE", + nombre: "Multiplayer", descripcion: "Create a room for other player to join and play 1vs1", foto: "../gameImg/foto3.jpg" } @@ -142,6 +143,7 @@ describe('Instructions component', () => { // Click in the game button fireEvent.click(screen.getAllByRole('button')[index]); + console.log(game.descripcion); // Verify that the game name and description are in the document const gameName = await screen.findAllByText(game.nombre); expect(gameName).toHaveLength(2); // Check the expected number of matches diff --git a/webapp/src/__tests__/pages/Login.test.js b/webapp/src/__tests__/pages/Login.test.js index b4166936..84282fdb 100644 --- a/webapp/src/__tests__/pages/Login.test.js +++ b/webapp/src/__tests__/pages/Login.test.js @@ -1,27 +1,33 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import { SessionContext } from '../../SessionContext'; -import { BrowserRouter as Router } from 'react-router-dom'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Login from '../../pages/Login'; import '../../localize/i18n'; const mockAxios = new MockAdapter(axios); +const mockNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, +})); describe('Login component', () => { beforeEach(() => { mockAxios.reset(); - // Mock the axios.post request to simulate a successful response - mockAxios.onPost('http://localhost:8000/login').reply(200); + mockNavigate.mockReset(); + mockAxios.onPost('http://localhost:8000/login').reply(200, { avatar: 'bertinIcon.jpg' }); }); it('should render login form', () => { render( - + }])}> - + ); @@ -32,21 +38,25 @@ describe('Login component', () => { }); it('should log in a user', async () => { + const createSession = jest.fn(); + const updateAvatar = jest.fn(); + render( - - + + }])}> - + ); fireEvent.change(screen.getByLabelText('Username'), { target: { value: 'testuser' } }); fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'testpassword' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { - expect(mockAxios.history.post.length).toBe(1); // Ensure one POST request is made + expect(createSession).toHaveBeenCalledWith('testuser'); + expect(updateAvatar).toHaveBeenCalledWith('bertinIcon.jpg'); + expect(mockNavigate).toHaveBeenCalledWith('/homepage'); }); }); @@ -55,19 +65,18 @@ describe('Login component', () => { render( - + }])}> - + ); fireEvent.change(screen.getByLabelText('Username'), { target: { value: ' ' } }); fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'testpassword' } }); - fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { expect(screen.getByText('Error: The username cannot contain only spaces')).toBeInTheDocument(); }); }); -}); \ No newline at end of file +}); diff --git a/webapp/src/__tests__/pages/MultiplayerGame.test.js b/webapp/src/__tests__/pages/MultiplayerGame.test.js index a0f1c742..eb828baf 100644 --- a/webapp/src/__tests__/pages/MultiplayerGame.test.js +++ b/webapp/src/__tests__/pages/MultiplayerGame.test.js @@ -33,7 +33,7 @@ describe('Game component', () => { correctAnswer: 'Madrid', categories: ['Geography'], language: 'en' - }; + }; const generateQuestionArray = (questionObject, count) => { const questionArray = []; diff --git a/webapp/src/__tests__/pages/Profile.test.js b/webapp/src/__tests__/pages/Profile.test.js index fd1fe13f..3f11cc61 100644 --- a/webapp/src/__tests__/pages/Profile.test.js +++ b/webapp/src/__tests__/pages/Profile.test.js @@ -7,7 +7,13 @@ import { SessionContext } from '../../SessionContext'; import Profile from '../../pages/Profile'; const mockAxios = new MockAdapter(axios); +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, +})); + describe('Profile component', () => { const username = 'testuser'; const initialUserInfo = { @@ -19,13 +25,14 @@ describe('Profile component', () => { }; beforeEach(() => { - mockAxios.reset(); + mockAxios.reset(); }); it('should fetch and display user information', async () => { - mockAxios.onGet(`http://localhost:8000/user/profile`, { params: { username } }).reply(200, initialUserInfo); + const updateAvatar = jest.fn(); + mockAxios.onGet(`http://localhost:8000/profile`, { params: { username } }).reply(200, initialUserInfo); render( - + @@ -41,10 +48,11 @@ describe('Profile component', () => { }); it('should display an error if fetching user info fails', async () => { - mockAxios.onGet(`http://localhost:8000/user/profile`, { params: { username } }).reply(400, { error: 'Error fetching user information' }); + const updateAvatar = jest.fn(); + mockAxios.onGet(`http://localhost:8000/profile`, { params: { username } }).reply(400, { error: 'Error fetching user information' }); render( - + @@ -57,12 +65,13 @@ describe('Profile component', () => { }); it('should handle avatar selection and update', async () => { + const updateAvatar = jest.fn(); const newAvatar = 'bertinIcon.jpg'; - mockAxios.onGet(`http://localhost:8000/user/profile`, { params: { username } }).reply(200, initialUserInfo); - mockAxios.onPost(`http://localhost:8000/user/profile/${username}`, { imageUrl: newAvatar }).reply(200); + mockAxios.onGet(`http://localhost:8000/profile`, { params: { username } }).reply(200, initialUserInfo); + mockAxios.onPut(`http://localhost:8000/profile/${username}`, { imageUrl: newAvatar }).reply(200); render( - + @@ -78,23 +87,23 @@ describe('Profile component', () => { fireEvent.click(screen.getByTestId('confirm-button')); await waitFor(() => { - expect(screen.getByText('Avatar changed successfully')).toBeInTheDocument(); - expect(mockAxios.history.post.length).toBe(1); - expect(mockAxios.history.post[0].data).toContain(newAvatar); + expect(mockAxios.history.put.length).toBe(1); + expect(mockAxios.history.put[0].data).toContain(newAvatar); }); }); it('should handle avatar selection and update after choosing different characters', async () => { + const updateAvatar = jest.fn(); const newAvatar = 'teresaIcon.jpg'; - mockAxios.onGet(`http://localhost:8000/user/profile`, { params: { username } }).reply(200, initialUserInfo); - mockAxios.onPost(`http://localhost:8000/user/profile/${username}`, { imageUrl: newAvatar }).reply(200); + mockAxios.onGet(`http://localhost:8000/profile`, { params: { username } }).reply(200, initialUserInfo); + mockAxios.onPut(`http://localhost:8000/profile/${username}`, { imageUrl: newAvatar }).reply(200); render( - + - + ); await waitFor(() => { @@ -112,19 +121,20 @@ describe('Profile component', () => { fireEvent.click(screen.getByTestId('confirm-button')); await waitFor(() => { - expect(screen.getByText('Avatar changed successfully')).toBeInTheDocument(); - expect(mockAxios.history.post.length).toBe(1); - expect(mockAxios.history.post[0].data).toContain(newAvatar); + expect(mockAxios.history.put.length).toBe(1); + expect(mockAxios.history.put[0].data).toContain(newAvatar); }); + }); it('should display an error if avatar update fails', async () => { + const updateAvatar = jest.fn(); const newAvatar = 'bertinIcon.jpg'; - mockAxios.onGet(`http://localhost:8000/user/profile`, { params: { username } }).reply(200, initialUserInfo); - mockAxios.onPost(`http://localhost:8000/user/profile/${username}`, { imageUrl: newAvatar }).reply(400, { error: 'Error updating user information' }); + mockAxios.onGet(`http://localhost:8000/profile`, { params: { username } }).reply(200, initialUserInfo); + mockAxios.onPost(`http://localhost:8000/profile/${username}`, { imageUrl: newAvatar }).reply(400, { error: 'Error updating user information' }); render( - + @@ -144,6 +154,4 @@ describe('Profile component', () => { }); }); - - -}); +}); \ No newline at end of file diff --git a/webapp/src/__tests__/pages/Register.test.js b/webapp/src/__tests__/pages/Register.test.js index db401b1f..1ba15bbc 100644 --- a/webapp/src/__tests__/pages/Register.test.js +++ b/webapp/src/__tests__/pages/Register.test.js @@ -1,28 +1,35 @@ import React from 'react'; import { render, fireEvent, screen, waitFor } from '@testing-library/react'; import { SessionContext } from '../../SessionContext'; -import { BrowserRouter as Router } from 'react-router-dom'; +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import Register from '../../pages/Register'; import '../../localize/i18n'; const mockAxios = new MockAdapter(axios); +const mockNavigate = jest.fn(); + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, +})); describe('Register component', () => { beforeEach(() => { mockAxios.reset(); + mockNavigate.mockReset(); // Mock the axios.post request to simulate a successful response mockAxios.onPost('http://localhost:8000/user').reply(200); - mockAxios.onPost('http://localhost:8000/login').reply(200); + mockAxios.onPost('http://localhost:8000/login').reply(200, { avatar: 'bertinIcon.jpg' }); }); it('should render sign up form', () => { render( - - - + }])}> + + ); @@ -35,11 +42,14 @@ describe('Register component', () => { }); it('should sign up a user', async () => { + const createSession = jest.fn(); + const updateAvatar = jest.fn(); + render( - - + + }])}> - + ); @@ -60,9 +70,9 @@ describe('Register component', () => { render( - + }])}> - + ); diff --git a/webapp/src/__tests__/pages/Statistics.test.js b/webapp/src/__tests__/pages/Statistics.test.js index e0634682..ed24cbd5 100644 --- a/webapp/src/__tests__/pages/Statistics.test.js +++ b/webapp/src/__tests__/pages/Statistics.test.js @@ -18,7 +18,7 @@ jest.mock('react-router-dom', () => ({ describe('Statistics component', () => { beforeAll(async () => { - mockAxios.onGet('http://localhost:8000/user/statistics/testuser',{ params: { loggedUser: "testuser" } }).reply(200, { + mockAxios.onGet('http://localhost:8000/statistics/testuser',{ params: { loggedUser: "testuser" } }).reply(200, { wise_men_stack_earned_money: 50, wise_men_stack_correctly_answered_questions: 8, wise_men_stack_incorrectly_answered_questions: 12, @@ -39,7 +39,7 @@ describe('Statistics component', () => { online_games_played: 12, }); - mockAxios.onGet('http://localhost:8000/user/questionsRecord/testuser/TheChallenge').reply(200, [ + mockAxios.onGet('http://localhost:8000/questionsRecord/testuser/TheChallenge').reply(200, [ { createdAt: '2024-04-11T12:00:00Z', questions: [ @@ -53,7 +53,7 @@ describe('Statistics component', () => { }, ]); - mockAxios.onGet('http://localhost:8000/user/questionsRecord/testuser/WiseMenStack').reply(200, [ + mockAxios.onGet('http://localhost:8000/questionsRecord/testuser/WiseMenStack').reply(200, [ { createdAt: '2024-04-11T12:00:00Z', questions: [ @@ -67,7 +67,7 @@ describe('Statistics component', () => { }, ]); - mockAxios.onGet('http://localhost:8000/user/questionsRecord/testuser/WarmQuestion').reply(200, [ + mockAxios.onGet('http://localhost:8000/questionsRecord/testuser/WarmQuestion').reply(200, [ { createdAt: '2024-04-11T12:00:00Z', questions: [ @@ -81,7 +81,7 @@ describe('Statistics component', () => { }, ]); - mockAxios.onGet('http://localhost:8000/user/questionsRecord/testuser/DiscoveringCities').reply(200, [ + mockAxios.onGet('http://localhost:8000/questionsRecord/testuser/DiscoveringCities').reply(200, [ { createdAt: '2024-04-11T12:00:00Z', questions: [ @@ -95,7 +95,7 @@ describe('Statistics component', () => { }, ]); - mockAxios.onGet('http://localhost:8000/user/questionsRecord/testuser/OnlineMode').reply(200, [ + mockAxios.onGet('http://localhost:8000/questionsRecord/testuser/OnlineMode').reply(200, [ { createdAt: '2024-04-11T12:00:00Z', questions: [ @@ -239,9 +239,9 @@ describe('Statistics component', () => { await screen.findByText('STATISTICS'); - fireEvent.click(screen.getByText('Online Mode')); + fireEvent.click(screen.getByText('Multiplayer')); - expect(screen.getByText('Online Mode')).toBeInTheDocument(); + expect(screen.getByText('Multiplayer')).toBeInTheDocument(); expect(screen.getByText('Total Points:')).toBeInTheDocument(); expect(screen.getByText('15')).toBeInTheDocument(); expect(screen.getByText('Correctly Answered Questions:')).toBeInTheDocument(); @@ -253,7 +253,7 @@ describe('Statistics component', () => { expect(screen.getByText('Games Played:')).toBeInTheDocument(); expect(screen.getAllByText('12')); - fireEvent.click(screen.getByText('Show Questions Record')); + fireEvent.click(screen.getByText("Show Questions Record")); await screen.findByText('Game 04/11/2024, 14:00'); expect(screen.getByText('What is 1 + 1?')).toBeInTheDocument(); diff --git a/webapp/src/__tests__/pages/TheChallengeGame.test.js b/webapp/src/__tests__/pages/TheChallengeGame.test.js index a64e3f00..9627bba8 100644 --- a/webapp/src/__tests__/pages/TheChallengeGame.test.js +++ b/webapp/src/__tests__/pages/TheChallengeGame.test.js @@ -23,8 +23,8 @@ describe('Game component', () => { ] ); - mockAxios.onPost(`http://localhost:8000/statistics`).reply(200, { success: true }); - mockAxios.onPost(`http://localhost:8000/user/questionsRecord`).reply(200, { success: true }); + mockAxios.onPut(`http://localhost:8000/statistics`).reply(200, { success: true }); + mockAxios.onPut(`http://localhost:8000/questionsRecord`).reply(200, { success: true }); }); @@ -50,13 +50,10 @@ describe('Game component', () => { fireEvent.click(screen.getByRole('button', { name: 'Start game' })); // Espera a que aparezca la pregunta - await waitFor(() => screen.getByText('Which is the capital of Spain?')); - - expect(screen.findByText('1')); - //expect(screen.findByText('1/4')); + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); // Verifica que el juego haya comenzado correctamente mostrando la pregunta y las opciones - expect(screen.getByText('Which is the capital of Spain?')).toBeInTheDocument(); + expect(screen.getByText('Which is the capital of Spain?'.toUpperCase())).toBeInTheDocument(); expect(screen.getByText('Madrid')).toBeInTheDocument(); expect(screen.getByText('Barcelona')).toBeInTheDocument(); expect(screen.getByText('Paris')).toBeInTheDocument(); @@ -83,17 +80,13 @@ describe('Game component', () => { fireEvent.click(screen.getByRole('button', { name: 'Start game' })); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); const correctAnswer = screen.getByRole('button', { name: 'Madrid' }); - expect(correctAnswer).not.toHaveStyle({ backgroundColor: 'green' }); - + expect(screen.findByTestId("anwer0")); //selects correct answer fireEvent.click(correctAnswer); - - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'lightgreen' }); - - expect(correctAnswer).toHaveStyle({ backgroundColor: 'green' }); + expect(screen.findByTestId("succes0")); }); @@ -118,16 +111,13 @@ describe('Game component', () => { fireEvent.click(screen.getByRole('button', { name: 'Start game' })); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); const incorrectAnswer = screen.getByRole('button', { name: 'Barcelona' }); - expect(incorrectAnswer).not.toHaveStyle({ backgroundColor: 'red' }); - + expect(screen.findByTestId("anwer1")); //selects correct answer fireEvent.click(incorrectAnswer); - - expect(incorrectAnswer).toHaveStyle({ backgroundColor: 'red' }); - //expect(screen.findByText('1')).toHaveStyle({ backgroundColor: 'salmon' }); + expect(screen.findByTestId("failure1")); }); @@ -151,7 +141,7 @@ describe('Game component', () => { fireEvent.click(screen.getByRole('button', { name: 'Start game' })); // waits for the question to appear - await waitFor(() => screen.getByText('Which is the capital of Spain?')); + await waitFor(() => screen.getByText('Which is the capital of Spain?'.toUpperCase())); setTimeout(() => { // Comprobamos que el callback ha sido llamado después del tiempo especificado diff --git a/webapp/src/__tests__/pages/WarmQuestionGame.test.js b/webapp/src/__tests__/pages/WarmQuestionGame.test.js index bd1c6156..6297a0d5 100644 --- a/webapp/src/__tests__/pages/WarmQuestionGame.test.js +++ b/webapp/src/__tests__/pages/WarmQuestionGame.test.js @@ -22,8 +22,8 @@ describe('Game component', () => { } ); - mockAxios.onPost('http://localhost:8000/statistics').reply(200, { success: true }); - mockAxios.onPost('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/questionsRecord').reply(200, { success: true }); }); it('should render question, answers and other', async () => { diff --git a/webapp/src/__tests__/pages/WiseMenStackGame.test.js b/webapp/src/__tests__/pages/WiseMenStackGame.test.js index bd8082df..ff4961cf 100644 --- a/webapp/src/__tests__/pages/WiseMenStackGame.test.js +++ b/webapp/src/__tests__/pages/WiseMenStackGame.test.js @@ -24,8 +24,8 @@ describe('Wise Men Stack Game component', () => { }] ); - mockAxios.onPost('http://localhost:8000/statistics').reply(200, { success: true }); - mockAxios.onPost('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/statistics').reply(200, { success: true }); + mockAxios.onPut('http://localhost:8000/user/questionsRecord').reply(200, { success: true }); }); diff --git a/webapp/src/components/NavBar.js b/webapp/src/components/NavBar.js index 322a6286..79f4dc2d 100644 --- a/webapp/src/components/NavBar.js +++ b/webapp/src/components/NavBar.js @@ -10,9 +10,8 @@ import { useTranslation } from 'react-i18next'; import i18n from 'i18next'; function NavBar() { - // Width for the nav menu element (?) Is it used later as a boolean ?????? const [anchorElNav, setAnchorElNav] = React.useState(null); - const { username, isLoggedIn, destroySession } = useContext(SessionContext); + const { username, isLoggedIn, avatar, destroySession } = useContext(SessionContext); const navigate = useNavigate(); @@ -50,7 +49,7 @@ function NavBar() { { path: '/statistics', text: t("NavBar.statistics") }, { path: '/instructions', text: t("NavBar.instructions") }, { path: '/group/menu', text: t("NavBar.groups") }, - { path: '/ranking', text: 'Ranking' } + { path: '/ranking', text: t("NavBar.ranking") } // Add an object for each new page ]; @@ -144,8 +143,7 @@ function NavBar() { {username} - {/* Need to change the image for the user profile one */} - + diff --git a/webapp/src/data/gameInfo.json b/webapp/src/data/gameInfo.json index 27a8397e..012e3110 100644 --- a/webapp/src/data/gameInfo.json +++ b/webapp/src/data/gameInfo.json @@ -1,6 +1,6 @@ [ { - "nombre": "WISE MEN STACK", + "nombre": "Wise Men Stack", "descripcion": "The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they move on to the next question.", "foto": "../instructions/foto0.png", @@ -8,21 +8,21 @@ "cardFoto": "../homePage/foto0.png" }, { - "nombre": "WARM QUESTION", + "nombre": "Warm Question", "descripcion": "It consists of ten topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly.", "foto": "../instructions/foto1.jpg", "card":"It consists of ten topics of varied themes.", "cardFoto": "../homePage/foto1.jpg" }, { - "nombre": "DISCOVERING CITIES", - "descripcion": "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test..", + "nombre": "Discovering Cities", + "descripcion": "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test.", "foto": "../instructions/foto2.png", "card":"The contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world.", "cardFoto": "../homePage/foto2.png" }, { - "nombre": "THE CHALLENGE", + "nombre": "Challenge", "descripcion": "The 'Challenge' game mode is the quintessential game mode, as it allows you to customize the match to your liking. This game mode is tailored for those who wish to practice certain game formats before engaging in our various other game modes.", "foto": "../instructions/foto3.jpg", "card":"The 'Challenge' game mode is the quintessential game mode, as it allows you to customize the match to your liking.", @@ -30,7 +30,7 @@ }, { - "nombre": "MULTIPLAYER MODE", + "nombre": "Multiplayer", "descripcion": "Create a room and share the room code with other players to play. It also has a room chat.", "foto": "../instructions/online-photo.jpg", "card":"Create a room and share the room code with other players to play. It also has a room chat.", diff --git a/webapp/src/data/icons.js b/webapp/src/data/icons.js index 386ae2d9..2da14712 100644 --- a/webapp/src/data/icons.js +++ b/webapp/src/data/icons.js @@ -4,7 +4,7 @@ const getHugo = () => { const getAlberto = () => { return "bertinIcon.jpg"; - } +} const getWiffo = () => { return "wiffoIcon.jpg"; diff --git a/webapp/src/localize/en.json b/webapp/src/localize/en.json index 1522c944..ad821a35 100644 --- a/webapp/src/localize/en.json +++ b/webapp/src/localize/en.json @@ -6,11 +6,16 @@ "q": "QUEST" }, + "Homepage": { + "title": "GAME MODES" + }, + "NavBar":{ "play": "Play", "statistics": "Statistics", "instructions": "Instructions", "groups": "Groups", + "ranking": "Ranking", "languages": { "en": "English", "es": "Spanish", @@ -24,27 +29,26 @@ }, "Games": { - "wise_men": { + "Wise Men Stack": { "name": "Wise Men Stack", "desc": "The player chooses a topic from five available options and must answer a battery of questions related to it within 60 seconds. For each question, the host provides two options. If the contestant guesses correctly, they win €20; otherwise, they move on to the next question (as the correct answer would be the other option). If the time runs out before the question is fully asked and both possible answers are provided, the contestant may still answer it; however, if the statement hasn't been completed (or the options weren't provided), they cannot answer." }, - "warm_quest": { + "Warm Question": { "name": "Warm Question", "desc": "It consists of ten topics of varied themes. For each correct answer, €100 is earned, and €10 are lost if the contestant passes, does not respond, or answers incorrectly." }, - "discover": { + "Discovering Cities": { "name": "Discovering Cities", - "desc": "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test.." + "desc": "In the 'Discovering Cities' game mode, the contestant will face a challenge where they will be repeatedly asked questions referring to different cities around the world. To successfully overcome the challenge, the contestant must answer as many questions as possible correctly throughout the test." }, - "challenge": { + "Challenge": { "name": "Challenge", "desc": "The 'Challenge' game mode is the quintessential game mode, as it allows you to customize the match to your liking. This game mode is tailored for those who wish to practice certain game formats before engaging in our various other game modes." }, - "multiplayer": { + "Multiplayer": { "name": "Multiplayer", "desc": "Create a room for other player to join and play 1vs1" } - }, "Game": { @@ -96,8 +100,11 @@ "joined": "JOINED", "join": "JOIN IT!", "filled": "FILLED", + "exit": "EXIT IT!", + "delete": "DELETE", "Details": { "creator": "Creator", + "statistics": "See statistics", "date": "Created in", "members": "Members" } @@ -105,6 +112,7 @@ "Statistics": { "title": "STATISTICS", + "game": "Game", "table": { "money": "Total Points", "questions_corr": "Correctly Answered Questions", @@ -114,6 +122,10 @@ "total_time": "Total Time Played", "cities_corr": "Correctly Answered Cities", "cities_incorr": "Incorrectly Answered Cities" + }, + "button": { + "hide_record": "Hide Questions Record", + "show_record": "Show Questions Record" } }, @@ -155,6 +167,10 @@ "Ranking":{ "users":"Users", - "groups": "Groups" + "groups": "Groups", + "total_money": "POINTS", + "correct_answers": "CORRECT ANSWERS", + "incorrect_answers": "INCORRECT ANSWERS", + "total_games": "GAMES PLAYED" } } \ No newline at end of file diff --git a/webapp/src/localize/es.json b/webapp/src/localize/es.json index 8a7d97b3..5b51732b 100644 --- a/webapp/src/localize/es.json +++ b/webapp/src/localize/es.json @@ -6,11 +6,16 @@ "q": "QUESTIONARIO" }, + "Homepage": { + "title": "MODOS DE JUEGO" + }, + "NavBar":{ "play": "Jugar", "statistics": "Estadísticas", "instructions": "Instrucciones", "groups": "Grupos", + "ranking": "Ranking", "languages": { "en": "Inglés", "es": "Español", @@ -24,24 +29,24 @@ }, "Games": { - "wise_men": { - "name": "Hombres Sabiondos", + "Wise Men Stack": { + "name": "Wise Men Stack", "desc": "El jugador elige un tema entre cinco opciones disponibles y debe responder una batería de preguntas relacionadas con él en un plazo de 60 segundos. Para cada pregunta, el presentador proporciona dos opciones. Si el concursante acierta, gana €20; de lo contrario, pasa a la siguiente pregunta (ya que la respuesta correcta sería la otra opción). Si el tiempo se agota antes de que se formule por completo la pregunta y se proporcionen ambas posibles respuestas, el concursante aún puede responderla; sin embargo, si la afirmación no se ha completado (o las opciones no se proporcionaron), no pueden responder." }, - "warm_quest": { - "name": "Pregunta Calentorra", + "Warm Question": { + "name": "Warm Question", "desc": "Consta de diez temas de diferentes temáticas. Por cada respuesta correcta, se ganan €100, y se pierden €10 si el concursante pasa, no responde o responde incorrectamente." }, - "discover": { - "name": "Descubriendo Ciudades", + "Discovering Cities": { + "name": "Discovering Cities", "desc": "En el modo de juego 'Descubriendo Ciudades', el concursante enfrentará un desafío donde se le harán preguntas repetidas referentes a diferentes ciudades de todo el mundo. Para superar con éxito el desafío, el concursante debe responder tantas preguntas como sea posible correctamente a lo largo de la prueba." }, - "challenge": { - "name": "Desafío", + "Challenge": { + "name": "Challenge", "desc": "El modo de juego 'Desafío' es el modo de juego por excelencia, ya que te permite personalizar el enfrentamiento a tu gusto. Este modo de juego está diseñado para aquellos que deseen practicar ciertos formatos de juego antes de participar en nuestros diversos otros modos de juego." }, - "multiplayer": { - "name": "Multijugador", + "Multiplayer": { + "name": "Multiplayer", "desc": "Crea una sala para que otros jugadores se unan y jueguen 1 contra 1." } }, @@ -93,10 +98,13 @@ "name": "Nombre", "see_members": "Ver Miembros", "joined": "UNIDO", - "join": "¡ÚNETE!", + "join": "UNIRSE", + "exit": "ABANDONAR", + "delete": "ELIMINAR", "filled": "LLENO", "Details": { "creator": "Creador", + "statistics": "Ver estadísticas", "date": "Creado en", "members": "Miembros" } @@ -104,6 +112,7 @@ "Statistics": { "title": "ESTADÍSTICAS", + "game": "Partida", "table": { "money": "Puntos Ganado", "questions_corr": "Preguntas Respondidas Correctamente", @@ -113,6 +122,10 @@ "total_time": "Tiempo Total Jugado", "cities_corr": "Ciudades Respondidas Correctamente", "cities_incorr": "Ciudades Respondidas Incorrectamente" + }, + "button": { + "hide_record": "Ocultar registro de preguntas", + "show_record": "Mostrar registro de preguntas" } }, @@ -154,6 +167,11 @@ "Ranking":{ "users":"Usuarios", - "groups": "Grupos" + "groups": "Grupos", + "name":"NOMBRE", + "total_money": "PUNTOS", + "correct_answers": "RESPUESTAS CORRECTAS", + "incorrect_answers": "RESPUESTAS INCORRECTAS", + "total_games": "PARTIDAS JUGADAS" } } \ No newline at end of file diff --git a/webapp/src/localize/fr.json b/webapp/src/localize/fr.json index ab101a07..6ae2d1e5 100644 --- a/webapp/src/localize/fr.json +++ b/webapp/src/localize/fr.json @@ -6,11 +6,16 @@ "q": "QUEST" }, + "Homepage": { + "title": "MODES DE JEU" + }, + "NavBar":{ "play": "Jouer", "statistics": "Statistiques", "instructions": "Instructions", "groups": "Groupes", + "ranking": "Ranking", "languages": { "en": "Anglais", "es": "Espagnol", @@ -19,29 +24,29 @@ }, "Footer": { - "api_questions": "QUESTIONS API DOC", - "api_users": "USERS API DOC" + "api_questions": "DOCUMENTATION DE L'API DES QUESTIONS", + "api_users": "DOCUMENTATION DE L'API DES UTILISATEURS" }, "Games": { - "wise_men": { - "name": "Pile de Sages", + "Wise Men Stack": { + "name": "Wise Men Stack", "desc": "Le joueur choisit un sujet parmi cinq options disponibles et doit répondre à une batterie de questions qui y sont liées dans un délai de 60 secondes. Pour chaque question, l'hôte fournit deux options. Si le participant devine correctement, il gagne 20 € ; sinon, il passe à la question suivante (car la bonne réponse serait l'autre option). Si le temps s'écoule avant que la question ne soit entièrement posée et que les deux réponses possibles soient fournies, le participant peut toujours y répondre ; cependant, si l'énoncé n'a pas été complété (ou si les options n'ont pas été fournies), il ne peut pas répondre." }, - "warm_quest": { - "name": "Question Chaleureuse", + "Warm Question": { + "name": "Warm Question", "desc": "Il se compose de dix sujets de thèmes variés. Pour chaque réponse correcte, 100 € sont gagnés, et 10 € sont perdus si le participant passe, ne répond pas ou répond incorrectement." }, - "discover": { - "name": "Découverte des Villes", + "Discovering Cities": { + "name": "Discovering Cities", "desc": "Dans le mode de jeu 'Découverte des Villes', le participant sera confronté à un défi où il lui sera posé à plusieurs reprises des questions se référant à différentes villes du monde entier. Pour surmonter avec succès le défi, le participant doit répondre correctement à autant de questions que possible tout au long du test." }, - "challenge": { - "name": "Défi", + "Challenge": { + "name": "Challenge", "desc": "Le mode de jeu 'Défi' est le mode de jeu par excellence, car il vous permet de personnaliser le match à votre guise. Ce mode de jeu est adapté à ceux qui souhaitent s'entraîner à certains formats de jeu avant de s'engager dans nos divers autres modes de jeu." }, - "multiplayer": { - "name": "Multijoueur", + "Multiplayer": { + "name": "Multiplayer", "desc": "Créez une salle pour que d'autres joueurs se joignent et jouent en 1 contre 1." } @@ -95,10 +100,13 @@ "name": "Nom", "see_members": "Voir les Membres", "joined": "REJOINT", - "join": "REJOINDRE !", + "join": "REJOINDRE", + "exit": "Sortir", + "delete": "Supprimer", "filled": "REMPLI", "Details": { "creator": "Créateur", + "statistics": "Statistiques", "date": "Créé en", "members": "Membres" } @@ -106,6 +114,7 @@ "Statistics": { "title": "STATISTIQUES", + "game": "Partie", "table": { "money": "Points gagnés", "questions_corr": "Questions Correctement Répondues", @@ -115,6 +124,10 @@ "total_time": "Temps Total Joué", "cities_corr": "Villes Répondus Correctement", "cities_incorr": "Villes Répondus Incorrectement" + }, + "button": { + "hide_record": "Masquer l'historique des questions", + "show_record": "Afficher l'historique des questions" } }, @@ -152,7 +165,11 @@ }, "Ranking": { "users": "Utilisateurs", - "groups": "Groupes" + "groups": "Groupes", + "total_money": "POINTS", + "correct_answers": "CORRECT ANSWERS", + "incorrect_answers": "INCORRECT ANSWERS", + "total_games": "GAMES PLAYED" } } diff --git a/webapp/src/pages/DiscoveringCitiesGame.js b/webapp/src/pages/DiscoveringCitiesGame.js index 3d50053e..7575ff67 100644 --- a/webapp/src/pages/DiscoveringCitiesGame.js +++ b/webapp/src/pages/DiscoveringCitiesGame.js @@ -100,7 +100,7 @@ const DiscovertingCitiesGame = () => { const updateStatistics = async() => { try { - await axios.post(`${apiEndpoint}/statistics`, { + await axios.put(`${apiEndpoint}/statistics`, { username:username, the_callenge_earned_money:0, the_callenge_correctly_answered_questions:0, @@ -133,7 +133,7 @@ const DiscovertingCitiesGame = () => { const updateQuestionsRecord = async() => { try { - await axios.post(`${apiEndpoint}/user/questionsRecord`, { + await axios.put(`${apiEndpoint}/questionsRecord`, { questions: userResponses, username: username, gameMode: "DiscoveringCities" diff --git a/webapp/src/pages/Game.js b/webapp/src/pages/Game.js index db5aa3d8..0310eeaf 100644 --- a/webapp/src/pages/Game.js +++ b/webapp/src/pages/Game.js @@ -94,6 +94,7 @@ const Game = () => { // gets a random question from the database and initializes button states to null const startNewRound = async () => { setAnswered(false); + setPassNewRound(false); // Updates current language setCurrentLanguage(i18n.language); @@ -109,7 +110,7 @@ const Game = () => { const updateStatistics = async() => { try { - await axios.post(`${apiEndpoint}/statistics`, { + await axios.put(`${apiEndpoint}/statistics`, { username:username, the_callenge_earned_money:totalScore, the_callenge_correctly_answered_questions:correctlyAnsweredQuestions, @@ -143,7 +144,7 @@ const Game = () => { const updateQuestionsRecord = async() => { try { - await axios.post(`${apiEndpoint}/user/questionsRecord`, { + await axios.put(`${apiEndpoint}/questionsRecord`, { questions: userResponses, username: username, gameMode: "TheChallenge" diff --git a/webapp/src/pages/GroupDetails.js b/webapp/src/pages/GroupDetails.js index 6517f515..621fa121 100644 --- a/webapp/src/pages/GroupDetails.js +++ b/webapp/src/pages/GroupDetails.js @@ -50,21 +50,19 @@ const GroupDetails = () => { // Returns all group data including the creator, the creation date and the members list return ( - - {groupInfo.name} + + {groupInfo.name} - { `${t("Groups.Details.creator")}: ${groupInfo.creator}` } + {t("Groups.Details.creator")}: {groupInfo.creator} - { `${t("Groups.Details.date")}: ${new Date(groupInfo.createdAt).toLocaleDateString()}` } + {t("Groups.Details.date")}: {new Date(groupInfo.createdAt).toLocaleDateString()} - { - `${ t("Groups.Details.members") } (${totalMembers}/${expectedMembers}):` - } + {t("Groups.Details.members")}: {totalMembers}/{expectedMembers} @@ -74,7 +72,7 @@ const GroupDetails = () => { {groupInfo.show && ( )} diff --git a/webapp/src/pages/Groups.js b/webapp/src/pages/Groups.js index b3bfbc5b..e30383fd 100644 --- a/webapp/src/pages/Groups.js +++ b/webapp/src/pages/Groups.js @@ -66,7 +66,7 @@ const Groups = () => { // Function that makes the user join a group and shows the possible errors when making this const addToGroup = async (name) => { try { - await axios.post(`${apiEndpoint}/group/`+name, { username }); + await axios.put(`${apiEndpoint}/group/`+name, { username }); setSnackbarMessage('Joined the group successfully'); setOpenSnackbar(true); fetchData(); @@ -79,7 +79,7 @@ const Groups = () => { // Function that makes a member of a group leave it. const exitFromGroup = async (name) => { try { - await axios.post(`${apiEndpoint}/group/`+name+`/exit`, { username }); + await axios.put(`${apiEndpoint}/group/`+name+`/exit`, { username }); setSnackbarMessage('Left the group successfully'); setOpenSnackbar(true); fetchData(); @@ -97,7 +97,7 @@ const Groups = () => { return ( - + { t("Groups.title") } @@ -112,7 +112,7 @@ const Groups = () => { setName(e.target.value)} /> - @@ -130,17 +130,15 @@ const Groups = () => { - + {group.isMember ? ( group.isCreator ? ( ):( ) ) : group.isFull ? ( @@ -152,6 +150,9 @@ const Groups = () => { { t("Groups.join") } )} + {error && ( setError('')} message={`Error: ${error}`} />)} diff --git a/webapp/src/pages/Home.js b/webapp/src/pages/Home.js index 381a6c74..fcfa9336 100644 --- a/webapp/src/pages/Home.js +++ b/webapp/src/pages/Home.js @@ -1,10 +1,11 @@ import * as React from "react"; import {Box, Button} from "@mui/material"; import useMediaQuery from '@mui/material/useMediaQuery'; - +import { useTranslation } from 'react-i18next'; const Home = () => { const xxl = useMediaQuery('(min-width:1920px)'); + const { t } = useTranslation(); const styles = { logo:{ @@ -96,7 +97,7 @@ const Home = () => { Logo - +