From 8e5e7378ac0c39bead6d40c32584ee729352b870 Mon Sep 17 00:00:00 2001 From: sinne10 Date: Sun, 14 Apr 2024 12:38:23 +0200 Subject: [PATCH 01/74] Added basic password-strength-meter --- webapp/package-lock.json | 8 +++- webapp/package.json | 3 +- .../loginAndRegistration/AddUser.js | 45 ++++++++++++++++++- webapp/src/custom.css | 38 ++++++++++++++++ 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 076761a2..41859609 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -28,7 +28,8 @@ "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-scripts": "^5.0.1", - "web-vitals": "^3.5.1" + "web-vitals": "^3.5.1", + "zxcvbn": "^4.4.2" }, "devDependencies": { "axios-mock-adapter": "^1.22.0", @@ -28453,6 +28454,11 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zxcvbn": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/zxcvbn/-/zxcvbn-4.4.2.tgz", + "integrity": "sha512-Bq0B+ixT/DMyG8kgX2xWcI5jUvCwqrMxSFam7m0lAf78nf04hv6lNCsyLYdyYTrCVMqNDY/206K7eExYCeSyUQ==" } } } diff --git a/webapp/package.json b/webapp/package.json index 2ea16917..55422652 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -23,7 +23,8 @@ "react-icons": "^5.0.1", "react-router-dom": "^6.22.3", "react-scripts": "^5.0.1", - "web-vitals": "^3.5.1" + "web-vitals": "^3.5.1", + "zxcvbn": "^4.4.2" }, "scripts": { "start": "react-scripts start", diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 2324ecab..213967ca 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import axios from 'axios'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import zxcvbn from "zxcvbn"; const AddUser = () => { @@ -14,6 +15,8 @@ const AddUser = () => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [repeatPassword, setRepeatPassword] = useState(''); + const [passwordStrength, setPasswordStrength] = useState(undefined); + const [passwordStrengthText, setPasswordStrengthText] = useState('Weak password'); const handleSubmit = async (event) => { @@ -34,6 +37,35 @@ const AddUser = () => { } }; + const handlePasswordChange = (e) => { + const newPassword = e.target.value; + setPassword(newPassword); + + const newStrength = zxcvbn(newPassword); + + switch(newStrength.score){ + case 0: + setPasswordStrengthText('Weak password'); + break; + case 1: + setPasswordStrengthText('Weak password'); + break; + case 2: + setPasswordStrengthText('Fair password'); + break; + case 3: + setPasswordStrengthText('Good password'); + break; + case 4: + setPasswordStrengthText('Strong password'); + break; + default: + setPasswordStrengthText('Weak password'); + break; + } + setPasswordStrength(newStrength); + }; + return (
@@ -51,7 +83,7 @@ const AddUser = () => { onChange={(e) => setUsername(e.target.value)} />
-
+

{t("addUser.password_placeholder")}:

{ placeholder={t("addUser.password_placeholder")} required value={password} - onChange={(e) => setPassword(e.target.value)} + onChange={handlePasswordChange} />
+
+ + {passwordStrengthText} + + +

{t("addUser.repeat_password_placeholder")}:

Date: Sun, 14 Apr 2024 13:54:13 +0200 Subject: [PATCH 02/74] Added password policies --- .../components/loginAndRegistration/AddUser.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 213967ca..43e751e1 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -17,20 +17,27 @@ const AddUser = () => { const [repeatPassword, setRepeatPassword] = useState(''); const [passwordStrength, setPasswordStrength] = useState(undefined); const [passwordStrengthText, setPasswordStrengthText] = useState('Weak password'); + const [submitError, setSubmitError] = useState(''); const handleSubmit = async (event) => { event.preventDefault(); try { //TODO: Add more validations - if(password === repeatPassword){ //User put the same password + if(password !== repeatPassword){ //User put the same password + setSubmitError('Passwords do not match'); + } else if(/\s/.test(password)){ //User put spaces in password + setSubmitError('Password cannot contain spaces'); + } else if(password.length < 8){ //Password too short + setSubmitError('Password must be at least 8 characters long'); + } else if(password.length > 64){ //Password too long + setSubmitError('Password cannot be over 64 characters long'); + } else{ //Continue + setSubmitError(''); const response = await axios.post(apiUrl, { username, password }); console.log("Registered user: " + response.data.username); navigate('/login'); } - else{ - //TODO: Show some errors to the user - } } catch (error) { console.error('Error adding user:', error); @@ -114,7 +121,7 @@ const AddUser = () => { onChange={(e) => setRepeatPassword(e.target.value)} />
- + {submitError &&

{submitError}

} From 19733855e419c8de910d53d1bc79729d2e73437c Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 17:27:58 +0200 Subject: [PATCH 03/74] Added cookie managment in the whole application --- .github/workflows/release.yml | 2 ++ gatewayservice/gateway-service.js | 20 ++++++++++++++++++- questionservice/question-service.js | 2 +- users/authservice/auth-service.js | 2 +- users/recordservice/record-service.js | 4 ++-- webapp/package-lock.json | 9 +++++++++ webapp/package.json | 1 + webapp/src/App.js | 3 --- .../HistoricalData/HistoricalView.js | 10 +++++----- .../HistoricalData/HistoricalView.test.js | 12 +++++------ .../HistoricalData/HistoryRecordRetriever.js | 4 ++-- webapp/src/components/fragments/NavBar.js | 8 ++++---- .../src/components/fragments/NavBar.test.js | 8 +++----- .../loginAndRegistration/AddUser.js | 1 + .../components/loginAndRegistration/Login.js | 7 ++++--- .../loginAndRegistration/Login.test.js | 3 --- .../loginAndRegistration/UserContext.js | 19 ------------------ .../questionView/CreationHistoricalRecord.js | 5 +++-- .../questionView/QuestionGenerator.js | 4 ++-- .../components/questionView/QuestionView.js | 10 ++++++---- .../questionView/QuestionView.test.js | 14 ++++++------- 21 files changed, 77 insertions(+), 71 deletions(-) delete mode 100644 webapp/src/components/loginAndRegistration/UserContext.js diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 74f4a99e..cef2a143 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,6 +76,8 @@ jobs: - uses: actions/checkout@v4 - name: Publish to Registry uses: elgohr/Publish-Docker-Github-Action@v5 + env: + JWT_KEY: ${{secrets.JWT_KEY}} with: name: arquisoft/wiq_en1b/authservice username: ${{ github.actor }} diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 009db707..fba6f97b 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -62,7 +62,7 @@ app.get('/questions/:lang', async (req, res) => { try { const lang = req.params.lang; // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang); + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang.toString()); res.json(questionResponse.data); } catch (error) { @@ -108,4 +108,22 @@ const server = app.listen(port, () => { console.log(`Gateway Service listening at http://localhost:${port}`); }); +function verifyToken(req, res, next) { + // Get the token from the request headers + const token = req.headers['token'] || req.body.token || req.query.token; + + // Verify if the token is valid + jwt.verify(token, (process.env.JWT_KEY??'my-key'), (err, decoded) => { + if (err) { + // Token is not valid + res.status(401).json({ message: 'Invalid token' }); + } else { + // Token is valid + req.decodedToken = decoded; + // Call next() to proceed to the next middleware or route handler + next(); + } + }); +} + module.exports = server diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 026feb5c..76dc4234 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -35,7 +35,7 @@ app.get('/questions/:lang', async (req, res) => { const lang = req.params.lang; const questions = await Question.aggregate([ - {$match: {language : lang}}, //Condition + {$match: {language : lang.toString()}}, //Condition {$sample: {size:5}} //5 random from the ones that fullfil the condition ]); diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 98779572..0bdbf096 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -43,7 +43,7 @@ app.post('/login', async (req, res) => { // Check if the user exists and verify the password if (user && await bcrypt.compare(password, user.password)) { // Generate a JWT token - const token = jwt.sign({ userId: user._id }, 'your-secret-key', { expiresIn: '1h' }); + const token = jwt.sign({ userId: user._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '1h' }); // Respond with the token and user information res.json({ token: token, username: username}); } else { diff --git a/users/recordservice/record-service.js b/users/recordservice/record-service.js index dd383984..dc123b4b 100644 --- a/users/recordservice/record-service.js +++ b/users/recordservice/record-service.js @@ -21,7 +21,7 @@ app.post('/record', async (req, res) => { console.log(user) console.log(game) if(user && game){ - let record = await Record.findOne({ user : user }); + let record = await Record.findOne({ user : user.toString() }); if(record){ //If it exits record.games.push(game); } @@ -47,7 +47,7 @@ app.post('/record', async (req, res) => { app.get('/record/:user', async (req, res) => { try { - const recordFound = await Record.findOne({ user: req.params.user }, 'games'); + const recordFound = await Record.findOne({ user: req.params.user.toString() }, 'games'); if (!recordFound) { res.json({record: "undefined" }); } else { diff --git a/webapp/package-lock.json b/webapp/package-lock.json index 076761a2..6e5b491a 100644 --- a/webapp/package-lock.json +++ b/webapp/package-lock.json @@ -19,6 +19,7 @@ "express": "^4.19.2", "i18n": "^0.15.1", "jquery": "^3.7.1", + "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", "mongoose": "^8.3.0", "react": "^18.2.0", @@ -18497,6 +18498,14 @@ "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/webapp/package.json b/webapp/package.json index 2ea16917..cb732d60 100644 --- a/webapp/package.json +++ b/webapp/package.json @@ -14,6 +14,7 @@ "express": "^4.19.2", "i18n": "^0.15.1", "jquery": "^3.7.1", + "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.2", "mongoose": "^8.3.0", "react": "^18.2.0", diff --git a/webapp/src/App.js b/webapp/src/App.js index 5eefcf70..887abf5a 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -10,12 +10,10 @@ import Container from '@mui/material/Container'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; -import { UserContextProvider } from './components/loginAndRegistration/UserContext'; function App() { return ( -
@@ -31,7 +29,6 @@ function App() {
-
); } diff --git a/webapp/src/components/HistoricalData/HistoricalView.js b/webapp/src/components/HistoricalData/HistoricalView.js index f110430c..05d3515e 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.js +++ b/webapp/src/components/HistoricalData/HistoricalView.js @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import {useTranslation} from "react-i18next"; import HistoryRecordRetriever from './HistoryRecordRetriever'; -import { useUserContext } from '../loginAndRegistration/UserContext'; +import Cookies from 'js-cookie' import RecordList from './RecordList'; @@ -12,13 +12,13 @@ const retriever = new HistoryRecordRetriever(); export default function HistoricalView() { const [records, setRecords]= useState(null); const[t] = useTranslation("global"); - const {user} = useUserContext(); const getRecords = async ()=>{ try { - var jsonRecords = await retriever.getRecords(user.username); - var recordsArray = jsonRecords.games; - setRecords(recordsArray); + let cookie = JSON.parse(Cookies.get('user')) + var jsonRecords = await retriever.getRecords(cookie.username, cookie.token); + var recordsArray = jsonRecords.games; + setRecords(recordsArray); } catch (error) { console.log(error); } diff --git a/webapp/src/components/HistoricalData/HistoricalView.test.js b/webapp/src/components/HistoricalData/HistoricalView.test.js index e224a4ce..e2a5f39d 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.test.js +++ b/webapp/src/components/HistoricalData/HistoricalView.test.js @@ -4,9 +4,9 @@ import i18en from 'i18next'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { MemoryRouter } from 'react-router-dom'; -import { UserContextProvider} from '../loginAndRegistration/UserContext'; import HistoricalView from './HistoricalView'; import { act } from 'react-dom/test-utils'; +import Cookies from 'js-cookie'; const mockAxios = new MockAdapter(axios); i18en.use(initReactI18next).init({ resources: {}, @@ -16,29 +16,27 @@ i18en.use(initReactI18next).init({ } }); global.i18en = i18en; +Cookies.set('user', JSON.stringify({username:"dummy", token:"fasfda"})) describe('Historical View component', () => { beforeEach(() => { mockAxios.reset(); }); it('renders no games if the api is down', () => { - const user = { username: 'dummy' }; - render(); + render(); const text = screen.getByText(i18en.t('historicalView.no_games_played')); expect(text).toBeInTheDocument(); }); it('renders no games if the user has no games', async () => { - const user = { username: 'dummy' }; mockAxios.onGet('http://localhost:8000/record/dummy').reply(200, "undefined"); await act(async()=> - await render() + await render() ) const text = screen.getByText(i18en.t('historicalView.no_games_played')); expect(text).toBeInTheDocument(); }); it('renders Game Record Buttons', async () => { - const user = { username: 'dummy' }; mockAxios.onGet('http://localhost:8000/record/dummy').reply(200, {record : {user: "dummy", games: [{ @@ -53,7 +51,7 @@ describe('Historical View component', () => { }]}}); await act(async()=> - await render() + await render() ) const regex = /\d{1,2}\/\d{1,2}\/\d{4}/; // Expresión regular para el formato de fecha "MM/DD/YYYY" o "M/D/YYYY" const textRegex = new RegExp(regex); diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index d586a867..4732f308 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -7,9 +7,9 @@ class HistoryRecordRetriever{ } - async getRecords(user) { + async getRecords(user, token) { try { - const response = await axios.get(this.apiUrl + '/' + user); + const response = await axios.get(this.apiUrl + '/' + user, {token:token}); const receivedRecords = await response.data; console.log(receivedRecords) console.log(receivedRecords[0]) diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index cddffd1b..b2bee539 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -4,14 +4,13 @@ import MenuItem from '@mui/material/MenuItem'; import Menu from '@mui/material/Menu'; import "../../custom.css"; import { useTranslation } from "react-i18next"; -import { useUserContext } from '../loginAndRegistration/UserContext'; +import Cookies from 'js-cookie'; function Navbar() { const [t, i18n] = useTranslation("global"); const [anchorEl, setAnchorEl] = useState(null); - const { user } = useUserContext(); const handleLanguageMenuOpen = (event) => { setAnchorEl(event.currentTarget); @@ -44,8 +43,9 @@ function Navbar() { changeLanguage("tk")}> {t("navBar.tk")} - {user != null ? ( -

{user.username}

+ + {Cookies.get('user') ? ( +

{ JSON.parse(Cookies.get('user')).username}

) : null}
diff --git a/webapp/src/components/fragments/NavBar.test.js b/webapp/src/components/fragments/NavBar.test.js index 29ceb23f..7fe0137f 100644 --- a/webapp/src/components/fragments/NavBar.test.js +++ b/webapp/src/components/fragments/NavBar.test.js @@ -2,12 +2,10 @@ import { render , screen, fireEvent } from '@testing-library/react'; import { initReactI18next } from 'react-i18next'; import i18en from 'i18next'; import { MemoryRouter } from 'react-router-dom'; - -import { UserContextProvider} from '../loginAndRegistration/UserContext'; import Navbar from './NavBar' +import Cookies from 'js-cookie' import App from '../../App' - i18en.use(initReactI18next).init({ resources: {}, lng: 'en', @@ -16,12 +14,12 @@ i18en.use(initReactI18next).init({ } }); global.i18en = i18en; +Cookies.set('user', JSON.stringify({ username: 'dummy' })) describe('NavBar fragment', () => { it('shows the user name',async () => { - const user = { username: 'dummy' }; - render(); + render(); const text2 = await screen.findByText('dummy') expect(text2).toBeInTheDocument(); // Wait for questions to load diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 2324ecab..9ec557cd 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next"; import axios from 'axios'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import Cookies from 'js-cookie'; const AddUser = () => { diff --git a/webapp/src/components/loginAndRegistration/Login.js b/webapp/src/components/loginAndRegistration/Login.js index d7381d5f..0ad60066 100644 --- a/webapp/src/components/loginAndRegistration/Login.js +++ b/webapp/src/components/loginAndRegistration/Login.js @@ -5,7 +5,7 @@ import "../../custom.css"; import axios from 'axios'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useUserContext } from "./UserContext"; +import Cookies from 'js-cookie'; const Login = () => { const navigate = useNavigate(); @@ -13,13 +13,14 @@ const Login = () => { const { t } = useTranslation("global"); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); - const { setUser } = useUserContext(); const handleSubmit = async (event) => { event.preventDefault(); try { const response = await axios.post(apiUrl, { username, password }); - setUser({username : response.data.username, token : response.data.token}) + let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) + Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) + , {expires:oneHourAfter}); //Used to redirect to the menu to start playing navigate('/menu'); } catch (error) { diff --git a/webapp/src/components/loginAndRegistration/Login.test.js b/webapp/src/components/loginAndRegistration/Login.test.js index edd34e82..2406e721 100644 --- a/webapp/src/components/loginAndRegistration/Login.test.js +++ b/webapp/src/components/loginAndRegistration/Login.test.js @@ -2,7 +2,6 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; import Login from './Login'; import { BrowserRouter as Router } from 'react-router-dom'; -import { UserContextProvider } from './UserContext'; // Mocking useTranslation hook jest.mock('react-i18next', () => ({ @@ -12,11 +11,9 @@ jest.mock('react-i18next', () => ({ describe('', () => { test('renders the Login component', () => { render( - - ); expect(screen.getByText('login.title')).toBeInTheDocument(); diff --git a/webapp/src/components/loginAndRegistration/UserContext.js b/webapp/src/components/loginAndRegistration/UserContext.js deleted file mode 100644 index 95764078..00000000 --- a/webapp/src/components/loginAndRegistration/UserContext.js +++ /dev/null @@ -1,19 +0,0 @@ -import React, { createContext, useContext, useState } from 'react'; - -const UserContext = createContext(); - -export function UserContextProvider({ children, baseUser = null }) { - const [user, setUser] = useState(baseUser); - - return ( - - {children} - - ); -} - -export function useUserContext() { - return useContext(UserContext); -} - - diff --git a/webapp/src/components/questionView/CreationHistoricalRecord.js b/webapp/src/components/questionView/CreationHistoricalRecord.js index 9da8f624..5b7d12c2 100644 --- a/webapp/src/components/questionView/CreationHistoricalRecord.js +++ b/webapp/src/components/questionView/CreationHistoricalRecord.js @@ -34,7 +34,7 @@ class CreationHistoricalRecord{ } - async sendRecord(user) { + async sendRecord(user, token) { const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/record"; const body = { @@ -44,7 +44,8 @@ class CreationHistoricalRecord{ try { const response = await axios.post(apiUrl, body, { headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'token':token } }); diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 28038332..a48dbb82 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -8,9 +8,9 @@ class QuestionGenerator{ } - async generateQuestions(lang) { + async generateQuestions(lang, token) { try { - const response = await axios.get(this.apiUrl + '/' + lang); + const response = await axios.get(this.apiUrl + '/' + lang, {token:token}); const receivedQuestions = await response.data; let i = 0; var questions = []; diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 96d0978c..16160a7a 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -8,21 +8,23 @@ import {useTranslation} from "react-i18next"; import $ from 'jquery'; import RecordList from '../HistoricalData/RecordList'; import ButtonHistoricalData from "../HistoricalData/ButtonHistoricalData"; -import { useUserContext } from '../loginAndRegistration/UserContext'; +import Cookies from 'js-cookie' const creationHistoricalRecord = new CreationHistoricalRecord(); const questionGenerator = new QuestionGenerator(); var points = 0; + function QuestionView(){ const [numQuestion, setnumQuestion] = useState(-1); const [questions, setQuestions] = useState(null); const[t, i18n] = useTranslation("global"); - const {user} = useUserContext(); + const cookie = JSON.parse(Cookies.get('user')) const generateQuestions = async (numQuestion) => { if (numQuestion < 0) { try { - var generatedQuestions = await questionGenerator.generateQuestions(i18n.language); + + var generatedQuestions = await questionGenerator.generateQuestions(i18n.language, cookie.token); setQuestions(generatedQuestions); setnumQuestion(0); } catch (error) { @@ -92,7 +94,7 @@ function QuestionView(){ if(!(numQuestion < questions.length - 1)){ creationHistoricalRecord.setDate(Date.now()); creationHistoricalRecord.setPoints(points); - creationHistoricalRecord.sendRecord(user.username); + creationHistoricalRecord.sendRecord(cookie.username, cookie.token); } }, 1000); diff --git a/webapp/src/components/questionView/QuestionView.test.js b/webapp/src/components/questionView/QuestionView.test.js index 25392c62..cf26e93d 100644 --- a/webapp/src/components/questionView/QuestionView.test.js +++ b/webapp/src/components/questionView/QuestionView.test.js @@ -4,11 +4,10 @@ import i18en from 'i18next'; import QuestionView from './QuestionView'; import { MemoryRouter } from 'react-router-dom'; import { act } from 'react-dom/test-utils'; -import { UserContextProvider} from '../loginAndRegistration/UserContext'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import {configure} from '@testing-library/dom'; - +import Cookies from 'js-cookie' configure({ testIdAttribute: 'data-value', @@ -26,6 +25,7 @@ i18en.use(initReactI18next).init({ } }); global.i18en = i18en; +Cookies.set('user', JSON.stringify({username:"dummy", token:"fasfda"})) describe('Question View component', () => { @@ -34,7 +34,7 @@ describe('Question View component', () => { }); it('shows the no_questions_message as the endpoint does not exist',async () => { - render(); + render(); const text = screen.getByText(i18en.t('questionView.no_questions_message')); expect(text).toBeInTheDocument(); // Wait for questions to load @@ -48,7 +48,7 @@ describe('Question View component', () => { //It gives an error as we are not wrapping it by act, however by doing this we simulate a no questions situation await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); @@ -65,7 +65,7 @@ describe('Question View component', () => { [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); @@ -83,7 +83,7 @@ describe('Question View component', () => { [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); @@ -101,7 +101,7 @@ describe('Question View component', () => { [{question: "What is the population of Oviedo?", answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); From a65f6e5d6f4d5426a8bc920b93a7376bfe2fa2cd Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 17:40:25 +0200 Subject: [PATCH 04/74] Forgot to add the token verifier on some endpoints --- gatewayservice/gateway-service.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index fba6f97b..046158f7 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -47,7 +47,7 @@ app.post('/adduser', async (req, res) => { } }); -app.get('/questions', async (req, res) => { +app.get('/questions', verifyToken, async (req, res) => { try { // Forward the question request to the quetion service @@ -58,7 +58,7 @@ app.get('/questions', async (req, res) => { } }); -app.get('/questions/:lang', async (req, res) => { +app.get('/questions/:lang', verifyToken, async (req, res) => { try { const lang = req.params.lang; // Forward the question request to the quetion service @@ -71,7 +71,7 @@ app.get('/questions/:lang', async (req, res) => { } }); -app.post('/record', async(req, res) => { +app.post('/record', verifyToken, async(req, res) => { try { // Forward the record request to the record service const recordResponse = await axios.post(recordServiceUrl+'/record', req.body); @@ -81,7 +81,7 @@ app.post('/record', async(req, res) => { } }); -app.get('/record/:user', async(req, res)=>{ +app.get('/record/:user', verifyToken, async(req, res)=>{ try { const user = req.params.user; // Forward the record request to the record service From e9ad4778324c0395f8b5c57a193a091bca7ee8d1 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 17:50:33 +0200 Subject: [PATCH 05/74] Added token to the test requests of the gateway service --- gatewayservice/gateway-service.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index bfed664f..98baf495 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -57,7 +57,7 @@ describe('Gateway Service', () => { // Test /questions endpoint it('should forward questions request to question service', async () => { const response = await request(app) - .get('/questions'); + .get('/questions').set('token', 'valorDelToken'); expect(response.statusCode).toBe(200); expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); @@ -66,7 +66,7 @@ describe('Gateway Service', () => { // Test /questions/:lang endpoint it('should forward questions request to question service', async () => { const response = await request(app) - .get('/questions/es'); + .get('/questions/es').set('token', 'valorDelToken'); expect(response.statusCode).toBe(200); expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); @@ -75,7 +75,7 @@ describe('Gateway Service', () => { // Test /record endpoint it('should forward record request to record service', async () => { const response = await request(app) - .post('/record'); + .post('/record').set('token', 'valorDelToken'); expect(response.statusCode).toBe(200); expect(response.body.user).toBe('testuser'); @@ -84,7 +84,7 @@ describe('Gateway Service', () => { // Test /record/:user endpoint it('should forward record request to record service', async () => { const response = await request(app) - .get('/record/testuser'); + .get('/record/testuser').set('token', 'valorDelToken'); expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); From cbb7d358bb9434c335b23ba9519b633240119a72 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 18:05:38 +0200 Subject: [PATCH 06/74] Mocked jwt in gateway tests --- gatewayservice/gateway-service.js | 4 +- gatewayservice/gateway-service.test.js | 15 ++- gatewayservice/package-lock.json | 124 +++++++++++++++++++++++++ gatewayservice/package.json | 1 + 4 files changed, 142 insertions(+), 2 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 046158f7..0a2a7dc3 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -6,7 +6,7 @@ const promBundle = require('express-prom-bundle'); const swaggerUi = require('swagger-ui-express'); const fs = require("fs") const YAML = require('yaml') - +const jwt = require('jsonwebtoken'); const app = express(); const port = 8000; @@ -72,6 +72,7 @@ app.get('/questions/:lang', verifyToken, async (req, res) => { }); app.post('/record', verifyToken, async(req, res) => { + console.log("in") try { // Forward the record request to the record service const recordResponse = await axios.post(recordServiceUrl+'/record', req.body); @@ -114,6 +115,7 @@ function verifyToken(req, res, next) { // Verify if the token is valid jwt.verify(token, (process.env.JWT_KEY??'my-key'), (err, decoded) => { + console.log(decoded) if (err) { // Token is not valid res.status(401).json({ message: 'Invalid token' }); diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 98baf495..3e42091f 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -1,14 +1,21 @@ const request = require('supertest'); const axios = require('axios'); +const jwt = require('jsonwebtoken'); const app = require('./gateway-service'); afterAll(async () => { app.close(); }); + +jest.mock('jsonwebtoken'); + jest.mock('axios'); + + describe('Gateway Service', () => { + // Mock responses from external services axios.post.mockImplementation((url, data) => { if (url.endsWith('/login')) { @@ -33,6 +40,12 @@ describe('Gateway Service', () => { } }); + // Mock the `verify` function of JWT + jwt.verify.mockImplementation((token, secretOrPublicKey, callback) => { + // Assume the token is valid and return the payload + callback(null, "decoded"); + }); + // Test /login endpoint it('should forward login request to auth service', async () => { const response = await request(app) @@ -85,7 +98,7 @@ describe('Gateway Service', () => { it('should forward record request to record service', async () => { const response = await request(app) .get('/record/testuser').set('token', 'valorDelToken'); - + console.log(response) expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); }); diff --git a/gatewayservice/package-lock.json b/gatewayservice/package-lock.json index ca71293c..48729129 100644 --- a/gatewayservice/package-lock.json +++ b/gatewayservice/package-lock.json @@ -14,6 +14,7 @@ "express": "^4.18.2", "express-openapi": "^12.1.3", "express-prom-bundle": "^7.0.0", + "jsonwebtoken": "^9.0.2", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" }, @@ -1600,6 +1601,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1980,6 +1986,14 @@ "esprima": "^4.0.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3438,6 +3452,81 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jsonwebtoken/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -3474,11 +3563,46 @@ "node": ">=8" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", diff --git a/gatewayservice/package.json b/gatewayservice/package.json index 93a9914e..0757ca8f 100644 --- a/gatewayservice/package.json +++ b/gatewayservice/package.json @@ -22,6 +22,7 @@ "cors": "^2.8.5", "express": "^4.18.2", "express-openapi": "^12.1.3", + "jsonwebtoken": "^9.0.2", "express-prom-bundle": "^7.0.0", "swagger-ui-express": "^5.0.0", "yaml": "^2.4.1" From b9efe753a46afd467fb75ce083cb631d36249b1e Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sun, 14 Apr 2024 18:13:06 +0200 Subject: [PATCH 07/74] Added favicon and title to webapp --- webapp/public/favicon.ico | Bin 3870 -> 15406 bytes webapp/src/App.js | 6 +++++- webapp/src/index.js | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/webapp/public/favicon.ico b/webapp/public/favicon.ico index a11777cc471a4344702741ab1c8a588998b1311a..5019a7d6d97bc325ca1e23a4f78d9172773a533c 100644 GIT binary patch literal 15406 zcmeI22T)e$*6;o1-kJNIlSHNWE>bLjNbkK$6T2vgNKq6E78E;*y?|oZXpBdVQKJzx ziCq&ln#8WL_X48Q)SNTFyY`E(5j2`Q^WC{&=9$gj@7~X|+OyVr{%bXA^k0qsSEHs) z8_CtYQO5sn)X23_qekZDb@#R!jT$YJXO509esA8W(IK5ijcnx|`9!{`doGtZ{sn94 z@P~$`eE$n0P*ayc4eek2UA3;f6I^fT$v5(>sR2VB9q4D!D zL-h;@($Xc-I)TYPpRjktG!A9wvT>p%If2H68|cx&&hW(?KYy>x86-SphDS0gC5XvM zo=h7!pUFYVEY4`g{xvNr%4$acb{2GZZTZ^wAsTuNjkBSrQ!05;DV#eoiIaPSDd}rV zQ9oSUtGUZG#UZ*SL(MBhNx49_An*p&$#!r1Yz<;?9E z$%;{F?3g)%!)xb})Y^r3>*m7e&-5$u?_l4GxnrUzyY>}d6;EWzuxxg2oXfI4NnAKm z%6A8LaOu!i7LN3!e?%C8iq2lFukhZXMN4}5xKeiK3^z_6WXIa&e6sFSR%G_%*jHPL zw6W*vflXxin9{AStI+)O_saSi!6D2Vn#JBt8#uUiGiQ(OVRH5`Mn%VS`RE~L^ytF5 zJv+$=X-%Yoaee+p2JLO^D9r50u;@PIIv2BI>NtiKE~QtuIm{U}o6kO+!R-D~433H= zOv~uC?-d=U`PwojYA)krb2+iskC}rUNRFIBaoo2|O^W7|qCiIXNUoFlEBveNFUriE zUfzBTj&bFqnXY7v-%Pi`U(h#hI^CN^5o6hmFawj<^soM2eGSnykaukuJW6z)KZPh$ zb3)~r`uDo)tMU^YQ0$<=2-4K6d#2cagWptpPHjK0{r0B!8$PRB|BX)JH#}7QjJXVd z8H#`UrN2MqW7M|(|MFF_x8FGe6u!jziVgZV)x)caCZR8$ zy>5#QZA*P$XJcwV^XtD8UYlx*&)30M)=jmtpsSfNUhioT&_wGEdqKTk!|ShEUxyHh z8R0tm5_cr_DEv0jrk!SUeBalmcQZ3iXZ2%G&n&u&-|~2`2{A^RLWlCM4xvruJGm+` z>*v^{rsG}f*Zm%(sVy`J9kRZk@Efle$(**I(bG7Jttn|d+_an8!VlTlDVa1UUA9lK zpr5M_evNerk+p-glo%^Rp+V;OS!(MtC>Y!n279lT&`6XwY32I2~b< zcLtYBR&c#=3A1C}*jsGLm2X<}-NrVA=xK?5wCL_+$<+RKs&(sZ^j-W5T|%qMz6=c6 zFn>OC^F|VGs!N8iDIHt&r*Q6l=I7pGp8GBaIVLjP-23Z@?O@9Icu!{b zjbl#da3)4sGbLW1k8`bAIm(X3{mq!%!HDDQJ^7~8n`mn-+}@M5jEo2pdw9G2>+~F^ z--K{oU6H#v9Svg1wC&8UEu(l;Ih}hq`*QVgJFXlJ=lGV^OwBfB+5i)d?`Xm0;}TE5 zP2j=J93Iu=vVT_qJsc9qw8_O=*&o8!yVeiYFl14_JD(N@(8)Sf=0D2>_gVNDTC!u~ z42~U`$F}tWoIU7GPN*^S!`+$U?nb{@T`r%^;o$z!96!34J=>-eW};7+z9mCjO(Uau z7EyW@gvnldnV-DD|AfdMOKfRIHy2wXZ5` zPCqW4IL)Q0qlj&xPcL6HCi#0XC(@1a-VRJoj9|~yk#sa|PExaYB6ai#mGRc~MMq(} zVrQ*e5ooAICucLx?Oe_I%}Yd&TAVmq$c-Z#iO@5lAiEFGo}A{)?2(dp=(DzPIFIig zr2Nje+&H?0vWt6oa{mM)J4X_3rp3{2JtHRcOXAVPFS$4)pCm6&YK4|#J2!CX)1^GGxyRGm z8|*8Y!sIkZ_AYlP(bkF(?K@9P zW10DaoOs{QGG+hl>js+0CA!G*iNi%|!Qa^M7?dDcanUEh|;b2G8_jtuPBhUB-iGYNHVjQ_%lr{`D5 zV~(-J|0ZIvmdh1|3}7IPok%9Tk(~{8JXnI z(cQa9_7AAr^DkxnCi_$G4Y^7V5UQ(1uONTc&7ZIG2N9x^ANKa*{=-BrA91BepbuS# zouX_1GlUxVWK8lwisqG5{qoeHkpZQj=@%HtVd`-y(0>rPkGq)zn z%#I`vPZC?$FtCF&U0N8EGU7|7oU0+{bS+5(_K1ufXzv+IOfzSRdwN6~D|mu7ezNC7 zgr9(yyel-keSMV<8oaO26eGSN$<0OV(})3sN3!;AB@-@I(QjlSfnqah{(i(*SiRu4 zPT%U>Q+=(I#k<#kU1q|A$RoC88_D+##7~>if5aFD=1(Wq!dT*J9af@X25Ws&Khd{uIG)p_E@ zysGonpRqM%odRduLnwY@q$Ot+M4(R_+O?>1G zrr>o!Dy&kSBgovU{6ywc?TY+nG56Q* zq;|%~+!!{QLU%7BbQ)8bV8(_K7J?ZGKHWs*AUr6TXsA{l?ZJvpU+7h>m-;+3py!?Y zSHW9<9>Jo6P;IgACOY_hpi4WW5c0!HnHzVJ5|=~dTMcGbXlJf0T0_~k-8?Q{$AkP5 zrbmP`sl5sNip)6qFKf0pXoutg8f?KCv&?C*6rRzgPv`MGAFN`;*uxKjK9f>Hs3L+W=;%<_?#)l70g)hfTFmo zEObA>P@BQbk4s@mVmAsqm~(Wo1s6(f1V`|otlW+LpSB{+Tbr)UjRZ@24-bvTf_?dO zY;SJ{$GH<>U?yj;rf>@Mk9$ z4s&MLJP!u>8`9azjRDaM7(JnsqQN_v?7NoHuK7gkSrKcd$-MqHl$JDS&n6eX`O1gS zmphOhW=NKY0i{zM`Dk1#qAbM^NbNnw&Vt?nfyB3LrlPHWeL(R|Z#%qIiQ`ABf>4+j{qDoc;0nFef|)`AD;Vz_oXlvPE7;Tbm;JXZF^2gV|M zMfY|3uP@`b(;uwZob(ceD1J@ZM~zJh(J>Jnw;)a@oaAOPOdHgW2e%gT_;CRh4>P!a zCWebUJ-E8lo%6d~x$sp>zFsFXUt-UWC3f<^Js0;|%W&cRZeK3$@#5C01i>P+c=R-d zn|Bf@n&?EwW(lO*55)I93w#?(%}8`#SF3q7CcK?~#g~OOHl~xk7L(GO)1$4j7mVp> z63-;x4HUWli{8x>C>YwEdk;RLvU(#=%9jX!lg-`ReYkfqhSKFuWOy0S-QIxq_ImX6 z(`WlS2kuSV+l4Pr-yN>0+JCbg#{1S!EOOzLCTTrEagp z0>#c>8H(MjM?K#pxt5NW#1b>cba&wXrF52$^CjBci7`F5F}LU;GZI!4rQu8~`93c# zp0D?;WW}5`it<~qqR5`pyIYeLq0e|f7q%yd^LfW;`bxcOcy}#MABki8)*-A~n#sRb z4CmmUr4$bCioc!)vBpM3=t{qbq4a`yt|i?zix9#5J6T%S;p2j%yrfx8yK-$nzw%Cq z#MO4q^qAZ?oK6mc?^#$fV%#c*E%{n-Cm)8m+LK_akJ~?+(B}Qdq_k*3j=^1In3J z3MbB3@Nv7obT;iEHA-EX(@110`2MRjzxsbe`W4%kX9{i|qRdV4Hqam^)?R#@4u_Xbrn3AL-^|RDeWuHx?)Kcf z^BE6s9br*MXS|zg64T0-dsn{W`Q!6k5?-hHx)Gyq!lzQpDZhD$%7=$}SoS@S@1Nwx z4?CrY=4+l+oucgOal!TD7~9Q}P(58bwRK~3iu8}@DS4t2&)&d81Ns$gP|2ZGxuy6p zCEpy;!-j7UbfcTZ#Z$Xy$XX}3v}HBvR@TgrT1?fgL)6?l%j&+Vk|&G&?X5X~W)anu z2e`gw0n?>cmKhmIZP^*A2c6`{Ltk-jzwG@xr==cIR}-qdeTvmXGU;Nk!?Di-Slq8U zu?8Y{*%zTI8g=A6^lRv^dnZtImgeJ2T3~?0tQhefvq)~|z_lxj_^DRtxP6wD?ZQ;O zN6(*JkQ&nWEYImJIg!-TWk1}$v5VT8AGs#3%N zx7SJT=H*Uq?^K>Wy-w}D6O3*reMw14e6e{W$B!SBn%H&G;ZZjAO(xPpo8>d(8QImB zZL3Q-d0-!X{DZ`HWKX`(Lp?v#z<$*6S&@IR?0tW!83)N;4v_nC*@^u7r_)lCyUEk1 zH@Lj7l(I`lsd;=$?r(E&(+a7X7!zyPid&+eXJQ+7*Ue*wuOqpAQmB1;m#TXgC} z8|iCk{a#b@veTu;bd`#WyZAh(lbn6TPQ+(K=$Vr%JZ@OHkPbF2#GYlZs?MMd{AdIE zRlN}+`v^Iwq_}y|#mRvvBXiM-5z~hC=DE;$L+Xe(FCF2b=)C;aF}~UM36EN!gi2sq=OIjM&9zUSs?iI$zh2r&(4{(&4V1EA$q5p=eF1sbW z8*vu;j7_qoy@BKb#!_o{7TjJ{CsDBFciT_h8BSub_{^AQ)_go|DuWXeh&C~yblo`q z^SnxGSNl10>Pm!d8@3-7+pnnR%G&u%^KoW)uXL_f zRZ@NXDx-vcZ>cA?{claiXJ_&6+Q&S;ah5H8dy-(M#h%q3%*(WvnzfnW{?-kAi~I|HT*&K_EH%>mJiT|7(J|q;|3g!3s3{``Wbyb}4Heff z@OgSK5*>^=e>_L(%&mwswIa(eNaX|7*nrx;U+#eh_M?)&(69XNE4?B!h7TtrG=_Kw zYkoYrou`$xe7ld_c9-$L9=7AOh{172}7?zNn<)>S;Enr${cLP2Y&Nr1<%X{I$epUhS{= zb@rn=kBT0WJ-vvLo`Xo~`N;D3=C0%e)m2rjo;;r3;o&@a_&{ub3yJd1tt%JBo~kKb zFq0r1L)sgAGte`a;cc>rY$9jH4jn0f^hoODHC!p(#Q7z~{II-)n}_!Cy!sipFa1bf zm=|GY66-Y$2{yA7f1|`vh2~es+wDiGX({vviTzJVOQ&~exa=PTRo%MgNfi~h?=VVm z5jPD};@d<@%`8s(#Y|W+sX*-b8PyN(kl^CYkc`DlTz8lJk~5^b4aY;%irJ&b^WZ@l zk7N&D+V{ECzH9i;v*+BqU%~9LW6AV3rl@xd+8f$RZ$+r&iN+GUgx=pkzxrLp*LG~_ zAbG!q_#aL3lM?xK$s*R4%p@bwm#)Tf6tvmNEbs5>V3Nq>%yiZ)TF9D`c|^DcFk#zS zR$Tsx@n2QYCv6Q0@=hZ+y`q}htjtQjz7ENd>@6F#mH?4K&VGPz9k{+5*HIg-y9lM^Gk$8cxy z5zX1Y)Skj56LP)vC`z#m=_E)L>Xb3_YCMsxYkx>3e$FQ~OfRZ@2$f^`Y2M zfaIf*7DjZimGh9HhUAgbEACe+y)Rc-IC&rG4qX`;;LI0Gy|{Z#@}!G$KK#as%|$H< zG}p&Vqcy|2tY_NFau!K{+uX?WEOFY%V5zUCdfM^j)_#(!j^Nl1SLP0G$FXf&Sv_qo z@e*@G6%64I$Y0?_IsdmaF{ZD7Yl2OU$p~skFNbbYfB%9QmmqTdOxU~Hn}_AysjQ9W z$s>2^18c?D9+t#;^&_s;FzIbhVMywC%w1N?k|B3l;(MKWj$7zumOy(a8xE9CVoI(D z9on?ylVwX8pOZs~iS)(1l=;tg{VxCi+BZUfh~z)v#u|)GPv_+RgQWO+5F)+X!Ge*` z>@EF)cY0A?)t#~j!Q4F7n(65xJNGnt47y6!v?~NRA4Y;@Z}O9mP`tE?IsNaddKz+V z^8_Ohy-I$PDsd{#(pGvL{7H0ls@u0p&hr`%zutaS@|W+G+)u#)5?$Sxmp_I`a~q;e zZ0P3TLPASZ))po6<)&bEtZ&2AWP2jqd(&;y59I8T9)Pba=rZ6eVfNW{GD%`^_mvDS z*viP5g(T{FNk4)fQ5I%&bZSkkiS!7F-wu>~CtUo8>KyV1=~wbOKjloO!ir@tNdHB! z#Fa!l2Zkhez(>xDU0XVm8sH~=ShfVqSv6_EDhiKQQ+%U}q355Iy|0Gu85{6vYC(j4 zbK1N5sQTrDl{&wi*&`*+*WpxkeHDH}v*Q2!<*MZU^>y&uEaoA9OTT@~H* za&@P-pO4^aUXo`U(zovrR$sWy*7D~}xKz#f)A#A0J4x^yIgeYKGe2)Qsa{@ndsB^X z%9&N^15@Ty(_EMD|8e^3^WShj!N3I{jhBASAkll6?1^?ZEg6`XNAdYv%=n>#{$r;S z<0$?}IY%fyMb7fd|7tqlHGlozf4ltafB!oD{$dB6T@-m5s?Pplg1rU{#+y5Q48z9H zkX*)sM2Q{BJim|TSJ(XW=~u-$Wsivbm40%iCn!XC36S%Dq|OT*SbFMJ=O?l2m)F;9 z`&BxB>Hj~U{yLwm&?$3^{i<_Dg%70`r0hkN+!b4Yb^MQ^|CeO-S3LhK&}efdj!(BFT5OW=1);Qs=Fyp?JI literal 3870 zcma);c{J4h9>;%nil|2-o+rCuEF-(I%-F}ijC~o(k~HKAkr0)!FCj~d>`RtpD?8b; zXOC1OD!V*IsqUwzbMF1)-gEDD=A573Z-&G7^LoAC9|WO7Xc0Cx1g^Zu0u_SjAPB3vGa^W|sj)80f#V0@M_CAZTIO(t--xg= z!sii`1giyH7EKL_+Wi0ab<)&E_0KD!3Rp2^HNB*K2@PHCs4PWSA32*-^7d{9nH2_E zmC{C*N*)(vEF1_aMamw2A{ZH5aIDqiabnFdJ|y0%aS|64E$`s2ccV~3lR!u<){eS` z#^Mx6o(iP1Ix%4dv`t@!&Za-K@mTm#vadc{0aWDV*_%EiGK7qMC_(`exc>-$Gb9~W!w_^{*pYRm~G zBN{nA;cm^w$VWg1O^^<6vY`1XCD|s_zv*g*5&V#wv&s#h$xlUilPe4U@I&UXZbL z0)%9Uj&@yd03n;!7do+bfixH^FeZ-Ema}s;DQX2gY+7g0s(9;`8GyvPY1*vxiF&|w z>!vA~GA<~JUqH}d;DfBSi^IT*#lrzXl$fNpq0_T1tA+`A$1?(gLb?e#0>UELvljtQ zK+*74m0jn&)5yk8mLBv;=@}c{t0ztT<v;Avck$S6D`Z)^c0(jiwKhQsn|LDRY&w(Fmi91I7H6S;b0XM{e zXp0~(T@k_r-!jkLwd1_Vre^v$G4|kh4}=Gi?$AaJ)3I+^m|Zyj#*?Kp@w(lQdJZf4 z#|IJW5z+S^e9@(6hW6N~{pj8|NO*>1)E=%?nNUAkmv~OY&ZV;m-%?pQ_11)hAr0oAwILrlsGawpxx4D43J&K=n+p3WLnlDsQ$b(9+4 z?mO^hmV^F8MV{4Lx>(Q=aHhQ1){0d*(e&s%G=i5rq3;t{JC zmgbn5Nkl)t@fPH$v;af26lyhH!k+#}_&aBK4baYPbZy$5aFx4}ka&qxl z$=Rh$W;U)>-=S-0=?7FH9dUAd2(q#4TCAHky!$^~;Dz^j|8_wuKc*YzfdAht@Q&ror?91Dm!N03=4=O!a)I*0q~p0g$Fm$pmr$ zb;wD;STDIi$@M%y1>p&_>%?UP($15gou_ue1u0!4(%81;qcIW8NyxFEvXpiJ|H4wz z*mFT(qVx1FKufG11hByuX%lPk4t#WZ{>8ka2efjY`~;AL6vWyQKpJun2nRiZYDij$ zP>4jQXPaP$UC$yIVgGa)jDV;F0l^n(V=HMRB5)20V7&r$jmk{UUIe zVjKroK}JAbD>B`2cwNQ&GDLx8{pg`7hbA~grk|W6LgiZ`8y`{Iq0i>t!3p2}MS6S+ zO_ruKyAElt)rdS>CtF7j{&6rP-#c=7evGMt7B6`7HG|-(WL`bDUAjyn+k$mx$CH;q2Dz4x;cPP$hW=`pFfLO)!jaCL@V2+F)So3}vg|%O*^T1j>C2lx zsURO-zIJC$^$g2byVbRIo^w>UxK}74^TqUiRR#7s_X$e)$6iYG1(PcW7un-va-S&u zHk9-6Zn&>T==A)lM^D~bk{&rFzCi35>UR!ZjQkdSiNX*-;l4z9j*7|q`TBl~Au`5& z+c)*8?#-tgUR$Zd%Q3bs96w6k7q@#tUn`5rj+r@_sAVVLqco|6O{ILX&U-&-cbVa3 zY?ngHR@%l{;`ri%H*0EhBWrGjv!LE4db?HEWb5mu*t@{kv|XwK8?npOshmzf=vZA@ zVSN9sL~!sn?r(AK)Q7Jk2(|M67Uy3I{eRy z_l&Y@A>;vjkWN5I2xvFFTLX0i+`{qz7C_@bo`ZUzDugfq4+>a3?1v%)O+YTd6@Ul7 zAfLfm=nhZ`)P~&v90$&UcF+yXm9sq!qCx3^9gzIcO|Y(js^Fj)Rvq>nQAHI92ap=P z10A4@prk+AGWCb`2)dQYFuR$|H6iDE8p}9a?#nV2}LBCoCf(Xi2@szia7#gY>b|l!-U`c}@ zLdhvQjc!BdLJvYvzzzngnw51yRYCqh4}$oRCy-z|v3Hc*d|?^Wj=l~18*E~*cR_kU z{XsxM1i{V*4GujHQ3DBpl2w4FgFR48Nma@HPgnyKoIEY-MqmMeY=I<%oG~l!f<+FN z1ZY^;10j4M4#HYXP zw5eJpA_y(>uLQ~OucgxDLuf}fVs272FaMxhn4xnDGIyLXnw>Xsd^J8XhcWIwIoQ9} z%FoSJTAGW(SRGwJwb=@pY7r$uQRK3Zd~XbxU)ts!4XsJrCycrWSI?e!IqwqIR8+Jh zlRjZ`UO1I!BtJR_2~7AbkbSm%XQqxEPkz6BTGWx8e}nQ=w7bZ|eVP4?*Tb!$(R)iC z9)&%bS*u(lXqzitAN)Oo=&Ytn>%Hzjc<5liuPi>zC_nw;Z0AE3Y$Jao_Q90R-gl~5 z_xAb2J%eArrC1CN4G$}-zVvCqF1;H;abAu6G*+PDHSYFx@Tdbfox*uEd3}BUyYY-l zTfEsOqsi#f9^FoLO;ChK<554qkri&Av~SIM*{fEYRE?vH7pTAOmu2pz3X?Wn*!ROX ztd54huAk&mFBemMooL33RV-*1f0Q3_(7hl$<#*|WF9P!;r;4_+X~k~uKEqdzZ$5Al zV63XN@)j$FN#cCD;ek1R#l zv%pGrhB~KWgoCj%GT?%{@@o(AJGt*PG#l3i>lhmb_twKH^EYvacVY-6bsCl5*^~L0 zonm@lk2UvvTKr2RS%}T>^~EYqdL1q4nD%0n&Xqr^cK^`J5W;lRRB^R-O8b&HENO||mo0xaD+S=I8RTlIfVgqN@SXDr2&-)we--K7w= zJVU8?Z+7k9dy;s;^gDkQa`0nz6N{T?(A&Iz)2!DEecLyRa&FI!id#5Z7B*O2=PsR0 zEvc|8{NS^)!d)MDX(97Xw}m&kEO@5jqRaDZ!+%`wYOI<23q|&js`&o4xvjP7D_xv@ z5hEwpsp{HezI9!~6O{~)lLR@oF7?J7i>1|5a~UuoN=q&6N}EJPV_GD`&M*v8Y`^2j zKII*d_@Fi$+i*YEW+Hbzn{iQk~yP z>7N{S4)r*!NwQ`(qcN#8SRQsNK6>{)X12nbF`*7#ecO7I)Q$uZsV+xS4E7aUn+U(K baj7?x%VD!5Cxk2YbYLNVeiXvvpMCWYo=by@ diff --git a/webapp/src/App.js b/webapp/src/App.js index 5eefcf70..46edf703 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React,{ useEffect } from 'react'; import QuestionView from './components/questionView/QuestionView'; import GameMenu from './components/GameMenu/GameMenu'; import Navbar from './components/fragments/NavBar'; @@ -12,7 +12,11 @@ import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; import { UserContextProvider } from './components/loginAndRegistration/UserContext'; + function App() { + useEffect(() => { + document.title = 'WIQ'; + }, []); return ( diff --git a/webapp/src/index.js b/webapp/src/index.js index 409a8681..05cbd873 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -33,6 +33,10 @@ i18next.init({ const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + + + + {/* envolviendo la app con el sistema de traducciones */} From 32b7a628dc7363516c095625e31d5fc01b8f8818 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 18:19:16 +0200 Subject: [PATCH 08/74] Added test to check no token requests --- gatewayservice/gateway-service.js | 3 ++- gatewayservice/gateway-service.test.js | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 0a2a7dc3..57f9ac0b 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -118,7 +118,8 @@ function verifyToken(req, res, next) { console.log(decoded) if (err) { // Token is not valid - res.status(401).json({ message: 'Invalid token' }); + res.status(403).json({authorized: false, + error: 'Invalid token or outdated'}); } else { // Token is valid req.decodedToken = decoded; diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 3e42091f..ca42bc32 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -14,7 +14,7 @@ jest.mock('axios'); -describe('Gateway Service', () => { +describe('Gateway Service with token mock', () => { // Mock responses from external services axios.post.mockImplementation((url, data) => { @@ -40,6 +40,8 @@ describe('Gateway Service', () => { } }); + + // Mock the `verify` function of JWT jwt.verify.mockImplementation((token, secretOrPublicKey, callback) => { // Assume the token is valid and return the payload @@ -102,4 +104,17 @@ describe('Gateway Service', () => { expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); }); -}); \ No newline at end of file + + +}); + +describe('Gateway Service without token mock', () => { + // Test /record/:user endpoint + it('should not verify the token', async () => { + const response = await request(app) + .get('/record/testuser'); + console.log(response) + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('record', "undefined"); + }); +}) \ No newline at end of file From dd1c1fe8f498afad981e9c1b3d0b6c8ff6a6578e Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 18:25:02 +0200 Subject: [PATCH 09/74] Removed unused import --- webapp/src/components/loginAndRegistration/AddUser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 9ec557cd..2324ecab 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -5,7 +5,6 @@ import { useTranslation } from "react-i18next"; import axios from 'axios'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import Cookies from 'js-cookie'; const AddUser = () => { From 24931ad9fdc1f29bf986d4d1c49c6ee11345bf3a Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 19:41:33 +0200 Subject: [PATCH 10/74] Added logout functionality --- gatewayservice/gateway-service.js | 1 - .../HistoricalData/HistoryRecordRetriever.js | 2 +- webapp/src/components/fragments/NavBar.js | 35 ++++++++++++++++--- .../questionView/CreationHistoricalRecord.js | 6 +--- .../questionView/QuestionGenerator.js | 3 +- webapp/src/translations/en/global.json | 3 +- webapp/src/translations/es/global.json | 3 +- webapp/src/translations/tk/global.json | 3 +- 8 files changed, 40 insertions(+), 16 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 57f9ac0b..745e428b 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -115,7 +115,6 @@ function verifyToken(req, res, next) { // Verify if the token is valid jwt.verify(token, (process.env.JWT_KEY??'my-key'), (err, decoded) => { - console.log(decoded) if (err) { // Token is not valid res.status(403).json({authorized: false, diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index 4732f308..dd566cef 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -9,7 +9,7 @@ class HistoryRecordRetriever{ async getRecords(user, token) { try { - const response = await axios.get(this.apiUrl + '/' + user, {token:token}); + const response = await axios.get(this.apiUrl + '/' + user, { headers : {'token':token}}); const receivedRecords = await response.data; console.log(receivedRecords) console.log(receivedRecords[0]) diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index b2bee539..2609183d 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -4,13 +4,15 @@ import MenuItem from '@mui/material/MenuItem'; import Menu from '@mui/material/Menu'; import "../../custom.css"; import { useTranslation } from "react-i18next"; +import { useNavigate } from 'react-router-dom'; import Cookies from 'js-cookie'; function Navbar() { - + const navigate = useNavigate(); const [t, i18n] = useTranslation("global"); - const [anchorEl, setAnchorEl] = useState(null); + const [anchorEl, setAnchorEl] = useState(); + const [anchorUser, setAnchorUser] = useState(); const handleLanguageMenuOpen = (event) => { setAnchorEl(event.currentTarget); @@ -20,6 +22,19 @@ function Navbar() { setAnchorEl(null); }; + const handleUserMenuOpen = (event) => { + setAnchorUser(event.currentTarget); + }; + + const handleUserMenuClose = () => { + setAnchorUser(null); + }; + + const removeCookie = () => { + Cookies.remove('user'); + navigate('/home') + }; + const changeLanguage = (language) => { i18n.changeLanguage(language); handleLanguageMenuClose(); @@ -43,10 +58,20 @@ function Navbar() { changeLanguage("tk")}> {t("navBar.tk")} - + {Cookies.get('user') ? ( -

{ JSON.parse(Cookies.get('user')).username}

- ) : null} + <> + + + removeCookie()}> {t("navBar.logout")} + + + ) : null} + ); diff --git a/webapp/src/components/questionView/CreationHistoricalRecord.js b/webapp/src/components/questionView/CreationHistoricalRecord.js index 5b7d12c2..a07aac31 100644 --- a/webapp/src/components/questionView/CreationHistoricalRecord.js +++ b/webapp/src/components/questionView/CreationHistoricalRecord.js @@ -49,11 +49,7 @@ class CreationHistoricalRecord{ } }); - if (!response.ok) { - throw new Error('Error al enviar el registro'); - } - - const data = await response.json(); + const data = await response.data; console.log(data); } catch (error) { console.error('Error:', error); diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index a48dbb82..bd7d14d2 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -10,7 +10,8 @@ class QuestionGenerator{ async generateQuestions(lang, token) { try { - const response = await axios.get(this.apiUrl + '/' + lang, {token:token}); + const response = await axios.get(this.apiUrl + '/' + lang, {headers : {'token':token}}); + console.log(response) const receivedQuestions = await response.data; let i = 0; var questions = []; diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 8f79adda..f04a6b86 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -16,7 +16,8 @@ "en": "English", "es": "Spanish", "tk": "Turkish", - "language": "Language" + "language": "Language", + "logout":"Log Out" }, "instructions": { "title": "WIQ Instructions", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 410eebfa..9a6d1428 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -17,7 +17,8 @@ "en": "Inglés", "es": "Español", "tk": "Turco", - "language": "Idioma" + "language": "Idioma", + "logout":"Desconectarse" }, "instructions": { diff --git a/webapp/src/translations/tk/global.json b/webapp/src/translations/tk/global.json index 5a17d359..064da5dd 100644 --- a/webapp/src/translations/tk/global.json +++ b/webapp/src/translations/tk/global.json @@ -16,7 +16,8 @@ "en": "İngilizce", "es": "İspanyolca", "tk": "Türkçe", - "language": "Dil" + "language": "Dil", + "logout":"Log Out" }, "instructions": { "title": "WIQ Talimatları", From 690c90a3681bb1031ff9e92b0c5c782f808cc817 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sun, 14 Apr 2024 20:27:31 +0200 Subject: [PATCH 11/74] Added one back button --- webapp/src/components/GameMenu/GameMenu.js | 6 ++++-- .../src/components/HistoricalData/HistoricalView.js | 11 ++++++++++- webapp/src/translations/en/global.json | 3 ++- webapp/src/translations/es/global.json | 3 ++- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/webapp/src/components/GameMenu/GameMenu.js b/webapp/src/components/GameMenu/GameMenu.js index 7f7bd957..04c08f45 100644 --- a/webapp/src/components/GameMenu/GameMenu.js +++ b/webapp/src/components/GameMenu/GameMenu.js @@ -9,8 +9,8 @@ export default function GameMenu() { return (

{t("gameMenu.title")}

- +
); } @@ -22,4 +22,6 @@ export default function GameMenu() {

{t("gameMenu.new_game_button")}

); - } \ No newline at end of file + } + + \ No newline at end of file diff --git a/webapp/src/components/HistoricalData/HistoricalView.js b/webapp/src/components/HistoricalData/HistoricalView.js index f110430c..c9415240 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.js +++ b/webapp/src/components/HistoricalData/HistoricalView.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import {useTranslation} from "react-i18next"; import HistoryRecordRetriever from './HistoryRecordRetriever'; import { useUserContext } from '../loginAndRegistration/UserContext'; - +import { Link } from "react-router-dom"; import RecordList from './RecordList'; @@ -29,6 +29,7 @@ export default function HistoricalView() { return (
+ {(records && records.length !== 0) ? records.map((record, index) => ( )):

{t("historicalView.no_games_played")}

} @@ -53,4 +54,12 @@ function HistoricalGameElement({record,t}){
); +} + +function BackButton({t}){ + return( + +

⬅️{t("gameMenu.back")}

+ + ); } \ No newline at end of file diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 8f79adda..70c3da87 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -55,7 +55,8 @@ "gameMenu":{ "history_button":"View Historical Data", "new_game_button":"Create New Game", - "title":"Game Menu" + "title":"Game Menu", + "back":"Back" }, "questionView":{ "seconds":"seconds", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 410eebfa..8c878915 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -59,7 +59,8 @@ "gameMenu":{ "history_button":"Ver Historial", "new_game_button":"Crear nuevo juego", - "title":"Menú del Juego" + "title":"Menú del Juego", + "back":"Atrás" },"questionView":{ "seconds":"segundos", "question_counter":"Pregunta nº ", From 1710458f9be1cf46b4e435dbd6f28feff8e62dc5 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sun, 14 Apr 2024 21:06:05 +0200 Subject: [PATCH 12/74] Added back Buttons to GameMenu and Mocks --- .../HistoricalData/HistoricalView.js | 13 +--- .../HistoricalData/HistoryRecordRetriever.js | 78 ++++++++++++++++--- .../fragments/BackButtonToGameMenu.js | 9 +++ .../questionView/QuestionGenerator.js | 27 ++++++- .../components/questionView/QuestionView.js | 2 + webapp/src/custom.css | 19 +++++ 6 files changed, 126 insertions(+), 22 deletions(-) create mode 100644 webapp/src/components/fragments/BackButtonToGameMenu.js diff --git a/webapp/src/components/HistoricalData/HistoricalView.js b/webapp/src/components/HistoricalData/HistoricalView.js index c9415240..9812acc7 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.js +++ b/webapp/src/components/HistoricalData/HistoricalView.js @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import {useTranslation} from "react-i18next"; import HistoryRecordRetriever from './HistoryRecordRetriever'; import { useUserContext } from '../loginAndRegistration/UserContext'; -import { Link } from "react-router-dom"; +import BackButtonToGameMenu from '../fragments/BackButtonToGameMenu'; import RecordList from './RecordList'; @@ -16,7 +16,7 @@ export default function HistoricalView() { const getRecords = async ()=>{ try { - var jsonRecords = await retriever.getRecords(user.username); + var jsonRecords = await retriever.getRecords("user"); var recordsArray = jsonRecords.games; setRecords(recordsArray); } catch (error) { @@ -29,7 +29,7 @@ export default function HistoricalView() { return (
- + {(records && records.length !== 0) ? records.map((record, index) => ( )):

{t("historicalView.no_games_played")}

} @@ -56,10 +56,3 @@ function HistoricalGameElement({record,t}){ ); } -function BackButton({t}){ - return( - -

⬅️{t("gameMenu.back")}

- - ); -} \ No newline at end of file diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index d586a867..c240853e 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -8,18 +8,74 @@ class HistoryRecordRetriever{ } async getRecords(user) { - try { - const response = await axios.get(this.apiUrl + '/' + user); - const receivedRecords = await response.data; - console.log(receivedRecords) - console.log(receivedRecords[0]) - return receivedRecords.record; - } catch (error) { - console.log(error) - throw new Error(error); - - } + return { + userId: user, + games: [ + { + questions: [ + { + question: "¿Cuál es la capital de Francia?", + answers: ["Madrid", "París", "Londres", "Roma"], + answerGiven: "París", + correctAnswer: "París" + }, + { + question: "¿En qué año comenzó la Segunda Guerra Mundial?", + answers: ["1939", "1945", "1914", "1941"], + answerGiven: "1939", + correctAnswer: "1939" + }, + { + question: "¿Quién escribió 'Don Quijote de la Mancha'?", + answers: ["Miguel de Cervantes", "Gabriel García Márquez", "Federico García Lorca", "Jorge Luis Borges"], + answerGiven: "Miguel de Cervantes", + correctAnswer: "Miguel de Cervantes" + } + ], + points: 3000, + date: "01/02/24" + }, + { + questions: [ + { + question: "¿Cuál es el río más largo del mundo?", + answers: ["Nilo", "Amazonas", "Yangtsé", "Misisipi"], + answerGiven: "Amazonas", + correctAnswer: "Amazonas" + }, + { + question: "¿Cuál es el elemento más abundante en la corteza terrestre?", + answers: ["Hierro", "Oxígeno", "Silicio", "Aluminio"], + answerGiven: "Oxígeno", + correctAnswer: "Oxígeno" + } + ], + points: 2500, + date: "02/02/24" + }, + { + questions: [ + { + question: "¿Quién pintó la Mona Lisa?", + answers: ["Leonardo da Vinci", "Pablo Picasso", "Vincent van Gogh", "Rembrandt"], + answerGiven: "Leonardo da Vinci", + correctAnswer: "Leonardo da Vinci" + }, + { + question: "¿Cuál es el planeta más grande del sistema solar?", + answers: ["Júpiter", "Saturno", "Neptuno", "Urano"], + answerGiven: "Júpiter", + correctAnswer: "Júpiter" + } + ], + points: 3500, + date: "03/02/24" + } + ] + }; } + + } diff --git a/webapp/src/components/fragments/BackButtonToGameMenu.js b/webapp/src/components/fragments/BackButtonToGameMenu.js new file mode 100644 index 00000000..e9bab12c --- /dev/null +++ b/webapp/src/components/fragments/BackButtonToGameMenu.js @@ -0,0 +1,9 @@ +import { Link } from "react-router-dom"; + +export default function BackButton({t}){ + return( + +

⬅ {t("gameMenu.back")}

+ + ); + } \ No newline at end of file diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 28038332..ff9058de 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,6 +9,31 @@ class QuestionGenerator{ } async generateQuestions(lang) { + try { + //const response = await fetch(this.apiUrl); + //const receivedQuestions = await response.json(); + + //Mockup + const receivedQuestions = JSON.parse('{"0":{"question":"¿Cuál es la población de Oviedo?","answers":["225089","191325","220587","121548"]},'+ + '"1":{"question":"¿Cuál es la población de Gijón?","answers":["275274","159658","233982","305554"]},'+ + '"2":{"question":"¿Cuál es la población de Avilés?","answers":["82568","115595","41284","122200"]},'+ + '"3":{"question":"¿Cuál es la capital de Asturias?","answers":["Ciudad de Oviedo","a","b","c"]},'+ + '"4":{"question":"¿Cuál es la capital de España?","answers":["Madrid","a","b","c"]},'+ + '"5":{"question":"¿Cuál es la capital de Turquía?","answers":["Ankara","a","b","c"]}}') + + let i = 0; + var questions = []; + for (const key in receivedQuestions) { + questions[i] = new Question(receivedQuestions[key]); + i += 1; + } + console.log(questions); + return questions; + } catch (error) { + throw new Error(error); + } + + /* try { const response = await axios.get(this.apiUrl + '/' + lang); const receivedQuestions = await response.data; @@ -21,7 +46,7 @@ class QuestionGenerator{ return questions; } catch (error) { throw new Error(error); - } + }*/ } } diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 96d0978c..cf4ea79c 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -9,6 +9,7 @@ import $ from 'jquery'; import RecordList from '../HistoricalData/RecordList'; import ButtonHistoricalData from "../HistoricalData/ButtonHistoricalData"; import { useUserContext } from '../loginAndRegistration/UserContext'; +import BackButtonToGameMenu from '../fragments/BackButtonToGameMenu'; const creationHistoricalRecord = new CreationHistoricalRecord(); const questionGenerator = new QuestionGenerator(); @@ -146,6 +147,7 @@ function QuestionComponent({questions, numQuestion, handleClick, t, points}){ ) : ( <>

{t("questionView.finished_game")}

+

{points} {t("questionView.point")}

    < RecordList record={creationHistoricalRecord.getRecord().game}/>
diff --git a/webapp/src/custom.css b/webapp/src/custom.css index daa780fd..39d91582 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1216,6 +1216,25 @@ svg { /*------------------------------Historical--------------------------------------------*/ /* Estilos para los botones */ +.linkButtonHistorical{ + display: flex; + justify-content: center; + align-items: center; + width: 10em; + height: 45px; + background:#00b8ff; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor: pointer; + font-size: 1em; + color: black; + font-weight: 700; + margin: 1em; + text-decoration: none; + +} .historicalButton { color: white; width: 50em; From 24f10df42def0066d3ac1350f71698b341c45592 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 21:18:10 +0200 Subject: [PATCH 13/74] Added restrictions to routes in the webapp --- webapp/src/App.js | 16 ++++++++++------ webapp/src/components/GameMenu/GameMenu.js | 1 + .../src/components/loginAndRegistration/Login.js | 2 +- .../src/components/questionView/QuestionView.js | 2 ++ 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index 887abf5a..683d31d7 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -1,4 +1,3 @@ -import React from 'react'; import QuestionView from './components/questionView/QuestionView'; import GameMenu from './components/GameMenu/GameMenu'; import Navbar from './components/fragments/NavBar'; @@ -10,8 +9,13 @@ import Container from '@mui/material/Container'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; +import React from 'react'; +import Cookies from 'js-cookie'; function App() { + + const isLoggedIn = !!Cookies.get('user'); + return (
@@ -20,12 +24,12 @@ function App() { } /> } /> - } /> + : } /> } /> - } /> - } /> - } /> - } /> + : } /> + : } /> + : } /> + : } />
diff --git a/webapp/src/components/GameMenu/GameMenu.js b/webapp/src/components/GameMenu/GameMenu.js index 7f7bd957..1b79a7e3 100644 --- a/webapp/src/components/GameMenu/GameMenu.js +++ b/webapp/src/components/GameMenu/GameMenu.js @@ -3,6 +3,7 @@ import { Link } from "react-router-dom"; import {useTranslation} from "react-i18next"; import ButtonHistoricalData from "../HistoricalData/ButtonHistoricalData"; + export default function GameMenu() { const[t] = useTranslation("global"); diff --git a/webapp/src/components/loginAndRegistration/Login.js b/webapp/src/components/loginAndRegistration/Login.js index 0ad60066..695f7d70 100644 --- a/webapp/src/components/loginAndRegistration/Login.js +++ b/webapp/src/components/loginAndRegistration/Login.js @@ -21,8 +21,8 @@ const Login = () => { let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) , {expires:oneHourAfter}); - //Used to redirect to the menu to start playing navigate('/menu'); + window.location.reload(); } catch (error) { console.error('Error adding user:', error); } diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 16160a7a..a895c48a 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -20,6 +20,8 @@ function QuestionView(){ const[t, i18n] = useTranslation("global"); const cookie = JSON.parse(Cookies.get('user')) + + const generateQuestions = async (numQuestion) => { if (numQuestion < 0) { try { From 490c2ba458aaaf0dbb0c78b7035da60f3d942a14 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 21:35:06 +0200 Subject: [PATCH 14/74] Added a reload when logging out too --- webapp/src/components/fragments/NavBar.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index 2609183d..1bebea15 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -11,15 +11,15 @@ import Cookies from 'js-cookie'; function Navbar() { const navigate = useNavigate(); const [t, i18n] = useTranslation("global"); - const [anchorEl, setAnchorEl] = useState(); - const [anchorUser, setAnchorUser] = useState(); + const [anchorLanguage, setAnchorLanguage] = useState(null); + const [anchorUser, setAnchorUser] = useState(null); const handleLanguageMenuOpen = (event) => { - setAnchorEl(event.currentTarget); + setAnchorLanguage(event.currentTarget); }; const handleLanguageMenuClose = () => { - setAnchorEl(null); + setAnchorLanguage(null); }; const handleUserMenuOpen = (event) => { @@ -32,7 +32,8 @@ function Navbar() { const removeCookie = () => { Cookies.remove('user'); - navigate('/home') + navigate('/home'); + window.location.reload(); }; const changeLanguage = (language) => { @@ -49,9 +50,10 @@ function Navbar() {
changeLanguage("en")}> {t("navBar.en")} changeLanguage("es")}> {t("navBar.es")} @@ -63,9 +65,10 @@ function Navbar() { <> removeCookie()}> {t("navBar.logout")} From 4153efcd52b2a1e311ff54dc357d1f4f7334fab8 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sun, 14 Apr 2024 22:18:31 +0200 Subject: [PATCH 15/74] Added Game configuration View to configurate customized games and competitive games --- webapp/src/App.js | 2 + .../GameConfigurator/GameConfigurator.js | 57 +++++++++++++++++++ webapp/src/components/GameMenu/GameMenu.js | 2 +- webapp/src/translations/en/global.json | 12 ++++ webapp/src/translations/es/global.json | 13 ++++- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 webapp/src/components/GameConfigurator/GameConfigurator.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 46edf703..9ed9bbde 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -11,6 +11,7 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-d import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; import { UserContextProvider } from './components/loginAndRegistration/UserContext'; +import GameConfigurator from './components/GameConfigurator/GameConfigurator'; function App() { @@ -32,6 +33,7 @@ function App() { } /> } /> } /> + }/>
diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js new file mode 100644 index 00000000..7413dcc2 --- /dev/null +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -0,0 +1,57 @@ +import React, { useState } from 'react'; +import {useTranslation} from "react-i18next"; +import { Link } from "react-router-dom"; + +function GameConfigurator(){ + const [tipoPregunta, setTipoPregunta] = useState(''); + const [numeroPreguntas, setNumeroPreguntas] = useState(5); + const[t, i18n] = useTranslation("global"); + + return ( +
+

{t("gameConfigurator.game_config")}

+ + + +

+ + + {/* Spinner para seleccionar el número de preguntas */} + setNumeroPreguntas(e.target.value)} + min="1" + /> +

+ +

+

{t("gameConfigurator.rules_competi")}

+ {/* Botones para jugar un juego personalizado o competitivo */} + + + +
+ ); +} + +function ButtonCustomized({t, type, number}){ + return( + + ); +} + +function ButtonCompetitive({t}){ + //llamar setTipoPregunta COMPETITIVE + return( + + ); +} + + +export default GameConfigurator; diff --git a/webapp/src/components/GameMenu/GameMenu.js b/webapp/src/components/GameMenu/GameMenu.js index 04c08f45..e0e65053 100644 --- a/webapp/src/components/GameMenu/GameMenu.js +++ b/webapp/src/components/GameMenu/GameMenu.js @@ -18,7 +18,7 @@ export default function GameMenu() { function ButtonNewGame({ t }) { return ( - +

{t("gameMenu.new_game_button")}

); diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 70c3da87..2a2963dd 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -70,6 +70,18 @@ "game":"Game", "points":"points", "no_games_played":"No games played yet" + }, + "gameConfigurator":{ + "game_config":"Game configuration", + "type_quest":"Type of question: ", + "num_quest":"Number of questions: ", + "play_custom":"Play Customized Game", + "rules_competi":"Play with all kinds of questions and a quantity of 5", + "play_competi":"Play competitive Game", + "option_population":"Population", + "option_capital":"Capital", + "option_language":"Language", + "option_size":"Size" } } diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 8c878915..2f855737 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -73,8 +73,19 @@ "game":"Partida", "points":"puntos", "no_games_played":"No has jugado todavía" + }, + "gameConfigurator":{ + "game_config":"Configuración del Juego", + "type_quest":"Tipo de Pregunta", + "num_quest":"Numero de Preguntas", + "play_custom":"Jugar personalizado", + "rules_competi":"Jugar con todo tipo de preguntas siendo estas 5", + "play_competi":"Jugar Competitivo", + "option_population":"Población", + "option_capital":"Capital", + "option_language":"Lenguaje", + "option_size":"Extensión" } - } \ No newline at end of file From 62d7d76c3e6a9fa1ad1f14db67e15e3854f6ec90 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 14 Apr 2024 23:39:50 +0200 Subject: [PATCH 16/74] Added new test case for code cover --- .../loginAndRegistration/Login.test.js | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/Login.test.js b/webapp/src/components/loginAndRegistration/Login.test.js index 2406e721..d58571a1 100644 --- a/webapp/src/components/loginAndRegistration/Login.test.js +++ b/webapp/src/components/loginAndRegistration/Login.test.js @@ -1,15 +1,19 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import Login from './Login'; import { BrowserRouter as Router } from 'react-router-dom'; +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; // Mocking useTranslation hook jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: key => key }), })); +const mockAxios = new MockAdapter(axios); + describe('', () => { - test('renders the Login component', () => { + it('renders the Login component', () => { render( @@ -22,4 +26,24 @@ describe('', () => { expect(screen.getByText('login.login_button')).toBeInTheDocument(); expect(screen.getByText('login.register_link')).toBeInTheDocument(); }); + + it('sends a correct log in petition', async () => { + mockAxios.onPost('http://localhost:8000/login').reply(200, { token: "token", username: "username"}); + + render( + + + + ); + + fireEvent.change(screen.getByPlaceholderText('login.username_placeholder'), { target: { value: 'user' } }); + fireEvent.change(screen.getByPlaceholderText('login.password_placeholder'), { target: { value: 'j' } }); + fireEvent.click(screen.getByText('login.login_button')); + + // Wait for redirection to happen + await waitFor(() => { + expect(window.location.pathname).toBe('/menu'); + }); + + }); }); From 6b48eacb91f96baf3c966d0201760d351941b898 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 00:03:39 +0200 Subject: [PATCH 17/74] Added new endpoints for taking different sizes of questions --- gatewayservice/gateway-service.js | 14 ++++++++++++ gatewayservice/gateway-service.test.js | 12 +++++++++++ questionservice/question-service.js | 27 ++++++++++++++++++++++++ questionservice/question-service.test.js | 18 +++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 009db707..12036c07 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -58,6 +58,20 @@ app.get('/questions', async (req, res) => { } }); +app.get('/questions/:lang/:amount', async (req, res) => { + try { + const lang = req.params.lang; + const amount = req.params.amount; + // Forward the question request to the quetion service + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount); + + res.json(questionResponse.data); + } catch (error) { + + res.status(error.response.status).json({ error: error.response.data.error }); + } +}); + app.get('/questions/:lang', async (req, res) => { try { const lang = req.params.lang; diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index bfed664f..69a771dd 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -24,6 +24,9 @@ describe('Gateway Service', () => { if (url.endsWith('/questions')){ return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", answers: ["225089","272357","267855","231841"]}] }); + } else if (url.endsWith('/questions/es/1')){ + return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", + answers: ["225089","272357","267855","231841"]}] }); } else if (url.endsWith('/questions/es')){ return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", answers: ["225089","272357","267855","231841"]}] }); @@ -72,6 +75,15 @@ describe('Gateway Service', () => { expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); }); + // Test /questions/:lang/:amount endpoint + it('should forward questions request to question service', async () => { + const response = await request(app) + .get('/questions/es/1'); + + expect(response.statusCode).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + }); + // Test /record endpoint it('should forward record request to record service', async () => { const response = await request(app) diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 026feb5c..9278f131 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -30,6 +30,33 @@ app.get('/questions', async (req, res) => { } }); +app.get('/questions/:lang/:amount', async (req, res) => { + try { + const lang = req.params.lang; + let amount = parseInt(req.params.amount); + + if(amount > 20) + amount = 20; + + const questions = await Question.aggregate([ + {$match: {language : lang}}, //Condition + {$sample: {size:amount}} //5 random from the ones that fullfil the condition + ]); + + let jsonResult = {}; + for (let i = 0; i < questions.length; i++) { + const question = questions[i]; + jsonResult[i] = { + question : question.question, + answers : question.answers + } + } + res.json(jsonResult); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + app.get('/questions/:lang', async (req, res) => { try { const lang = req.params.lang; diff --git a/questionservice/question-service.test.js b/questionservice/question-service.test.js index 67b5ca66..a3f51c1c 100644 --- a/questionservice/question-service.test.js +++ b/questionservice/question-service.test.js @@ -12,7 +12,7 @@ beforeAll(async () => { app = require('./question-service'); //Populate db - for(let i = 0; i < 6 ; i++){ + for(let i = 0; i < 21 ; i++){ const question = new Question( { question: "¿Cuál es la población de Oviedo?", answers: [ @@ -60,4 +60,20 @@ describe('Question Service', () => { expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); expect(Object.keys(response.body).length).toBe(5); }); + + it('Should give 10 questions /questions/es/10', async () => { + + let response = await request(app).get('/questions/es/10'); + expect(response.status).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + expect(Object.keys(response.body).length).toBe(10); + }); + + it('Should give 20 questions as the max is 20 /questions/es/21', async () => { + + let response = await request(app).get('/questions/es/21'); + expect(response.status).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + expect(Object.keys(response.body).length).toBe(20); + }); }); \ No newline at end of file From 46ca5377f49c7858b41e418678c7e450a3fc69fb Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 00:25:49 +0200 Subject: [PATCH 18/74] Added email to users at the backend --- users/authservice/auth-model.js | 1 + users/authservice/auth-service.js | 14 +++++++++----- users/authservice/auth-service.test.js | 6 ++++-- users/userservice/user-model.js | 4 ++++ users/userservice/user-service.js | 13 ++++++++++--- users/userservice/user-service.test.js | 17 +++++++++++++++-- 6 files changed, 43 insertions(+), 12 deletions(-) diff --git a/users/authservice/auth-model.js b/users/authservice/auth-model.js index 7763b51e..5cdbaf69 100644 --- a/users/authservice/auth-model.js +++ b/users/authservice/auth-model.js @@ -1,6 +1,7 @@ const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ + email: String, username: String, password: String, createdAt: Date, diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 0bdbf096..60c425b4 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -28,24 +28,28 @@ app.post('/login', async (req, res) => { try { // Check if required fields are present in the request body try{ - validateRequiredFields(req, ['username', 'password']); + validateRequiredFields(req, ['email', 'username', 'password']); } catch(error){ res.status(400).json({ error : error.message }); return } - const { username, password } = req.body; + const { email, username, password } = req.body; - // Find the user by username in the database - const user = await User.findOne({ username }); + let user; + if(username) //Can log in with both + // Find the user by username in the database + user = await User.findOne({ username }) + else + user = await User.findOne({ email }) // Check if the user exists and verify the password if (user && await bcrypt.compare(password, user.password)) { // Generate a JWT token const token = jwt.sign({ userId: user._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '1h' }); // Respond with the token and user information - res.json({ token: token, username: username}); + res.json({ token: token, username: username, email: email}); } else { res.status(400).json({ error: 'Invalid credentials' }); } diff --git a/users/authservice/auth-service.test.js b/users/authservice/auth-service.test.js index f4a5a82e..28067d5a 100644 --- a/users/authservice/auth-service.test.js +++ b/users/authservice/auth-service.test.js @@ -8,6 +8,7 @@ let app; //test user const user = { + email: 'nice@g.com', username: 'testuser', password: 'testpassword', }; @@ -15,6 +16,7 @@ const user = { async function addUser(user){ const hashedPassword = await bcrypt.hash(user.password, 10); const newUser = new User({ + email: user.email, username: user.username, password: hashedPassword, createdAt: new Date() @@ -47,11 +49,11 @@ describe('Auth Service', () => { it('Should show missing field user /login', async () => { const response = await request(app).post('/login').send(); expect(response.status).toBe(400); - expect(response.body).toHaveProperty('error', 'Missing required field: username'); + expect(response.body).toHaveProperty('error', 'Missing required field: email'); }); it('Should show invalid credentials /login', async () => { - const user2 = {username:"Hello", password:"world"} + const user2 = {email:"nice@g.com" ,username:"Hello", password:"world"} const response = await request(app).post('/login').send(user2); expect(response.status).toBe(400); expect(response.body).toHaveProperty('error', 'Invalid credentials'); diff --git a/users/userservice/user-model.js b/users/userservice/user-model.js index 71d81b5f..e6643ff2 100644 --- a/users/userservice/user-model.js +++ b/users/userservice/user-model.js @@ -1,6 +1,10 @@ const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ + email: { + type: String, + required: true, + }, username: { type: String, required: true, diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 69899c29..6278ce18 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -30,7 +30,7 @@ app.post('/adduser', async (req, res) => { try { // Check if required fields are present in the request body try{ - validateRequiredFields(req, ['username', 'password']); + validateRequiredFields(req, ['email', 'username', 'password']); } catch(error){ res.status(400).json({ error : error.message }); @@ -38,15 +38,22 @@ app.post('/adduser', async (req, res) => { } //Check there is not a user with the same name - const user = await User.findOne({username: req.body.username}); + const userUsername = await User.findOne({username: req.body.username}); - if(user) + //Check there is not a user with the same name + const userEmail = await User.findOne({email: req.body.email}); + + if(userUsername) return res.status(400).json({error : "Username already in use"}) + if(userEmail) + return res.status(400).json({error : "Email already in use"}) + // Encrypt the password before saving it const hashedPassword = await bcrypt.hash(req.body.password, 10); const newUser = new User({ + email: req.body.email, username: req.body.username, password: hashedPassword, }); diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index 7b9e1b4f..a691d432 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -1,5 +1,6 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); +const { email } = require('asciidoctor-emoji/dist/node/twemoji-map'); let mongoServer; let app; @@ -21,6 +22,7 @@ afterAll(async () => { describe('User Service', () => { it('should add a new user on POST /adduser', async () => { const newUser = { + email: 'Nice@g.com', username: 'testuser', password: 'testpassword' }; @@ -33,11 +35,12 @@ describe('User Service', () => { it('Should show missing field user /adduser', async () => { const response = await request(app).post('/adduser').send(); expect(response.status).toBe(400); - expect(response.body).toHaveProperty('error', 'Missing required field: username'); + expect(response.body).toHaveProperty('error', 'Missing required field: email'); }); it('Should not register user /adduser', async () => { const newUser = { + email: 'Nice2@g.com', username: 'testuser', password: 'testpassword' }; @@ -47,6 +50,16 @@ describe('User Service', () => { expect(response.body).toHaveProperty('error', 'Username already in use'); }); - + it('Should not register user /adduser', async () => { + const newUser = { + email: 'Nice@g.com', + username: 'testuser2', + password: 'testpassword' + }; + + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Email already in use'); + }); }); From 257db61f995179c1f4432eb73ca52c34eed2e12b Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 00:29:48 +0200 Subject: [PATCH 19/74] Removed weird import prob vs code stuff --- users/userservice/user-service.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index a691d432..396b6f1d 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -1,6 +1,5 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); -const { email } = require('asciidoctor-emoji/dist/node/twemoji-map'); let mongoServer; let app; From 57b44f33bea83059b2f8c53c928ad17332e2bf9e Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 00:36:54 +0200 Subject: [PATCH 20/74] Added changes to fix code duplication issues and security flaws --- gatewayservice/gateway-service.test.js | 2 -- users/authservice/auth-service.js | 4 +++- users/userservice/user-service.js | 4 ++-- users/userservice/user-service.test.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index ca42bc32..d3904a1d 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -100,7 +100,6 @@ describe('Gateway Service with token mock', () => { it('should forward record request to record service', async () => { const response = await request(app) .get('/record/testuser').set('token', 'valorDelToken'); - console.log(response) expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); }); @@ -113,7 +112,6 @@ describe('Gateway Service without token mock', () => { it('should not verify the token', async () => { const response = await request(app) .get('/record/testuser'); - console.log(response) expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); }); diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 60c425b4..85ca92ec 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -35,7 +35,9 @@ app.post('/login', async (req, res) => { return } - const { email, username, password } = req.body; + const email = req.body.email.toString(); + const username = req.body.username.toString(); + const password = req.body.password.toString(); let user; if(username) //Can log in with both diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 6278ce18..34cfb7e0 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -38,10 +38,10 @@ app.post('/adduser', async (req, res) => { } //Check there is not a user with the same name - const userUsername = await User.findOne({username: req.body.username}); + const userUsername = await User.findOne({username: req.body.username.toString()}); //Check there is not a user with the same name - const userEmail = await User.findOne({email: req.body.email}); + const userEmail = await User.findOne({email: req.body.email.toString()}); if(userUsername) return res.status(400).json({error : "Username already in use"}) diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index 396b6f1d..227755bb 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -41,7 +41,7 @@ describe('User Service', () => { const newUser = { email: 'Nice2@g.com', username: 'testuser', - password: 'testpassword' + password: 'test' }; const response = await request(app).post('/adduser').send(newUser); @@ -53,7 +53,7 @@ describe('User Service', () => { const newUser = { email: 'Nice@g.com', username: 'testuser2', - password: 'testpassword' + password: 'password' }; const response = await request(app).post('/adduser').send(newUser); From 22dba52ae62b50bc68324a4fb171ee727ea1e0c0 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 00:45:34 +0200 Subject: [PATCH 21/74] Try to reduce duplicate code --- users/userservice/user-service.test.js | 32 ++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index 227755bb..d5352626 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -4,7 +4,11 @@ const { MongoMemoryServer } = require('mongodb-memory-server'); let mongoServer; let app; - +let newUser = { + email: 'Nice@g.com', + username: 'testuser', + password: 'testpassword' +}; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); @@ -18,14 +22,16 @@ afterAll(async () => { await mongoServer.stop(); }); +afterEach(async () => { + newUser = { + email: 'Nice@g.com', + username: 'testuser', + password: 'testpassword' + }; +}) + describe('User Service', () => { it('should add a new user on POST /adduser', async () => { - const newUser = { - email: 'Nice@g.com', - username: 'testuser', - password: 'testpassword' - }; - const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(200); expect(response.body).toHaveProperty('username', 'testuser'); @@ -38,11 +44,7 @@ describe('User Service', () => { }); it('Should not register user /adduser', async () => { - const newUser = { - email: 'Nice2@g.com', - username: 'testuser', - password: 'test' - }; + newUser.email = 'Nice2@g.com'; const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(400); @@ -50,11 +52,7 @@ describe('User Service', () => { }); it('Should not register user /adduser', async () => { - const newUser = { - email: 'Nice@g.com', - username: 'testuser2', - password: 'password' - }; + newUser.username = 'testuser2'; const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(400); From 2e0ff2e46246ae013e95dc409304aa160d757a82 Mon Sep 17 00:00:00 2001 From: sinne10 Date: Mon, 15 Apr 2024 01:01:26 +0200 Subject: [PATCH 22/74] User policies and internationalization --- .../loginAndRegistration/AddUser.js | 57 +++++++++++++------ webapp/src/translations/en/global.json | 11 +++- webapp/src/translations/es/global.json | 11 +++- webapp/src/translations/tk/global.json | 11 +++- 4 files changed, 70 insertions(+), 20 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 43e751e1..5b577edf 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -16,23 +16,32 @@ const AddUser = () => { const [password, setPassword] = useState(''); const [repeatPassword, setRepeatPassword] = useState(''); const [passwordStrength, setPasswordStrength] = useState(undefined); - const [passwordStrengthText, setPasswordStrengthText] = useState('Weak password'); + const [passwordStrengthText, setPasswordStrengthText] = useState(""); const [submitError, setSubmitError] = useState(''); const handleSubmit = async (event) => { event.preventDefault(); try { - //TODO: Add more validations - if(password !== repeatPassword){ //User put the same password - setSubmitError('Passwords do not match'); - } else if(/\s/.test(password)){ //User put spaces in password - setSubmitError('Password cannot contain spaces'); - } else if(password.length < 8){ //Password too short - setSubmitError('Password must be at least 8 characters long'); - } else if(password.length > 64){ //Password too long - setSubmitError('Password cannot be over 64 characters long'); - } else{ //Continue + //Validations + //TODO: email validation + if(password !== repeatPassword){ + //User put the same password + setSubmitError(t("addUser.error_passwords_no_match")); + } else if(/\s/.test(password)){ + //User put spaces in password + setSubmitError(t("addUser.error_password_spaces")); + } else if(password.length < 8){ + //Password too short + setSubmitError(t("addUser.error_password_minimum_length")); + } else if(password.length > 64){ + //Password too long + setSubmitError(t("addUser.error_password_maximum_length")); + } else if(/\s/.test(username)){ + //Spaces in username + setSubmitError(t("addUser.error_username_spaces")); + } else{ + //Continue setSubmitError(''); const response = await axios.post(apiUrl, { username, password }); console.log("Registered user: " + response.data.username); @@ -40,10 +49,24 @@ const AddUser = () => { } } catch (error) { + if(error.response.data.error === "Username already in use"){ //TODO: Improve + setSubmitError(t("addUser.error_username_in_use")); + } console.error('Error adding user:', error); } }; + //Possible email validation + /** + const validateEmail = (email) => { + return String(email) + .toLowerCase() + .match( + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + ); + }; + */ + const handlePasswordChange = (e) => { const newPassword = e.target.value; setPassword(newPassword); @@ -52,22 +75,22 @@ const AddUser = () => { switch(newStrength.score){ case 0: - setPasswordStrengthText('Weak password'); + setPasswordStrengthText(t("addUser.very_weak_password")); break; case 1: - setPasswordStrengthText('Weak password'); + setPasswordStrengthText(t("addUser.very_weak_password")); break; case 2: - setPasswordStrengthText('Fair password'); + setPasswordStrengthText(t("addUser.weak_password")); break; case 3: - setPasswordStrengthText('Good password'); + setPasswordStrengthText(t("addUser.good_password")); break; case 4: - setPasswordStrengthText('Strong password'); + setPasswordStrengthText(t("addUser.strong_password")); break; default: - setPasswordStrengthText('Weak password'); + setPasswordStrengthText(t("addUser.very_weak_password")); break; } setPasswordStrength(newStrength); diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 8f79adda..154bdfa0 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -50,7 +50,16 @@ "password_placeholder": "Password", "repeat_password_placeholder": "Repeat password", "register_button": "Register", - "login_link": "Do you have an account? Login here." + "login_link": "Do you have an account? Login here.", + "very_weak_password": "Very weak password", + "weak_password": "Weak password", + "good_password": "Good password", + "strong_password": "Strong password", + "error_passwords_no_match": "Passwords do not match", + "error_password_spaces": "Password cannot contain spaces", + "error_password_minimum_length": "Password must be at least 8 characters long", + "error_password_maximum_length": "Password cannot be over 64 characters long", + "error_username_in_use": "Username already in use" }, "gameMenu":{ "history_button":"View Historical Data", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 410eebfa..1c0a5f1b 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -54,7 +54,16 @@ "password_placeholder": "Contraseña", "repeat_password_placeholder": "Repetir contraseña", "register_button": "Registrarse", - "login_link": "¿Ya tienes una cuenta? Inicia sesión aquí." + "login_link": "¿Ya tienes una cuenta? Inicia sesión aquí.", + "very_weak_password": "Contraseña muy débil", + "weak_password": "Contraseña débil", + "good_password": "Contraseña buena", + "strong_password": "Contraseña fuerte", + "error_passwords_no_match": "Las contraseñas no coinciden", + "error_password_spaces": "La contraseña no puede contener espacios", + "error_password_minimum_length": "La contraseña debe tener al menos 8 caracteres", + "error_password_maximum_length": "La contraseña no debe tener más de 64 caracteres", + "error_username_in_use": "Nombre de usuario no disponible" }, "gameMenu":{ "history_button":"Ver Historial", diff --git a/webapp/src/translations/tk/global.json b/webapp/src/translations/tk/global.json index 5a17d359..d79e6e10 100644 --- a/webapp/src/translations/tk/global.json +++ b/webapp/src/translations/tk/global.json @@ -50,7 +50,16 @@ "password_placeholder": "Şifre", "repeat_password_placeholder": "Şifreyi Tekrar Girin", "register_button": "Kayıt Ol", - "login_link": "Hesabınız var mı? Buradan giriş yapın." + "login_link": "Hesabınız var mı? Buradan giriş yapın.", + "very_weak_password": "Çok zayıf şifre", + "weak_password": "Zayıf şifre", + "good_password": "İyi şifre", + "strong_password": "Güçlü şifre", + "error_passwords_no_match": "Şifreler eşleşmiyor", + "error_password_spaces": "Şifre boşluk içeremez", + "error_password_minimum_length": "Şifre en az 8 karakter uzunluğunda olmalıdır", + "error_password_maximum_length": "Şifre en fazla 64 karakter uzunluğunda olabilir", + "error_username_in_use": "Kullanıcı adı zaten kullanımda" }, "gameMenu": { "history_button": "Tarihsel Verileri Görüntüle", From aa34cf9367d5e6825bd4a85f92758419213bd4dd Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 17:54:34 +0200 Subject: [PATCH 23/74] Added new endpoint to get an specific amount of questions of a specific type --- gatewayservice/gateway-service.js | 16 ++++++++++++++ gatewayservice/gateway-service.test.js | 17 ++++++++++++-- questionservice/question-service.js | 28 ++++++++++++++++++++++++ questionservice/question-service.test.js | 15 +++++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 12036c07..3eb2b382 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -58,6 +58,22 @@ app.get('/questions', async (req, res) => { } }); +app.get('/questions/:lang/:amount/:type', async (req, res) => { + try { + const lang = req.params.lang; + const amount = req.params.amount; + const type = req.params.type; + // Forward the question request to the quetion service + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type); + + res.json(questionResponse.data); + } catch (error) { + + res.status(error.response.status).json({ error: error.response.data.error }); + } +}); + + app.get('/questions/:lang/:amount', async (req, res) => { try { const lang = req.params.lang; diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 69a771dd..8a4b0469 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -24,6 +24,10 @@ describe('Gateway Service', () => { if (url.endsWith('/questions')){ return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", answers: ["225089","272357","267855","231841"]}] }); + } else if (url.endsWith('/questions/es/1/CAPITAL')){ + return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", + answers: ["225089","272357","267855","231841"]}] }); + } else if (url.endsWith('/questions/es/1')){ return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", answers: ["225089","272357","267855","231841"]}] }); @@ -75,8 +79,8 @@ describe('Gateway Service', () => { expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); }); - // Test /questions/:lang/:amount endpoint - it('should forward questions request to question service', async () => { + // Test /questions/:lang/:amount endpoint + it('should forward questions request to question service', async () => { const response = await request(app) .get('/questions/es/1'); @@ -84,6 +88,15 @@ describe('Gateway Service', () => { expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); }); + // Test /questions/:lang/:amount/:type endpoint + it('should forward questions request to question service', async () => { + const response = await request(app) + .get('/questions/es/1/CAPITAL'); + + expect(response.statusCode).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + }); + // Test /record endpoint it('should forward record request to record service', async () => { const response = await request(app) diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 9278f131..12f4c7ca 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -30,6 +30,34 @@ app.get('/questions', async (req, res) => { } }); +app.get('/questions/:lang/:amount/:type', async (req, res) => { + try { + const lang = req.params.lang.toString(); + let amount = parseInt(req.params.amount); + const type = req.params.type.toString(); + + if(amount > 20) + amount = 20; + + const questions = await Question.aggregate([ + {$match: {language : lang, type: type}}, //Condition + {$sample: {size:amount}} //5 random from the ones that fullfil the condition + ]); + + let jsonResult = {}; + for (let i = 0; i < questions.length; i++) { + const question = questions[i]; + jsonResult[i] = { + question : question.question, + answers : question.answers + } + } + res.json(jsonResult); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + app.get('/questions/:lang/:amount', async (req, res) => { try { const lang = req.params.lang; diff --git a/questionservice/question-service.test.js b/questionservice/question-service.test.js index a3f51c1c..6f68c54d 100644 --- a/questionservice/question-service.test.js +++ b/questionservice/question-service.test.js @@ -76,4 +76,19 @@ describe('Question Service', () => { expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); expect(Object.keys(response.body).length).toBe(20); }); + + it('Should give 10 questions /questions/es/10/POPULATION', async () => { + + let response = await request(app).get('/questions/es/10/POPULATION'); + expect(response.status).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + expect(Object.keys(response.body).length).toBe(10); + }); + + it('Should give 0 questions /questions/es/10/CAPITAL', async () => { + + let response = await request(app).get('/questions/es/10/CAPITAL'); + expect(response.status).toBe(200); + expect(Object.keys(response.body).length).toBe(0); + }); }); \ No newline at end of file From 581febaf82ea0050c0c23f6686b7dd75b01f4cbb Mon Sep 17 00:00:00 2001 From: uo289267 Date: Mon, 15 Apr 2024 19:30:34 +0200 Subject: [PATCH 24/74] Added Game Configurator logic --- webapp/public/index.html | 1 + .../GameConfigurator/GameConfigurator.js | 32 ++++++++++++------- .../questionView/QuestionGenerator.js | 9 ++++-- .../components/questionView/QuestionView.js | 6 ++-- webapp/src/index.js | 6 ++-- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/webapp/public/index.html b/webapp/public/index.html index aa069f27..a9a639d1 100644 --- a/webapp/public/index.html +++ b/webapp/public/index.html @@ -3,6 +3,7 @@ + :

{t("gameConfigurator.game_config")}

@@ -26,30 +33,33 @@ function GameConfigurator(){ type="number" value={numeroPreguntas} onChange={(e) => setNumeroPreguntas(e.target.value)} - min="1" + min="1" max="20" />

- +

{t("gameConfigurator.rules_competi")}

{/* Botones para jugar un juego personalizado o competitivo */} - -
); } -function ButtonCustomized({t, type, number}){ - return( - - ); + +function ButtonCustomized({t,handleClick}) { + + return ( + + ); } + function ButtonCompetitive({t}){ //llamar setTipoPregunta COMPETITIVE - return( - + return ( + +

{t("gameConfigurator.play_competi")}

+ ); } diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index ff9058de..8ea479e8 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -8,12 +8,13 @@ class QuestionGenerator{ } - async generateQuestions(lang) { + async generateQuestions(lang, type, amount) { try { //const response = await fetch(this.apiUrl); //const receivedQuestions = await response.json(); //Mockup + console.log("type: "+type+" amount: "+amount) const receivedQuestions = JSON.parse('{"0":{"question":"¿Cuál es la población de Oviedo?","answers":["225089","191325","220587","121548"]},'+ '"1":{"question":"¿Cuál es la población de Gijón?","answers":["275274","159658","233982","305554"]},'+ '"2":{"question":"¿Cuál es la población de Avilés?","answers":["82568","115595","41284","122200"]},'+ @@ -35,7 +36,11 @@ class QuestionGenerator{ /* try { - const response = await axios.get(this.apiUrl + '/' + lang); + if(type==="COMPETITIVE"){ + const response = await axios.get(this.apiUrl + '/' + lang); + }else{ + const response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type); + } const receivedQuestions = await response.data; let i = 0; var questions = []; diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index cf4ea79c..156e3828 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -14,16 +14,16 @@ import BackButtonToGameMenu from '../fragments/BackButtonToGameMenu'; const creationHistoricalRecord = new CreationHistoricalRecord(); const questionGenerator = new QuestionGenerator(); var points = 0; -function QuestionView(){ +function QuestionView({type= "COMPETITIVE", amount=5}){ const [numQuestion, setnumQuestion] = useState(-1); const [questions, setQuestions] = useState(null); const[t, i18n] = useTranslation("global"); const {user} = useUserContext(); - + const generateQuestions = async (numQuestion) => { if (numQuestion < 0) { try { - var generatedQuestions = await questionGenerator.generateQuestions(i18n.language); + var generatedQuestions = await questionGenerator.generateQuestions(i18n.language, type, amount); setQuestions(generatedQuestions); setnumQuestion(0); } catch (error) { diff --git a/webapp/src/index.js b/webapp/src/index.js index 05cbd873..103dc805 100644 --- a/webapp/src/index.js +++ b/webapp/src/index.js @@ -32,16 +32,14 @@ i18next.init({ const root = ReactDOM.createRoot(document.getElementById('root')); root.render( + - - - - {/* envolviendo la app con el sistema de traducciones */} + ); // If you want to start measuring performance in your app, pass a function From bb17d3998c5e52523d6655613403dca4f1953861 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 20:35:44 +0200 Subject: [PATCH 25/74] Added email field on login and register and be able to login with username or email --- users/authservice/auth-service.js | 13 +++++-------- users/authservice/auth-service.test.js | 18 ++++++++++++------ .../components/loginAndRegistration/AddUser.js | 14 +++++++++++++- .../components/loginAndRegistration/Login.js | 7 ------- webapp/src/translations/en/global.json | 5 +++-- webapp/src/translations/es/global.json | 5 +++-- 6 files changed, 36 insertions(+), 26 deletions(-) diff --git a/users/authservice/auth-service.js b/users/authservice/auth-service.js index 85ca92ec..924dc512 100644 --- a/users/authservice/auth-service.js +++ b/users/authservice/auth-service.js @@ -28,22 +28,19 @@ app.post('/login', async (req, res) => { try { // Check if required fields are present in the request body try{ - validateRequiredFields(req, ['email', 'username', 'password']); + validateRequiredFields(req, ['username', 'password']); } catch(error){ res.status(400).json({ error : error.message }); return } - const email = req.body.email.toString(); + const email = req.body.username.toString(); const username = req.body.username.toString(); const password = req.body.password.toString(); - let user; - if(username) //Can log in with both - // Find the user by username in the database - user = await User.findOne({ username }) - else + let user = await User.findOne({ username }) + if(!user) //There is no user by that username we may have received an email user = await User.findOne({ email }) // Check if the user exists and verify the password @@ -51,7 +48,7 @@ app.post('/login', async (req, res) => { // Generate a JWT token const token = jwt.sign({ userId: user._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '1h' }); // Respond with the token and user information - res.json({ token: token, username: username, email: email}); + res.json({ token: token, username: user.username, email: user.email}); } else { res.status(400).json({ error: 'Invalid credentials' }); } diff --git a/users/authservice/auth-service.test.js b/users/authservice/auth-service.test.js index 28067d5a..7f258f5c 100644 --- a/users/authservice/auth-service.test.js +++ b/users/authservice/auth-service.test.js @@ -7,8 +7,7 @@ let mongoServer; let app; //test user -const user = { - email: 'nice@g.com', +let user = { username: 'testuser', password: 'testpassword', }; @@ -16,7 +15,7 @@ const user = { async function addUser(user){ const hashedPassword = await bcrypt.hash(user.password, 10); const newUser = new User({ - email: user.email, + email: "user@gmail.com", username: user.username, password: hashedPassword, createdAt: new Date() @@ -46,14 +45,21 @@ describe('Auth Service', () => { expect(response.body).toHaveProperty('username', 'testuser'); }); - it('Should show missing field user /login', async () => { + it('Should perform a login operation with email /login', async () => { + user.username = "user@gmail.com"; + const response = await request(app).post('/login').send(user); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('username', 'testuser'); + }); + + it('Should show missing field username /login', async () => { const response = await request(app).post('/login').send(); expect(response.status).toBe(400); - expect(response.body).toHaveProperty('error', 'Missing required field: email'); + expect(response.body).toHaveProperty('error', 'Missing required field: username'); }); it('Should show invalid credentials /login', async () => { - const user2 = {email:"nice@g.com" ,username:"Hello", password:"world"} + const user2 = {username:"Hello", password:"world"} const response = await request(app).post('/login').send(user2); expect(response.status).toBe(400); expect(response.body).toHaveProperty('error', 'Invalid credentials'); diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 2324ecab..f5ee2557 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -11,6 +11,7 @@ const AddUser = () => { const navigate = useNavigate(); const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/adduser"; const { t } = useTranslation("global"); + const [email, setEmail] = useState(''); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [repeatPassword, setRepeatPassword] = useState(''); @@ -21,7 +22,7 @@ const AddUser = () => { try { //TODO: Add more validations if(password === repeatPassword){ //User put the same password - const response = await axios.post(apiUrl, { username, password }); + const response = await axios.post(apiUrl, { email, username, password }); console.log("Registered user: " + response.data.username); navigate('/login'); } @@ -40,6 +41,17 @@ const AddUser = () => {

{t("addUser.title")}

+
+

{t("addUser.email_placeholder")}:

+ setEmail(e.target.value)} + /> +

{t("addUser.username_placeholder")}:

{ onChange={(e) => setPassword(e.target.value)} />
- {//TODO: Study this option and see if it is viable - } -
- -
diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index f04a6b86..5b8cd91f 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -38,7 +38,7 @@ }, "login": { "title": "Login", - "username_placeholder": "Username", + "username_placeholder": "Username or Email", "password_placeholder": "Password", "remember_me": "Remember me", "forgot_password": "Forgot password?", @@ -51,7 +51,8 @@ "password_placeholder": "Password", "repeat_password_placeholder": "Repeat password", "register_button": "Register", - "login_link": "Do you have an account? Login here." + "login_link": "Do you have an account? Login here.", + "email_placeholder" : "Email" }, "gameMenu":{ "history_button":"View Historical Data", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 9a6d1428..57a91fdf 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -41,7 +41,7 @@ }, "login": { "title": "Inicio de sesión", - "username_placeholder": "Nombre de usuario", + "username_placeholder": "Nombre de usuario o Correo Electrónico", "password_placeholder": "Contraseña", "remember_me": "Recordarme", "forgot_password": "¿Olvidaste tu contraseña?", @@ -55,7 +55,8 @@ "password_placeholder": "Contraseña", "repeat_password_placeholder": "Repetir contraseña", "register_button": "Registrarse", - "login_link": "¿Ya tienes una cuenta? Inicia sesión aquí." + "login_link": "¿Ya tienes una cuenta? Inicia sesión aquí.", + "email_placeholder" : "Correo electrónico" }, "gameMenu":{ "history_button":"Ver Historial", From ee46ede9b9589e1924f703a0c14c77cb60dd032a Mon Sep 17 00:00:00 2001 From: sinne10 Date: Mon, 15 Apr 2024 21:04:20 +0200 Subject: [PATCH 26/74] Bugfix --- .../loginAndRegistration/AddUser.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 5b577edf..0714bd67 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -16,7 +16,7 @@ const AddUser = () => { const [password, setPassword] = useState(''); const [repeatPassword, setRepeatPassword] = useState(''); const [passwordStrength, setPasswordStrength] = useState(undefined); - const [passwordStrengthText, setPasswordStrengthText] = useState(""); + const [passwordStrengthText, setPasswordStrengthText] = useState(''); const [submitError, setSubmitError] = useState(''); @@ -27,19 +27,19 @@ const AddUser = () => { //TODO: email validation if(password !== repeatPassword){ //User put the same password - setSubmitError(t("addUser.error_passwords_no_match")); + setSubmitError("addUser.error_passwords_no_match"); } else if(/\s/.test(password)){ //User put spaces in password - setSubmitError(t("addUser.error_password_spaces")); + setSubmitError("addUser.error_password_spaces"); } else if(password.length < 8){ //Password too short - setSubmitError(t("addUser.error_password_minimum_length")); + setSubmitError("addUser.error_password_minimum_length"); } else if(password.length > 64){ //Password too long - setSubmitError(t("addUser.error_password_maximum_length")); + setSubmitError("addUser.error_password_maximum_length"); } else if(/\s/.test(username)){ //Spaces in username - setSubmitError(t("addUser.error_username_spaces")); + setSubmitError("addUser.error_username_spaces"); } else{ //Continue setSubmitError(''); @@ -50,7 +50,7 @@ const AddUser = () => { } catch (error) { if(error.response.data.error === "Username already in use"){ //TODO: Improve - setSubmitError(t("addUser.error_username_in_use")); + setSubmitError("addUser.error_username_in_use"); } console.error('Error adding user:', error); } @@ -75,22 +75,22 @@ const AddUser = () => { switch(newStrength.score){ case 0: - setPasswordStrengthText(t("addUser.very_weak_password")); + setPasswordStrengthText("addUser.very_weak_password"); break; case 1: - setPasswordStrengthText(t("addUser.very_weak_password")); + setPasswordStrengthText("addUser.very_weak_password"); break; case 2: - setPasswordStrengthText(t("addUser.weak_password")); + setPasswordStrengthText("addUser.weak_password"); break; case 3: - setPasswordStrengthText(t("addUser.good_password")); + setPasswordStrengthText("addUser.good_password"); break; case 4: - setPasswordStrengthText(t("addUser.strong_password")); + setPasswordStrengthText("addUser.strong_password"); break; default: - setPasswordStrengthText(t("addUser.very_weak_password")); + setPasswordStrengthText("addUser.very_weak_password"); break; } setPasswordStrength(newStrength); @@ -126,7 +126,7 @@ const AddUser = () => {
- {passwordStrengthText} + {t(passwordStrengthText.toString())} { onChange={(e) => setRepeatPassword(e.target.value)} />
- {submitError &&

{submitError}

} + {submitError &&

{t(submitError)}

} From 759076b7381f4db377699ccd0a4b0e8cda8870ea Mon Sep 17 00:00:00 2001 From: uo289267 Date: Mon, 15 Apr 2024 21:26:18 +0200 Subject: [PATCH 27/74] Added style to game configurator --- .../GameConfigurator/GameConfigurator.js | 19 ++++--- webapp/src/custom.css | 50 ++++++++++++++++++- webapp/src/translations/en/global.json | 4 +- webapp/src/translations/es/global.json | 8 +-- 4 files changed, 69 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js index 558b85f6..1c592294 100644 --- a/webapp/src/components/GameConfigurator/GameConfigurator.js +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import {useTranslation} from "react-i18next"; import { Link } from "react-router-dom"; import QuestionView from '../questionView/QuestionView' +import BackButtonToGameMenu from '../fragments/BackButtonToGameMenu'; function GameConfigurator(){ const [tipoPregunta, setTipoPregunta] = useState('POPULATION'); @@ -15,11 +16,12 @@ function GameConfigurator(){ } return ( clickedForNewGame ? : -
+
+

{t("gameConfigurator.game_config")}

- +

{t("gameConfigurator.custo_game")}

- setTipoPregunta(e.target.value)}> @@ -29,7 +31,7 @@ function GameConfigurator(){ {/* Spinner para seleccionar el número de preguntas */} - setNumeroPreguntas(e.target.value)} @@ -38,24 +40,27 @@ function GameConfigurator(){



+
+

+

{t("gameConfigurator.competi_game")}

{t("gameConfigurator.rules_competi")}

{/* Botones para jugar un juego personalizado o competitivo */} +
); } function ButtonCustomized({t,handleClick}) { - return ( - + ); } function ButtonCompetitive({t}){ - //llamar setTipoPregunta COMPETITIVE + return (

{t("gameConfigurator.play_competi")}

diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 39d91582..3c30da2f 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1231,7 +1231,8 @@ svg { font-size: 1em; color: black; font-weight: 700; - margin: 1em; + margin-top: 3em; + margin-bottom: 3em; text-decoration: none; } @@ -1299,3 +1300,50 @@ svg { h2{ text-align: center; } +/*--------------------------------------------------Configurator---------------------------------*/ +/* Estilo para el elemento select */ +.select-style { + width: 200px; + height: 35px; + border: 1px solid #ccc; + border-radius: 5px; + background-color: #f8f8f8; + padding: 5px; + font-size: 16px; + color: #333; +} + +.select-style:focus { + border-color: #6e9ecf; + outline: none; +} + +/* Estilo para el spinner */ +.spinner-style { + width: 40px; + height: 30px; + border: 1px solid #ccc; + background-color: #f8f8f8; + border-radius: 5px; + font-size: medium; +} +.GameConfiguratorDiv> * { + margin-top: 10px; + margin-bottom: 10px; +} +.GameConfiguratorDiv { + left: 50%; + top: 50%; +} +.hr-style { + border: 0; + border-top: 1px solid #8e888885; /* Color gris */ + margin: 20px 0; /* Espacio antes y después de la línea */ +} + +.GameConfiguratorDiv h2 { + text-align: left; /* Alinea el texto a la izquierda */ + margin-top:20px; + margin-bottom:20px; +} + diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 2a2963dd..28c2a341 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -81,7 +81,9 @@ "option_population":"Population", "option_capital":"Capital", "option_language":"Language", - "option_size":"Size" + "option_size":"Size", + "custo_game":"Create custom game", + "competi_game":"Play Competitive" } } diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 2f855737..32f9e22a 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -76,15 +76,17 @@ }, "gameConfigurator":{ "game_config":"Configuración del Juego", - "type_quest":"Tipo de Pregunta", - "num_quest":"Numero de Preguntas", + "type_quest":"Tipo de Pregunta : ", + "num_quest":"Número de Preguntas : ", "play_custom":"Jugar personalizado", "rules_competi":"Jugar con todo tipo de preguntas siendo estas 5", "play_competi":"Jugar Competitivo", "option_population":"Población", "option_capital":"Capital", "option_language":"Lenguaje", - "option_size":"Extensión" + "option_size":"Extensión", + "custo_game":"Crea una partida personalizada", + "competi_game":"Juega en modo Competitivo" } } From d31b5ab6ea8b8f8761a1bfa351c6129431f818ac Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 22:57:56 +0200 Subject: [PATCH 28/74] Added enpoints for getting the ranking information of the competitive games --- gatewayservice/gateway-service.js | 21 ++++ gatewayservice/gateway-service.test.js | 24 ++++ questionservice/question-service.js | 20 ++-- questionservice/question-service.test.js | 27 ++++- users/recordservice/record-model.js | 18 +-- users/recordservice/record-service.js | 60 +++++++++- users/recordservice/record-service.test.js | 112 +++++++++++++++++- .../HistoricalData/HistoryRecordRetriever.js | 10 ++ .../questionView/CreationHistoricalRecord.js | 4 + .../questionView/QuestionGenerator.js | 12 +- .../components/questionView/QuestionView.js | 1 + 11 files changed, 276 insertions(+), 33 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 3eb2b382..983670c6 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -111,6 +111,27 @@ app.post('/record', async(req, res) => { } }); +app.get('/record/ranking/top10', async(req, res)=>{ + try { + // Forward the record request to the record service + const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/top10'); + res.json(recordResponse.data); + } catch (error) { + res.send(error); + } +}); + +app.get('/record/ranking/:user', async(req, res)=>{ + try { + const user = req.params.user; + // Forward the record request to the record service + const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user); + res.json(recordResponse.data); + } catch (error) { + res.send(error); + } +}); + app.get('/record/:user', async(req, res)=>{ try { const user = req.params.user; diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 8a4b0469..57c63816 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -37,6 +37,12 @@ describe('Gateway Service', () => { } else if(url.endsWith('/record/testuser')){ //Dont need to check a good record just that it redirects the call return Promise.resolve({data : {record:'undefined'}}) + } else if(url.endsWith('/record/ranking/top10')){ + //Dont need to check a good record just that it redirects the call + return Promise.resolve({data : {record:'undefined'}}) + } else if(url.endsWith('/record/ranking/testuser')){ + //Dont need to check a good record just that it redirects the call + return Promise.resolve({data : {record:'undefined'}}) } }); @@ -114,4 +120,22 @@ describe('Gateway Service', () => { expect(response.statusCode).toBe(200); expect(response.body).toHaveProperty('record', "undefined"); }); + + // Test /record/ranking/:user endpoint + it('should forward record request to record service', async () => { + const response = await request(app) + .get('/record/ranking/testuser'); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('record', "undefined"); + }); + + // Test /record/ranking/top10 endpoint + it('should forward record request to record service', async () => { + const response = await request(app) + .get('/record/ranking/top10'); + + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('record', "undefined"); + }); }); \ No newline at end of file diff --git a/questionservice/question-service.js b/questionservice/question-service.js index 12f4c7ca..effc5ac7 100644 --- a/questionservice/question-service.js +++ b/questionservice/question-service.js @@ -33,15 +33,15 @@ app.get('/questions', async (req, res) => { app.get('/questions/:lang/:amount/:type', async (req, res) => { try { const lang = req.params.lang.toString(); - let amount = parseInt(req.params.amount); + let amount = checkAmount(parseInt(req.params.amount)); const type = req.params.type.toString(); - if(amount > 20) - amount = 20; + if(amount > 20 || amount < 1) + amount = 5; const questions = await Question.aggregate([ {$match: {language : lang, type: type}}, //Condition - {$sample: {size:amount}} //5 random from the ones that fullfil the condition + {$sample: {size:amount}} ]); let jsonResult = {}; @@ -61,14 +61,13 @@ app.get('/questions/:lang/:amount/:type', async (req, res) => { app.get('/questions/:lang/:amount', async (req, res) => { try { const lang = req.params.lang; - let amount = parseInt(req.params.amount); + let amount = checkAmount(parseInt(req.params.amount)); - if(amount > 20) - amount = 20; + const questions = await Question.aggregate([ {$match: {language : lang}}, //Condition - {$sample: {size:amount}} //5 random from the ones that fullfil the condition + {$sample: {size:amount}} ]); let jsonResult = {}; @@ -108,6 +107,11 @@ app.get('/questions/:lang', async (req, res) => { } }); +function checkAmount(amount){ + if(amount > 20 || amount < 1) + return 5; + return amount; +} const server = app.listen(port, () => { console.log(`Question Service listening at http://localhost:${port}`); diff --git a/questionservice/question-service.test.js b/questionservice/question-service.test.js index 6f68c54d..35b70eae 100644 --- a/questionservice/question-service.test.js +++ b/questionservice/question-service.test.js @@ -61,22 +61,39 @@ describe('Question Service', () => { expect(Object.keys(response.body).length).toBe(5); }); - it('Should give 10 questions /questions/es/10', async () => { - let response = await request(app).get('/questions/es/10'); + it('Should give 20 questions /questions/es/20', async () => { + + let response = await request(app).get('/questions/es/20'); expect(response.status).toBe(200); expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); - expect(Object.keys(response.body).length).toBe(10); + expect(Object.keys(response.body).length).toBe(20); }); - it('Should give 20 questions as the max is 20 /questions/es/21', async () => { + it('Should give 1 questions /questions/es/1', async () => { - let response = await request(app).get('/questions/es/21'); + let response = await request(app).get('/questions/es/20'); expect(response.status).toBe(200); expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); expect(Object.keys(response.body).length).toBe(20); }); + it('Should give 5 questions as the max is 20 /questions/es/21', async () => { + + let response = await request(app).get('/questions/es/21'); + expect(response.status).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + expect(Object.keys(response.body).length).toBe(5); + }); + + it('Should give 5 questions as the min is 1 /questions/es/0', async () => { + + let response = await request(app).get('/questions/es/0'); + expect(response.status).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + expect(Object.keys(response.body).length).toBe(5); + }); + it('Should give 10 questions /questions/es/10/POPULATION', async () => { let response = await request(app).get('/questions/es/10/POPULATION'); diff --git a/users/recordservice/record-model.js b/users/recordservice/record-model.js index a7a30d76..a302df52 100644 --- a/users/recordservice/record-model.js +++ b/users/recordservice/record-model.js @@ -1,16 +1,18 @@ const mongoose = require('mongoose'); +const { Schema } = mongoose; -const recordSchema = new mongoose.Schema({ - user: String, +const recordSchema = new Schema({ + user: { type: String, required: true }, games: [{ questions: [{ - question: String, - answers: [String], - answerGiven: String, - correctAnswer: String + question: { type: String, required: true }, + answers: { type: [String], required: true }, + answerGiven: { type: String, required: true }, + correctAnswer: { type: String, required: true } }], - points: Number, - date: String + points: { type: Number, required: true }, + date: { type: String, required: true }, + competitive: { type: Boolean, required: true } }] }); const Record = mongoose.model('Record', recordSchema); diff --git a/users/recordservice/record-service.js b/users/recordservice/record-service.js index dd383984..4528a2b6 100644 --- a/users/recordservice/record-service.js +++ b/users/recordservice/record-service.js @@ -1,6 +1,5 @@ const express = require('express'); const mongoose = require('mongoose'); -const bcrypt = require('bcrypt'); const bodyParser = require('body-parser'); const Record = require('./record-model') @@ -18,14 +17,12 @@ mongoose.connect(mongoUri); app.post('/record', async (req, res) => { const user = req.body.user; const game = req.body.game; - console.log(user) - console.log(game) if(user && game){ let record = await Record.findOne({ user : user }); if(record){ //If it exits record.games.push(game); } - else{ //Lo creamos + else{ //We make it record = new Record({ user:user, games:[game] @@ -45,9 +42,62 @@ app.post('/record', async (req, res) => { }); +app.get('/record/ranking/top10', async (req, res) => { + try { + const usersCompetitiveStats = await Record.aggregate([ + // Unwind the games array to work with each game separately + { $unwind: "$games" }, + // Match only competitive games + { $match: { "games.competitive": true } }, + // Group by user and calculate total points and total competitive games per user + { + $group: { + _id: "$user", + totalPoints: { $sum: "$games.points" }, + totalCompetitiveGames: { $sum: 1 } // Count the number of competitive games + } + }, + // Sort by total points in descending order (top 1 will have the highest points) + { $sort: { totalPoints: -1 } }, + // Limit to the top 10 + { $limit: 10 } + ]); + + res.json({usersCompetitiveStats: usersCompetitiveStats }); + } catch (err) { + res.status(500).send(); + } +}); + +app.get('/record/ranking/:user', async (req, res) => { + try { + const user = req.params.user.toString(); + //Gives back an array of 1 user + const userCompetitiveStats = await Record.aggregate([ + { $match: { user: user } }, + // Unwind the games array to work with each game separately + { $unwind: "$games" }, + // Match only competitive games + { $match: { "games.competitive": true } }, + // Group by user and calculate total points and total competitive games per user + { + $group: { + _id: "$user", + totalPoints: { $sum: "$games.points" }, + totalCompetitiveGames: { $sum: 1 } // Count the number of competitive games + } + } + ]); + + res.json({userCompetitiveStats: userCompetitiveStats[0] }); + } catch (err) { + res.status(500).send(); + } +}); + app.get('/record/:user', async (req, res) => { try { - const recordFound = await Record.findOne({ user: req.params.user }, 'games'); + const recordFound = await Record.findOne({ user: req.params.user.toString() }, 'games'); if (!recordFound) { res.json({record: "undefined" }); } else { diff --git a/users/recordservice/record-service.test.js b/users/recordservice/record-service.test.js index d2c2ca6e..0346bbc2 100644 --- a/users/recordservice/record-service.test.js +++ b/users/recordservice/record-service.test.js @@ -1,5 +1,6 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); +const Record = require('./record-model') let mongoServer; let app; @@ -9,6 +10,8 @@ beforeAll(async () => { const mongoUri = mongoServer.getUri(); process.env.MONGODB_URI = mongoUri; app = require('./record-service'); + + await populateDatabase(); }); afterAll(async () => { @@ -99,6 +102,33 @@ describe('Record Service', () => { response = await request(app).post('/record').send(newUser); expect(response.status).toBe(400); + //Data lacks competitive field + newUser = { + user:"testuser", + game: + { + "questions": [ + { + "question": "¿Cuál es el río más largo del mundo?", + "answers": ["Nilo", "Amazonas", "Yangtsé", "Misisipi"], + "answerGiven": "Amazonas", + "correctAnswer": "Amazonas" + }, + { + "question": "¿Cuál es el elemento más abundante en la corteza terrestre?", + "answers": ["Hierro", "Oxígeno", "Silicio", "Aluminio"], + "answerGiven": "Oxígeno", + "correctAnswer": "Oxígeno" + } + ], + "points": 2500, + "date": "02/02/24" + } + }; + + response = await request(app).post('/record').send(newUser); + expect(response.status).toBe(500); + }); it('should add a new record on POST /record', async () => { const newUser = { @@ -120,7 +150,8 @@ describe('Record Service', () => { } ], "points": 2500, - "date": "02/02/24" + "date": "02/02/24", + "competitive": false } }; @@ -161,7 +192,8 @@ describe('Record Service', () => { } ], "points": 3000, - "date": "03/03/24" + "date": "03/03/24", + "competitive": false } }; @@ -174,4 +206,78 @@ describe('Record Service', () => { expect(responseGet.body.record.games[1]).toHaveProperty('date', '03/03/24'); }); -}); \ No newline at end of file + + it('should get back on GET /record/testuser', async () => { + const responseGet = await request(app).get('/record/testuser'); + expect(responseGet.status).toBe(200); + expect(responseGet.body.record.games[0]).toHaveProperty('date', '02/02/24'); + }); + + it('should get back on GET /record/testuser', async () => { + const responseGet = await request(app).get('/record/testuser'); + expect(responseGet.status).toBe(200); + expect(responseGet.body.record.games[0]).toHaveProperty('date', '02/02/24'); + }); + + it('should get back on GET /record/ranking/top10', async () => { + const responseGet = await request(app).get('/record/ranking/top10'); + console.log(responseGet.body) + expect(responseGet.status).toBe(200); + const usersStats = responseGet.body.usersCompetitiveStats; + expect(usersStats.length).toBe(10); //Only top 10 + + //Ordered by points + expect(usersStats[0]).toHaveProperty('_id', 'user10'); + expect(usersStats[9]).toHaveProperty('_id', 'user1'); + + expect(usersStats[0]).toHaveProperty('totalCompetitiveGames', 2); + expect(usersStats[0]).toHaveProperty('totalPoints', 200); + }); + + it('should get back on GET /record/ranking/user1', async () => { + const responseGet = await request(app).get('/record/ranking/user1'); + expect(responseGet.status).toBe(200); + const userStats = responseGet.body.userCompetitiveStats; + + expect(userStats).toHaveProperty('_id', 'user1'); + expect(userStats).toHaveProperty('totalCompetitiveGames', 2); + expect(userStats).toHaveProperty('totalPoints', 20); //i * 10 * totalCompetitiveGames , i = 2 + }); +}); + + + +async function populateDatabase() { + try { + // Generate 10 users + for (let i = 1; i <= 10; i++) { + const user = `user${i}`; + const games = []; + + // Generate 3 games for each user + for (let j = 1; j <= 3; j++) { + const game = { + questions: [ + { + question: `Question ${j} for ${user}`, + answers: ["Answer 1", "Answer 2", "Answer 3", "Answer 4"], + answerGiven: "Answer 1", + correctAnswer: "Answer 1" + } + ], + points: i * 10, + date: "04/01/2024", + competitive: j <= 2 ? true : false // Only 2 games are competitive + }; + games.push(game); + } + + // Guardar el usuario en la base de datos + await Record.create({ user, games }); + } + + console.log('Database populated successfully'); + } catch (error) { + console.error('Error populating database:', error); + } +} \ No newline at end of file diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index c240853e..5b837321 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -8,6 +8,16 @@ class HistoryRecordRetriever{ } async getRecords(user) { + /* + try { + const response = await axios.get(this.apiUrl + '/' + user); + const receivedRecords = await response.data; + return receivedRecords.record; + } catch (error) { + console.log(error) + throw new Error(error); + + }*/ return { userId: user, games: [ diff --git a/webapp/src/components/questionView/CreationHistoricalRecord.js b/webapp/src/components/questionView/CreationHistoricalRecord.js index 9da8f624..68ceb4f5 100644 --- a/webapp/src/components/questionView/CreationHistoricalRecord.js +++ b/webapp/src/components/questionView/CreationHistoricalRecord.js @@ -29,6 +29,10 @@ class CreationHistoricalRecord{ this.record.game.date = date; } + setCompetitive(isCompetitive){ + this.record.game.competitive = isCompetitive; + } + getRecord() { return this.record; } diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 8ea479e8..2add8f05 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,6 +9,7 @@ class QuestionGenerator{ } async generateQuestions(lang, type, amount) { + try { //const response = await fetch(this.apiUrl); //const receivedQuestions = await response.json(); @@ -33,13 +34,15 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } - + + /* try { + let response; if(type==="COMPETITIVE"){ - const response = await axios.get(this.apiUrl + '/' + lang); + response = await axios.get(this.apiUrl + '/' + lang); }else{ - const response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type); + response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type); } const receivedQuestions = await response.data; let i = 0; @@ -51,7 +54,8 @@ class QuestionGenerator{ return questions; } catch (error) { throw new Error(error); - }*/ + } + */ } } diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 156e3828..37ebfc3f 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -91,6 +91,7 @@ function QuestionView({type= "COMPETITIVE", amount=5}){ //Last question sends the record if(!(numQuestion < questions.length - 1)){ + creationHistoricalRecord.setCompetitive(type === 'COMPETITIVE'); creationHistoricalRecord.setDate(Date.now()); creationHistoricalRecord.setPoints(points); creationHistoricalRecord.sendRecord(user.username); From a2b2f6a40a7cb9b1c79887720c1da2b3bda30c04 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 22:58:40 +0200 Subject: [PATCH 29/74] Removed console log --- users/recordservice/record-service.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/users/recordservice/record-service.test.js b/users/recordservice/record-service.test.js index 0346bbc2..a42d1978 100644 --- a/users/recordservice/record-service.test.js +++ b/users/recordservice/record-service.test.js @@ -221,7 +221,6 @@ describe('Record Service', () => { it('should get back on GET /record/ranking/top10', async () => { const responseGet = await request(app).get('/record/ranking/top10'); - console.log(responseGet.body) expect(responseGet.status).toBe(200); const usersStats = responseGet.body.usersCompetitiveStats; expect(usersStats.length).toBe(10); //Only top 10 From 9451f8787c6ac1925cd8e203b8f5fc01ded98464 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 23:04:15 +0200 Subject: [PATCH 30/74] Commented mocks in order to check sonar cloud evaluation --- .../components/HistoricalData/HistoryRecordRetriever.js | 7 ++++--- webapp/src/components/questionView/QuestionGenerator.js | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index 5b837321..86fad6d7 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -8,7 +8,7 @@ class HistoryRecordRetriever{ } async getRecords(user) { - /* + try { const response = await axios.get(this.apiUrl + '/' + user); const receivedRecords = await response.data; @@ -17,7 +17,8 @@ class HistoryRecordRetriever{ console.log(error) throw new Error(error); - }*/ + } + /* return { userId: user, games: [ @@ -82,7 +83,7 @@ class HistoryRecordRetriever{ date: "03/02/24" } ] - }; + };*/ } diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 2add8f05..b2c278e3 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,7 +9,7 @@ class QuestionGenerator{ } async generateQuestions(lang, type, amount) { - + /* try { //const response = await fetch(this.apiUrl); //const receivedQuestions = await response.json(); @@ -34,9 +34,9 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } + */ - /* try { let response; if(type==="COMPETITIVE"){ @@ -55,7 +55,7 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } - */ + } } From ccfd4833832f2b881d1f27be8b0cfe38a3f485be Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 23:19:03 +0200 Subject: [PATCH 31/74] Fixed a bug that was already solved in another branch --- webapp/src/components/HistoricalData/HistoricalView.js | 3 ++- webapp/src/components/HistoricalData/HistoryRecordRetriever.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/HistoricalData/HistoricalView.js b/webapp/src/components/HistoricalData/HistoricalView.js index 9812acc7..266efe26 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.js +++ b/webapp/src/components/HistoricalData/HistoricalView.js @@ -16,9 +16,10 @@ export default function HistoricalView() { const getRecords = async ()=>{ try { - var jsonRecords = await retriever.getRecords("user"); + var jsonRecords = await retriever.getRecords(user.username); var recordsArray = jsonRecords.games; setRecords(recordsArray); + console.log(recordsArray) } catch (error) { console.log(error); } diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index 86fad6d7..02456549 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -8,7 +8,7 @@ class HistoryRecordRetriever{ } async getRecords(user) { - + console.log(user) try { const response = await axios.get(this.apiUrl + '/' + user); const receivedRecords = await response.data; From 18636fe0feab6f401bca92654aa2b1dd216cded1 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 23:19:30 +0200 Subject: [PATCH 32/74] Removed console log --- webapp/src/components/HistoricalData/HistoricalView.js | 1 - webapp/src/components/HistoricalData/HistoryRecordRetriever.js | 1 - 2 files changed, 2 deletions(-) diff --git a/webapp/src/components/HistoricalData/HistoricalView.js b/webapp/src/components/HistoricalData/HistoricalView.js index 266efe26..66dc8a2d 100644 --- a/webapp/src/components/HistoricalData/HistoricalView.js +++ b/webapp/src/components/HistoricalData/HistoricalView.js @@ -19,7 +19,6 @@ export default function HistoricalView() { var jsonRecords = await retriever.getRecords(user.username); var recordsArray = jsonRecords.games; setRecords(recordsArray); - console.log(recordsArray) } catch (error) { console.log(error); } diff --git a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js index 02456549..0e65d6ff 100644 --- a/webapp/src/components/HistoricalData/HistoryRecordRetriever.js +++ b/webapp/src/components/HistoricalData/HistoryRecordRetriever.js @@ -8,7 +8,6 @@ class HistoryRecordRetriever{ } async getRecords(user) { - console.log(user) try { const response = await axios.get(this.apiUrl + '/' + user); const receivedRecords = await response.data; From ab9b42c7e3044deeeb769b6f753089389b7f27f4 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 23:30:27 +0200 Subject: [PATCH 33/74] Refactored the gateway-service tests for removing duplicated code --- gatewayservice/gateway-service.js | 12 ++--- gatewayservice/gateway-service.test.js | 64 +++++++++++++------------- 2 files changed, 39 insertions(+), 37 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 983670c6..317c55ae 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -60,9 +60,9 @@ app.get('/questions', async (req, res) => { app.get('/questions/:lang/:amount/:type', async (req, res) => { try { - const lang = req.params.lang; - const amount = req.params.amount; - const type = req.params.type; + const lang = req.params.lang.toString(); + const amount = req.params.amount.toString(); + const type = req.params.type.toString(); // Forward the question request to the quetion service const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type); @@ -76,8 +76,8 @@ app.get('/questions/:lang/:amount/:type', async (req, res) => { app.get('/questions/:lang/:amount', async (req, res) => { try { - const lang = req.params.lang; - const amount = req.params.amount; + const lang = req.params.lang.toString(); + const amount = req.params.amount.toString(); // Forward the question request to the quetion service const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount); @@ -90,7 +90,7 @@ app.get('/questions/:lang/:amount', async (req, res) => { app.get('/questions/:lang', async (req, res) => { try { - const lang = req.params.lang; + const lang = req.params.lang.toString(); // Forward the question request to the quetion service const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang); diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 57c63816..971fead9 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -20,29 +20,28 @@ describe('Gateway Service', () => { } }); + const question = { data: [{question: "¿Cuál es la población de Oviedo?", + answers: ["225089","272357","267855","231841"]}] }; + + //Dont need to check a good record just that it redirects the call + const record = {data : {record:'undefined'}}; + axios.get.mockImplementation((url, data) => { if (url.endsWith('/questions')){ - return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", - answers: ["225089","272357","267855","231841"]}] }); + return Promise.resolve(question); } else if (url.endsWith('/questions/es/1/CAPITAL')){ - return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", - answers: ["225089","272357","267855","231841"]}] }); - + return Promise.resolve(question); } else if (url.endsWith('/questions/es/1')){ - return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", - answers: ["225089","272357","267855","231841"]}] }); + return Promise.resolve(question); } else if (url.endsWith('/questions/es')){ - return Promise.resolve({ data: [{question: "¿Cuál es la población de Oviedo?", - answers: ["225089","272357","267855","231841"]}] }); + return Promise.resolve(question); + } else if(url.endsWith('/record/testuser')){ - //Dont need to check a good record just that it redirects the call - return Promise.resolve({data : {record:'undefined'}}) + return Promise.resolve(record) } else if(url.endsWith('/record/ranking/top10')){ - //Dont need to check a good record just that it redirects the call - return Promise.resolve({data : {record:'undefined'}}) + return Promise.resolve(record) } else if(url.endsWith('/record/ranking/testuser')){ - //Dont need to check a good record just that it redirects the call - return Promise.resolve({data : {record:'undefined'}}) + return Promise.resolve(record) } }); @@ -72,8 +71,7 @@ describe('Gateway Service', () => { const response = await request(app) .get('/questions'); - expect(response.statusCode).toBe(200); - expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + checkQuestion(response); }); // Test /questions/:lang endpoint @@ -81,8 +79,7 @@ describe('Gateway Service', () => { const response = await request(app) .get('/questions/es'); - expect(response.statusCode).toBe(200); - expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + checkQuestion(response); }); // Test /questions/:lang/:amount endpoint @@ -90,8 +87,7 @@ describe('Gateway Service', () => { const response = await request(app) .get('/questions/es/1'); - expect(response.statusCode).toBe(200); - expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + checkQuestion(response); }); // Test /questions/:lang/:amount/:type endpoint @@ -99,8 +95,7 @@ describe('Gateway Service', () => { const response = await request(app) .get('/questions/es/1/CAPITAL'); - expect(response.statusCode).toBe(200); - expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); + checkQuestion(response); }); // Test /record endpoint @@ -117,8 +112,7 @@ describe('Gateway Service', () => { const response = await request(app) .get('/record/testuser'); - expect(response.statusCode).toBe(200); - expect(response.body).toHaveProperty('record', "undefined"); + checkRecord(response); }); // Test /record/ranking/:user endpoint @@ -126,16 +120,24 @@ describe('Gateway Service', () => { const response = await request(app) .get('/record/ranking/testuser'); - expect(response.statusCode).toBe(200); - expect(response.body).toHaveProperty('record', "undefined"); + checkRecord(response); }); // Test /record/ranking/top10 endpoint it('should forward record request to record service', async () => { const response = await request(app) .get('/record/ranking/top10'); - - expect(response.statusCode).toBe(200); - expect(response.body).toHaveProperty('record', "undefined"); + checkRecord(response); + }); -}); \ No newline at end of file +}); + +function checkRecord(response){ + expect(response.statusCode).toBe(200); + expect(response.body).toHaveProperty('record', "undefined"); +} + +function checkQuestion(response){ + expect(response.statusCode).toBe(200); + expect(response.body[0]).toHaveProperty('question', "¿Cuál es la población de Oviedo?"); +} \ No newline at end of file From 88c100db3a8c632ab320abc81a2507ea94d04dfb Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 15 Apr 2024 23:51:43 +0200 Subject: [PATCH 34/74] Removed unused hook for e2e tests --- webapp/src/components/GameConfigurator/GameConfigurator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js index 1c592294..582f6d13 100644 --- a/webapp/src/components/GameConfigurator/GameConfigurator.js +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -9,7 +9,7 @@ function GameConfigurator(){ const [tipoPregunta, setTipoPregunta] = useState('POPULATION'); const [numeroPreguntas, setNumeroPreguntas] = useState(5); const [clickedForNewGame, setClickedForNewGame]= useState(false); - const[t, i18n] = useTranslation("global"); + const[t] = useTranslation("global"); function handleClick() { setClickedForNewGame(true); From 3aea4680d04ebad6b06759cb5da3e07cee941fdd Mon Sep 17 00:00:00 2001 From: sinne10 Date: Tue, 16 Apr 2024 15:40:35 +0200 Subject: [PATCH 35/74] Missing translation --- webapp/src/translations/en/global.json | 1 + webapp/src/translations/es/global.json | 1 + webapp/src/translations/tk/global.json | 1 + 3 files changed, 3 insertions(+) diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 154bdfa0..60fd49fa 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -57,6 +57,7 @@ "strong_password": "Strong password", "error_passwords_no_match": "Passwords do not match", "error_password_spaces": "Password cannot contain spaces", + "error_username_spaces": "Username cannot contain spaces", "error_password_minimum_length": "Password must be at least 8 characters long", "error_password_maximum_length": "Password cannot be over 64 characters long", "error_username_in_use": "Username already in use" diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 1c0a5f1b..6c6abaa9 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -61,6 +61,7 @@ "strong_password": "Contraseña fuerte", "error_passwords_no_match": "Las contraseñas no coinciden", "error_password_spaces": "La contraseña no puede contener espacios", + "error_username_spaces": "El nombre de usuario no puede contener espacios", "error_password_minimum_length": "La contraseña debe tener al menos 8 caracteres", "error_password_maximum_length": "La contraseña no debe tener más de 64 caracteres", "error_username_in_use": "Nombre de usuario no disponible" diff --git a/webapp/src/translations/tk/global.json b/webapp/src/translations/tk/global.json index d79e6e10..36d4dd1c 100644 --- a/webapp/src/translations/tk/global.json +++ b/webapp/src/translations/tk/global.json @@ -57,6 +57,7 @@ "strong_password": "Güçlü şifre", "error_passwords_no_match": "Şifreler eşleşmiyor", "error_password_spaces": "Şifre boşluk içeremez", + "error_username_spaces": "Kullanıcı adı boşluk içeremez.", "error_password_minimum_length": "Şifre en az 8 karakter uzunluğunda olmalıdır", "error_password_maximum_length": "Şifre en fazla 64 karakter uzunluğunda olabilir", "error_username_in_use": "Kullanıcı adı zaten kullanımda" From 699f57d66f7d1a564a10448ae35ed2762f471023 Mon Sep 17 00:00:00 2001 From: sinne10 Date: Tue, 16 Apr 2024 16:46:58 +0200 Subject: [PATCH 36/74] Added tests --- .../loginAndRegistration/AddUser.test.js | 153 +++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js index 8cf04089..56cbfb5f 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.test.js +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -1,14 +1,19 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import AddUser from './AddUser'; -import { BrowserRouter as Router } from 'react-router-dom'; +import axios from 'axios'; +import { BrowserRouter as Router } from 'react-router-dom'; // Mocking useTranslation hook jest.mock('react-i18next', () => ({ useTranslation: () => ({ t: key => key }), })); +// Mocking axios to simulate an error response +jest.mock('axios'); + describe('', () => { + test('renders the AddUser component', () => { render( @@ -24,6 +29,150 @@ describe('', () => { expect(screen.getByText('addUser.login_link')).toBeInTheDocument(); }); + + test('displays error message when passwords do not match', () => { + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'username' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '12345678' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '123456789' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + expect(screen.getByText('addUser.error_passwords_no_match')).toBeInTheDocument(); + }); + + test('displays error message when spaces in password', () => { + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'username' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '1234 5678' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '1234 5678' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + expect(screen.getByText('addUser.error_password_spaces')).toBeInTheDocument(); + }); + + test('displays error message when passwords too short', () => { + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'username' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '1234567' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '1234567' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + expect(screen.getByText('addUser.error_password_minimum_length')).toBeInTheDocument(); + }); + + test('displays error message when passwords too long', () => { + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'username' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '01234567890123456789012345678901234567890123456789012345678901234' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '01234567890123456789012345678901234567890123456789012345678901234' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + expect(screen.getByText('addUser.error_password_maximum_length')).toBeInTheDocument(); + }); + + test('displays error message when spaces in username', () => { + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'user name' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '12345678' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '12345678' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument(); + }); + + test('displays error message when username is already in use', async () => { + + // Mock axios post method to simulate response for username already in use + axios.post.mockRejectedValue({ response: { data: { error: 'Username already in use' } } }); + + render( + + + + ); + + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); + fireEvent.change(usernameInput, { target: { value: 'existing_user' } }); + + const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); + fireEvent.change(passwordInput, { target: { value: '12345678' } }); + + const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); + fireEvent.change(repeatPasswordInput, { target: { value: '12345678' } }); + + const submitButton = screen.getByText('addUser.register_button'); + fireEvent.click(submitButton); + + // Wait for the asynchronous axios call to be completed + await waitFor(() => { + expect(screen.getByText('addUser.error_username_in_use')).toBeInTheDocument(); + }); + + // Ensure axios.post is called with the correct data + expect(axios.post).toHaveBeenCalledWith(expect.any(String), { username: 'existing_user', password: '12345678' }); + + }); + }); From 46c3e9293c120dcb97fec83ff9d78633eaadaaac Mon Sep 17 00:00:00 2001 From: sinne10 Date: Tue, 16 Apr 2024 16:59:22 +0200 Subject: [PATCH 37/74] Fixed duplicated code --- .../loginAndRegistration/AddUser.test.js | 124 +++--------------- 1 file changed, 16 insertions(+), 108 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js index 56cbfb5f..ca05c287 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.test.js +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -14,12 +14,15 @@ jest.mock('axios'); describe('', () => { - test('renders the AddUser component', () => { + beforeEach(() => { render( ); + }); + + test('renders the AddUser component', () => { expect(screen.getByText('addUser.title')).toBeInTheDocument(); expect(screen.getByText('addUser.username_placeholder:')).toBeInTheDocument(); @@ -30,147 +33,52 @@ describe('', () => { }); - test('displays error message when passwords do not match', () => { - render( - - - - ); - + const fillFormAndSubmit = (username, password, repeatPassword) => { const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'username' } }); + fireEvent.change(usernameInput, { target: { value: username } }); const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '12345678' } }); + fireEvent.change(passwordInput, { target: { value: password } }); const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '123456789' } }); + fireEvent.change(repeatPasswordInput, { target: { value: repeatPassword } }); const submitButton = screen.getByText('addUser.register_button'); fireEvent.click(submitButton); + }; + test('displays error message when passwords do not match', () => { + fillFormAndSubmit('username', '12345678', '123456789'); expect(screen.getByText('addUser.error_passwords_no_match')).toBeInTheDocument(); }); test('displays error message when spaces in password', () => { - render( - - - - ); - - const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'username' } }); - - const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '1234 5678' } }); - - const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '1234 5678' } }); - - const submitButton = screen.getByText('addUser.register_button'); - fireEvent.click(submitButton); - + fillFormAndSubmit('username', '1234 5678', '1234 5678'); expect(screen.getByText('addUser.error_password_spaces')).toBeInTheDocument(); }); test('displays error message when passwords too short', () => { - render( - - - - ); - - const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'username' } }); - - const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '1234567' } }); - - const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '1234567' } }); - - const submitButton = screen.getByText('addUser.register_button'); - fireEvent.click(submitButton); - + fillFormAndSubmit('username', '1234567', '1234567'); expect(screen.getByText('addUser.error_password_minimum_length')).toBeInTheDocument(); }); test('displays error message when passwords too long', () => { - render( - - - - ); - - const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'username' } }); - - const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '01234567890123456789012345678901234567890123456789012345678901234' } }); - - const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '01234567890123456789012345678901234567890123456789012345678901234' } }); - - const submitButton = screen.getByText('addUser.register_button'); - fireEvent.click(submitButton); - + fillFormAndSubmit('username', '01234567890123456789012345678901234567890123456789012345678901234', '01234567890123456789012345678901234567890123456789012345678901234'); expect(screen.getByText('addUser.error_password_maximum_length')).toBeInTheDocument(); }); test('displays error message when spaces in username', () => { - render( - - - - ); - - const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'user name' } }); - - const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '12345678' } }); - - const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '12345678' } }); - - const submitButton = screen.getByText('addUser.register_button'); - fireEvent.click(submitButton); - + fillFormAndSubmit('user name', '12345678', '12345678'); expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument(); }); test('displays error message when username is already in use', async () => { - - // Mock axios post method to simulate response for username already in use axios.post.mockRejectedValue({ response: { data: { error: 'Username already in use' } } }); - - render( - - - - ); - - const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); - fireEvent.change(usernameInput, { target: { value: 'existing_user' } }); - - const passwordInput = screen.getByPlaceholderText('addUser.password_placeholder'); - fireEvent.change(passwordInput, { target: { value: '12345678' } }); - - const repeatPasswordInput = screen.getByPlaceholderText('addUser.repeat_password_placeholder'); - fireEvent.change(repeatPasswordInput, { target: { value: '12345678' } }); - - const submitButton = screen.getByText('addUser.register_button'); - fireEvent.click(submitButton); - - // Wait for the asynchronous axios call to be completed + fillFormAndSubmit('existing_user', '12345678', '12345678'); await waitFor(() => { expect(screen.getByText('addUser.error_username_in_use')).toBeInTheDocument(); }); - - // Ensure axios.post is called with the correct data expect(axios.post).toHaveBeenCalledWith(expect.any(String), { username: 'existing_user', password: '12345678' }); - }); }); From aeda7db304b5e4a4e74cf4bfc84f9f7c2f3e7bf0 Mon Sep 17 00:00:00 2001 From: sinne10 Date: Tue, 16 Apr 2024 17:21:33 +0200 Subject: [PATCH 38/74] Refactor --- .../loginAndRegistration/AddUser.test.js | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js index ca05c287..70493840 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.test.js +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -47,32 +47,23 @@ describe('', () => { fireEvent.click(submitButton); }; - test('displays error message when passwords do not match', () => { + test('displays correct error messages', async () => { + //Passwords do not match fillFormAndSubmit('username', '12345678', '123456789'); expect(screen.getByText('addUser.error_passwords_no_match')).toBeInTheDocument(); - }); - - test('displays error message when spaces in password', () => { + //Password with spaces fillFormAndSubmit('username', '1234 5678', '1234 5678'); expect(screen.getByText('addUser.error_password_spaces')).toBeInTheDocument(); - }); - - test('displays error message when passwords too short', () => { + //Password too short fillFormAndSubmit('username', '1234567', '1234567'); expect(screen.getByText('addUser.error_password_minimum_length')).toBeInTheDocument(); - }); - - test('displays error message when passwords too long', () => { + //Password too long fillFormAndSubmit('username', '01234567890123456789012345678901234567890123456789012345678901234', '01234567890123456789012345678901234567890123456789012345678901234'); expect(screen.getByText('addUser.error_password_maximum_length')).toBeInTheDocument(); - }); - - test('displays error message when spaces in username', () => { + //Username with spaces fillFormAndSubmit('user name', '12345678', '12345678'); expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument(); - }); - - test('displays error message when username is already in use', async () => { + //Username in use axios.post.mockRejectedValue({ response: { data: { error: 'Username already in use' } } }); fillFormAndSubmit('existing_user', '12345678', '12345678'); await waitFor(() => { From 6a49ed9ad1ad51f2607ea972cc4d357e1e6c621d Mon Sep 17 00:00:00 2001 From: uo289267 Date: Tue, 16 Apr 2024 22:09:24 +0200 Subject: [PATCH 39/74] Added Ranking View need of style --- webapp/src/App.js | 2 + .../questionView/QuestionGenerator.js | 6 +- .../components/ranking/RankingRetriever.js | 83 +++++++++++++++++++ webapp/src/components/ranking/RankingView.js | 58 +++++++++++++ 4 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 webapp/src/components/ranking/RankingRetriever.js create mode 100644 webapp/src/components/ranking/RankingView.js diff --git a/webapp/src/App.js b/webapp/src/App.js index 9ed9bbde..cc14d644 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -12,6 +12,7 @@ import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; import { UserContextProvider } from './components/loginAndRegistration/UserContext'; import GameConfigurator from './components/GameConfigurator/GameConfigurator'; +import RankingView from './components/ranking/RankingView'; function App() { @@ -34,6 +35,7 @@ function App() { } /> } /> }/> + } />
diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index b2c278e3..2add8f05 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,7 +9,7 @@ class QuestionGenerator{ } async generateQuestions(lang, type, amount) { - /* + try { //const response = await fetch(this.apiUrl); //const receivedQuestions = await response.json(); @@ -34,9 +34,9 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } - */ + /* try { let response; if(type==="COMPETITIVE"){ @@ -55,7 +55,7 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } - + */ } } diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js new file mode 100644 index 00000000..c988f218 --- /dev/null +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -0,0 +1,83 @@ +import axios from 'axios'; + +class RankingRetriever{ + + constructor(){ + this.apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'); + + } + + async getTopTen() { + /* + try { + const response = await axios.get(this.apiUrl + '/top10');//finding the top ten + const receivedTopTenRanking = await response.data; + return receivedTopTenRanking.record; + } catch (error) { + console.log(error) + throw new Error(error); + + }*/ + console.log("getting top 10") + return { + "usersCompetitiveStats": [ + { + "_id": "user", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }, + { + "_id": "user2", + "totalPoints": 900, + "totalCompetitiveGames": 2 + }, + { + "_id": "user3", + "totalPoints": 800, + "totalCompetitiveGames": 3 + }, + { + "_id": "user4", + "totalPoints": 700, + "totalCompetitiveGames": 5 + }, + { + "_id": "user5", + "totalPoints": 600, + "totalCompetitiveGames": 6 + }, + { + "_id": "user6", + "totalPoints": 500, + "totalCompetitiveGames": 7 + }, + { + "_id": "user7", + "totalPoints": 400, + "totalCompetitiveGames": 8 + }, + { + "_id": "user8", + "totalPoints": 300, + "totalCompetitiveGames": 9 + }, + { + "_id": "user9", + "totalPoints": 200, + "totalCompetitiveGames": 10 + }, + { + "_id": "user10", + "totalPoints": 100, + "totalCompetitiveGames": 11 + } + ] + } + } + + + +} + +export default RankingRetriever; + diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js new file mode 100644 index 00000000..f00c5696 --- /dev/null +++ b/webapp/src/components/ranking/RankingView.js @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import RankingRetriever from './RankingRetriever'; +const retriever = new RankingRetriever(); + +const RankingView = () => { + const [rankingData, setRankingData] = useState(null); + + const getRanking = async () => { + try { + console.log("caalling") + var ranking = await retriever.getTopTen(); + setRankingData(ranking.usersCompetitiveStats); + console.log(ranking) + console.log(rankingData) + } catch (error) { + console.log(error); + } + } + + useEffect(() => { + getRanking(); + }, []); + + return ( +
+

Ranking

+ {console.log("DIV")} + {console.log(rankingData)} + {rankingData && rankingData.length > 0 ? ( + + + + + + + + + + + + {rankingData.map((user, index) => ( + + + + + + + ))} + +
PositionUsernamePointsNum of Games
{index + 1}{user._id}{user.totalPoints}{user.totalCompetitiveGames}
+ ) : ( +

Loading...

+ )} +
+ ); +}; + +export default RankingView; From c1783678e3b8027f2fa4efccb1c14abec93965fe Mon Sep 17 00:00:00 2001 From: uo289267 Date: Wed, 17 Apr 2024 13:00:05 +0200 Subject: [PATCH 40/74] Added ranking View --- webapp/src/components/GameMenu/GameMenu.js | 10 ++ webapp/src/components/fragments/Loader.js | 19 +++ .../components/ranking/RankingRetriever.js | 18 ++- webapp/src/components/ranking/RankingView.js | 38 +++--- webapp/src/custom.css | 117 ++++++++++++++++++ webapp/src/translations/en/global.json | 8 ++ webapp/src/translations/es/global.json | 8 ++ 7 files changed, 203 insertions(+), 15 deletions(-) create mode 100644 webapp/src/components/fragments/Loader.js diff --git a/webapp/src/components/GameMenu/GameMenu.js b/webapp/src/components/GameMenu/GameMenu.js index e0e65053..5e7f140e 100644 --- a/webapp/src/components/GameMenu/GameMenu.js +++ b/webapp/src/components/GameMenu/GameMenu.js @@ -11,6 +11,7 @@ export default function GameMenu() {

{t("gameMenu.title")}

+
); } @@ -24,4 +25,13 @@ export default function GameMenu() { ); } + function ButtonRanking({ t }) { + return ( + +

{t("gameMenu.view_ranking")}

+ + + ); + } + \ No newline at end of file diff --git a/webapp/src/components/fragments/Loader.js b/webapp/src/components/fragments/Loader.js new file mode 100644 index 00000000..ff092709 --- /dev/null +++ b/webapp/src/components/fragments/Loader.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const Loader = () => { + return ( +
+
+
Status
+
+
+
+
+
+
+
Loading...
+
+ ); +} + +export default Loader; diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index c988f218..0f04736e 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -18,7 +18,6 @@ class RankingRetriever{ throw new Error(error); }*/ - console.log("getting top 10") return { "usersCompetitiveStats": [ { @@ -74,6 +73,23 @@ class RankingRetriever{ ] } } + async getMyPosition(user){ + /* + try { + const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten + const receivedMyRanking = await response.data; + return receivedMyRanking.record; + } catch (error) { + console.log(error) + throw new Error(error); + + }*/ + return { + "_id": "user", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }; + } diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index f00c5696..c195c21c 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -1,17 +1,20 @@ import React, { useState, useEffect } from 'react'; import RankingRetriever from './RankingRetriever'; +import {useTranslation} from "react-i18next"; +import Loader from "../fragments/Loader" const retriever = new RankingRetriever(); const RankingView = () => { const [rankingData, setRankingData] = useState(null); - + const [myRankingData, setMyRankingData] = useState(null); + const[t] = useTranslation("global"); const getRanking = async () => { try { - console.log("caalling") var ranking = await retriever.getTopTen(); setRankingData(ranking.usersCompetitiveStats); - console.log(ranking) - console.log(rankingData) + var myrank = await retriever.getMyPosition(); + setMyRankingData(myrank); + console.log(myrank) } catch (error) { console.log(error); } @@ -22,19 +25,16 @@ const RankingView = () => { }, []); return ( -
-

Ranking

- {console.log("DIV")} - {console.log(rankingData)} +
+

{t("ranking.ranking")}

{rankingData && rankingData.length > 0 ? ( - - - - - + + + + @@ -46,10 +46,20 @@ const RankingView = () => { ))} + {/* Blank row */} + + + + + + + + +
PositionUsernamePointsNum of Games{t("ranking.position")}{t("ranking.username")}{t("ranking.points")}{t("ranking.num_games")}
{user.totalCompetitiveGames}
0{myRankingData._id}{myRankingData.totalPoints}{myRankingData.totalCompetitiveGames}
) : ( -

Loading...

+ < Loader /> )}
); diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 3c30da2f..f3aa99e9 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1300,6 +1300,98 @@ svg { h2{ text-align: center; } +/*------------------------Loader------------------------------*/ +@keyframes blinkCursor { + 50% { + border-right-color: transparent; + } +} + +@keyframes typeAndDelete { + 0%, + 10% { + width: 0; + } + 45%, + 55% { + width: 6.2em; + } /* adjust width based on content */ + 90%, + 100% { + width: 0; + } +} + +.terminal-loader { + border: 0.1em solid #333; + background-color: #1a1a1a; + color: #0f0; + font-family: "Courier New", Courier, monospace; + font-size: 1em; + padding: 1.5em 1em; + width: 12em; + margin: 100px auto; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + border-radius: 4px; + position: relative; + overflow: hidden; + box-sizing: border-box; +} + +.terminal-header { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1.5em; + background-color: #333; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding: 0 0.4em; + box-sizing: border-box; +} + +.terminal-controls { + float: right; +} + +.control { + display: inline-block; + width: 0.6em; + height: 0.6em; + margin-left: 0.4em; + border-radius: 50%; + background-color: #777; +} + +.control.close { + background-color: #e33; +} + +.control.minimize { + background-color: #ee0; +} + +.control.maximize { + background-color: #0b0; +} + +.terminal-title { + float: left; + line-height: 1.5em; + color: #eee; +} + +.text { + display: inline-block; + white-space: nowrap; + overflow: hidden; + border-right: 0.2em solid green; /* Cursor */ + animation: typeAndDelete 4s steps(11) infinite, + blinkCursor 0.5s step-end infinite alternate; + margin-top: 1.5em; +} + /*--------------------------------------------------Configurator---------------------------------*/ /* Estilo para el elemento select */ .select-style { @@ -1346,4 +1438,29 @@ h2{ margin-top:20px; margin-bottom:20px; } +.table tbody tr.penultimate-row td { + background-color: black; + padding: 25px; +} + +.table th,td{ + padding-left: 30px; + padding-right: 30px; + padding-top: 10px; + padding-bottom: 10px; + text-align: center; + +} +/* Estilo para todos los td excepto el penúltimo */ +.table tbody tr:not(:nth-last-child(2)) td:nth-child(1) { + background-color: #69276f; +} + + +.table tr:nth-child(even) {background-color: #f2f2f22a;} +.table th { + background-color: #69276f; + color: white; +} +.table tr:hover {background-color: #a59c9ca2;} diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 28c2a341..2c824262 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -55,6 +55,7 @@ "gameMenu":{ "history_button":"View Historical Data", "new_game_button":"Create New Game", + "view_ranking":"Ranking", "title":"Game Menu", "back":"Back" }, @@ -84,6 +85,13 @@ "option_size":"Size", "custo_game":"Create custom game", "competi_game":"Play Competitive" + }, + "ranking":{ + "ranking":"Ranking", + "position":"Position", + "username":"Username", + "points":"Points", + "num_games":"Num Competitive games" } } diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 32f9e22a..1692648c 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -59,6 +59,7 @@ "gameMenu":{ "history_button":"Ver Historial", "new_game_button":"Crear nuevo juego", + "view_ranking":"Ranking", "title":"Menú del Juego", "back":"Atrás" },"questionView":{ @@ -87,6 +88,13 @@ "option_size":"Extensión", "custo_game":"Crea una partida personalizada", "competi_game":"Juega en modo Competitivo" + }, + "ranking":{ + "ranking":"Ranking", + "position":"Posición", + "username":"Username", + "points":"Puntos", + "num_games":"Nº juegos Competitivos" } } From 99425849d4414b1df597108687685c424764b9dd Mon Sep 17 00:00:00 2001 From: uo289267 Date: Wed, 17 Apr 2024 13:05:38 +0200 Subject: [PATCH 41/74] Added back button to menu in ranking --- webapp/src/components/ranking/RankingView.js | 2 ++ webapp/src/custom.css | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index c195c21c..6f58043a 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -2,6 +2,7 @@ import React, { useState, useEffect } from 'react'; import RankingRetriever from './RankingRetriever'; import {useTranslation} from "react-i18next"; import Loader from "../fragments/Loader" +import BackButton from '../fragments/BackButtonToGameMenu'; const retriever = new RankingRetriever(); const RankingView = () => { @@ -26,6 +27,7 @@ const RankingView = () => { return (
+

{t("ranking.ranking")}

{rankingData && rankingData.length > 0 ? ( diff --git a/webapp/src/custom.css b/webapp/src/custom.css index f3aa99e9..d0349386 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1231,8 +1231,8 @@ svg { font-size: 1em; color: black; font-weight: 700; - margin-top: 3em; - margin-bottom: 3em; + margin-top: 1.5em; + margin-bottom: 1em; text-decoration: none; } @@ -1440,7 +1440,7 @@ h2{ } .table tbody tr.penultimate-row td { background-color: black; - padding: 25px; + padding: 15px; } .table th,td{ From 8c179b94f091ffc722d584ee558434caae9670db Mon Sep 17 00:00:00 2001 From: uo289267 Date: Wed, 17 Apr 2024 18:26:21 +0200 Subject: [PATCH 42/74] Added test for BackButton and RankingView (need finishing up) --- .../src/components/GameMenu/GameMenu.test.js | 6 + .../fragments/BackButtonToGameMenu.test.js | 31 ++ .../components/ranking/RankingRetriever.js | 8 +- .../components/ranking/RankingView.test.js | 336 ++++++++++++++++++ 4 files changed, 377 insertions(+), 4 deletions(-) create mode 100644 webapp/src/components/fragments/BackButtonToGameMenu.test.js create mode 100644 webapp/src/components/ranking/RankingView.test.js diff --git a/webapp/src/components/GameMenu/GameMenu.test.js b/webapp/src/components/GameMenu/GameMenu.test.js index 7d9b7e4d..a52ed153 100644 --- a/webapp/src/components/GameMenu/GameMenu.test.js +++ b/webapp/src/components/GameMenu/GameMenu.test.js @@ -33,6 +33,12 @@ describe('GameMenu component', () => { const text = screen.getByText(i18en.t('gameMenu.history_button')); expect(text).toBeInTheDocument(); }); + + it('renders option to view ranking data', () => { + render(); + const text = screen.getByText(i18en.t('gameMenu.view_ranking')); + expect(text).toBeInTheDocument(); + }); }); diff --git a/webapp/src/components/fragments/BackButtonToGameMenu.test.js b/webapp/src/components/fragments/BackButtonToGameMenu.test.js new file mode 100644 index 00000000..586cf695 --- /dev/null +++ b/webapp/src/components/fragments/BackButtonToGameMenu.test.js @@ -0,0 +1,31 @@ +import { render, screen } from '@testing-library/react'; +import BackButtonToGameMenu from './BackButtonToGameMenu'; +import { MemoryRouter } from 'react-router-dom'; + +import { initReactI18next } from 'react-i18next'; +import i18en from 'i18next'; + +i18en.use(initReactI18next).init({ + resources: {}, + lng: 'en', + interpolation:{ + escapeValue: false, + } +}); +global.i18en = i18en; + + +describe('BackButtonToGameMenu component', () => { + + it('renders option to go back to the game menu', () => { + render(); + const text = screen.getByText((content, element) => { + const regex = new RegExp(i18en.t("gameMenu.back")); + return regex.test(content); + }); + + expect(text).toBeInTheDocument(); + }); +}); + + diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index 0f04736e..b4f5b2b2 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -3,7 +3,7 @@ import axios from 'axios'; class RankingRetriever{ constructor(){ - this.apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000'); + this.apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000')+ "/record/ranking"; } @@ -85,9 +85,9 @@ class RankingRetriever{ }*/ return { - "_id": "user", - "totalPoints": 1000, - "totalCompetitiveGames": 4 + "_id": "myUser", + "totalPoints": 250, + "totalCompetitiveGames": 1 }; } diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js new file mode 100644 index 00000000..93236ca9 --- /dev/null +++ b/webapp/src/components/ranking/RankingView.test.js @@ -0,0 +1,336 @@ +import { render , screen, waitFor, fireEvent } from '@testing-library/react'; +import { MemoryRouter } from 'react-router-dom'; +import axios from 'axios'; +import { initReactI18next } from 'react-i18next'; +import i18en from 'i18next'; +import RankingView from './RankingView'; +import MockAdapter from 'axios-mock-adapter'; +import { act } from 'react-dom/test-utils'; +i18en.use(initReactI18next).init({ + resources: {}, + lng: 'en', + interpolation:{ + escapeValue: false, + } +}); +global.i18en = i18en; + +const mockAxios = new MockAdapter(axios); +describe('GameMenu component', () => { + beforeEach(() => { + mockAxios.reset(); + }); + + it('renders title', () => { + act(()=>{ + render(); + }) + const text = screen.getByText(i18en.t('ranking.ranking')); + expect(text).toBeInTheDocument(); + }); + it('renders Loading if the call to the gateway has not been done', () => { + act(()=>{ + render(); + }) + const text = screen.getByText('Loading...'); + expect(text).toBeInTheDocument(); + }); + it('renders position all headers in the table',async ()=>{ + mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, + { + "usersCompetitiveStats": [ + { + "_id": "user", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }, + { + "_id": "user2", + "totalPoints": 900, + "totalCompetitiveGames": 2 + }, + { + "_id": "user3", + "totalPoints": 800, + "totalCompetitiveGames": 3 + }, + { + "_id": "user4", + "totalPoints": 700, + "totalCompetitiveGames": 5 + }, + { + "_id": "user5", + "totalPoints": 600, + "totalCompetitiveGames": 6 + }, + { + "_id": "user6", + "totalPoints": 500, + "totalCompetitiveGames": 7 + }, + { + "_id": "user7", + "totalPoints": 400, + "totalCompetitiveGames": 8 + }, + { + "_id": "user8", + "totalPoints": 300, + "totalCompetitiveGames": 9 + }, + { + "_id": "user9", + "totalPoints": 200, + "totalCompetitiveGames": 10 + }, + { + "_id": "user10", + "totalPoints": 100, + "totalCompetitiveGames": 11 + } + ] + }); + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + expect(screen.getByText(i18en.t('ranking.username'))).toBeInTheDocument() + expect(screen.getByText(i18en.t('ranking.points'))).toBeInTheDocument() + expect(screen.getByText(i18en.t('ranking.num_games'))).toBeInTheDocument() + }); + it('renders position all users usernames',async ()=>{ + mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, + { + "usersCompetitiveStats": [ + { + "_id": "user1", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }, + { + "_id": "user2", + "totalPoints": 900, + "totalCompetitiveGames": 2 + }, + { + "_id": "user3", + "totalPoints": 800, + "totalCompetitiveGames": 3 + }, + { + "_id": "user4", + "totalPoints": 700, + "totalCompetitiveGames": 5 + }, + { + "_id": "user5", + "totalPoints": 600, + "totalCompetitiveGames": 6 + }, + { + "_id": "user6", + "totalPoints": 500, + "totalCompetitiveGames": 7 + }, + { + "_id": "user7", + "totalPoints": 400, + "totalCompetitiveGames": 8 + }, + { + "_id": "user8", + "totalPoints": 300, + "totalCompetitiveGames": 9 + }, + { + "_id": "user9", + "totalPoints": 200, + "totalCompetitiveGames": 10 + }, + { + "_id": "user10", + "totalPoints": 100, + "totalCompetitiveGames": 11 + } + ] + }); + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + //expect(screen.getByText("user1")).toBeInTheDocument() + expect(screen.getByText("user2")).toBeInTheDocument() + expect(screen.getByText("user3")).toBeInTheDocument() + expect(screen.getByText("user4")).toBeInTheDocument() + expect(screen.getByText("user5")).toBeInTheDocument() + expect(screen.getByText("user6")).toBeInTheDocument() + expect(screen.getByText("user7")).toBeInTheDocument() + expect(screen.getByText("user8")).toBeInTheDocument() + expect(screen.getByText("user9")).toBeInTheDocument() + expect(screen.getByText("user10")).toBeInTheDocument() + }); + it('renders position all users totalPoints',async ()=>{ + mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, + { + "usersCompetitiveStats": [ + { + "_id": "user1", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }, + { + "_id": "user2", + "totalPoints": 900, + "totalCompetitiveGames": 2 + }, + { + "_id": "user3", + "totalPoints": 800, + "totalCompetitiveGames": 3 + }, + { + "_id": "user4", + "totalPoints": 700, + "totalCompetitiveGames": 5 + }, + { + "_id": "user5", + "totalPoints": 600, + "totalCompetitiveGames": 6 + }, + { + "_id": "user6", + "totalPoints": 500, + "totalCompetitiveGames": 7 + }, + { + "_id": "user7", + "totalPoints": 400, + "totalCompetitiveGames": 8 + }, + { + "_id": "user8", + "totalPoints": 300, + "totalCompetitiveGames": 9 + }, + { + "_id": "user9", + "totalPoints": 200, + "totalCompetitiveGames": 10 + }, + { + "_id": "user10", + "totalPoints": 100, + "totalCompetitiveGames": 11 + } + ] + }); + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + expect(screen.getByText("1000")).toBeInTheDocument() + expect(screen.getByText("900")).toBeInTheDocument() + expect(screen.getByText("800")).toBeInTheDocument() + expect(screen.getByText("700")).toBeInTheDocument() + expect(screen.getByText("600")).toBeInTheDocument() + expect(screen.getByText("500")).toBeInTheDocument() + expect(screen.getByText("400")).toBeInTheDocument() + expect(screen.getByText("300")).toBeInTheDocument() + expect(screen.getByText("200")).toBeInTheDocument() +}); +it('renders position all users competitive games',async ()=>{ + mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, + { + "usersCompetitiveStats": [ + { + "_id": "user1", + "totalPoints": 1000, + "totalCompetitiveGames": 4 + }, + { + "_id": "user2", + "totalPoints": 900, + "totalCompetitiveGames": 2 + }, + { + "_id": "user3", + "totalPoints": 800, + "totalCompetitiveGames": 3 + }, + { + "_id": "user4", + "totalPoints": 700, + "totalCompetitiveGames": 5 + }, + { + "_id": "user5", + "totalPoints": 600, + "totalCompetitiveGames": 6 + }, + { + "_id": "user6", + "totalPoints": 500, + "totalCompetitiveGames": 7 + }, + { + "_id": "user7", + "totalPoints": 400, + "totalCompetitiveGames": 8 + }, + { + "_id": "user8", + "totalPoints": 300, + "totalCompetitiveGames": 9 + }, + { + "_id": "user9", + "totalPoints": 200, + "totalCompetitiveGames": 10 + }, + { + "_id": "user10", + "totalPoints": 100, + "totalCompetitiveGames": 11 + } + ] + }); + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + /* + expect(screen.getByText("2")).toBeInTheDocument() + expect(screen.getByText("5")).toBeInTheDocument() + expect(screen.getByText("6")).toBeInTheDocument() + expect(screen.getByText("7")).toBeInTheDocument()*/ +}); +it('renders position all users competitive games',async ()=>{ + mockAxios.onGet('http://localhost:8000/ranking/user').reply(200, + { + "_id": "myUser", + "totalPoints": 250, + "totalCompetitiveGames": 1 + } + ); + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + + expect(screen.getByText("myUser")).toBeInTheDocument() + expect(screen.getByText("250")).toBeInTheDocument() + //should be one if only your rank is shown + expect(screen.getAllByText(/1/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + +}); + +}); + + From ee00ba2520aa4588c47595a0f7d9b2e4ac02c190 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Wed, 17 Apr 2024 18:43:14 +0200 Subject: [PATCH 43/74] Added Game Configurator tests --- .../GameConfigurator/GameConfigurator.js | 8 +-- .../GameConfigurator/GameConfigurator.test.js | 51 +++++++++++++++++++ 2 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 webapp/src/components/GameConfigurator/GameConfigurator.test.js diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js index 582f6d13..338e97ce 100644 --- a/webapp/src/components/GameConfigurator/GameConfigurator.js +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -20,8 +20,8 @@ function GameConfigurator(){

{t("gameConfigurator.game_config")}

{t("gameConfigurator.custo_game")}

- - setTipoPregunta(e.target.value)}> @@ -29,9 +29,9 @@ function GameConfigurator(){

- + {/* Spinner para seleccionar el número de preguntas */} - setNumeroPreguntas(e.target.value)} diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.test.js b/webapp/src/components/GameConfigurator/GameConfigurator.test.js new file mode 100644 index 00000000..273b93c0 --- /dev/null +++ b/webapp/src/components/GameConfigurator/GameConfigurator.test.js @@ -0,0 +1,51 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; // For additional matchers like toBeInTheDocument +import { BrowserRouter as Router } from 'react-router-dom'; +import GameConfigurator from './GameConfigurator'; +import { initReactI18next } from 'react-i18next'; +import i18en from 'i18next'; + + +i18en.use(initReactI18next).init({ + resources: {}, + lng: 'en', + interpolation:{ + escapeValue: false, + } +}); +global.i18en = i18en; + +describe('GameConfigurator', () => { + test('renders GameConfigurator component', () => { + render(); + expect(screen.getByText(i18en.t("gameConfigurator.game_config"))).toBeInTheDocument(); + }); + + + test('updates tipoPregunta state when select value changes', () => { + render(); + const selectElement = screen.getByLabelText(i18en.t("gameConfigurator.type_quest")); + fireEvent.change(selectElement, { target: { value: 'CAPITAL' } }); + expect(selectElement.value).toBe('CAPITAL'); + }); + + test('updates numeroPreguntas state when input value changes', () => { + render(); + const inputElement = screen.getByLabelText(i18en.t("gameConfigurator.num_quest")); + fireEvent.change(inputElement, { target: { value: '10' } }); + expect(inputElement.value).toBe('10'); + }); + it('renders option to play customized game', () => { + render(); + const text = screen.getByText(i18en.t('gameConfigurator.custo_game')); + expect(text).toBeInTheDocument(); +}); + +it('renders option to play Competitive game', () => { + render(); + const text = screen.getByText(i18en.t('gameConfigurator.competi_game')); + expect(text).toBeInTheDocument(); +}); + +}); From 7a715c0b5b73b5efad750f4824b2b129b91643e2 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Wed, 17 Apr 2024 21:57:31 +0200 Subject: [PATCH 44/74] Finished Ranking jest tests --- .../GameConfigurator/GameConfigurator.js | 17 + webapp/src/components/GameMenu/GameMenu.js | 2 + webapp/src/components/fragments/Loader.js | 6 +- .../components/ranking/RankingRetriever.js | 20 +- webapp/src/components/ranking/RankingView.js | 14 +- .../components/ranking/RankingView.test.js | 307 +++++------------- webapp/src/custom.css | 52 +-- webapp/src/translations/en/global.json | 4 +- webapp/src/translations/es/global.json | 4 +- 9 files changed, 150 insertions(+), 276 deletions(-) diff --git a/webapp/src/components/GameConfigurator/GameConfigurator.js b/webapp/src/components/GameConfigurator/GameConfigurator.js index 338e97ce..4a536729 100644 --- a/webapp/src/components/GameConfigurator/GameConfigurator.js +++ b/webapp/src/components/GameConfigurator/GameConfigurator.js @@ -14,14 +14,25 @@ function GameConfigurator(){ function handleClick() { setClickedForNewGame(true); } + + function handleClickRandomize() { + const options = ['ALL', 'POPULATION', 'CAPITAL', 'LANGUAGE', 'SIZE']; + const randomOptionIndex = Math.floor(Math.random() * options.length); + setTipoPregunta(options[randomOptionIndex]); + + const randomNumQuestions = Math.floor(Math.random() * 20) + 1; // Random number between 1 and 20 + setNumeroPreguntas(randomNumQuestions); + } return ( clickedForNewGame ? :

{t("gameConfigurator.game_config")}

{t("gameConfigurator.custo_game")}

+
diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js index 93236ca9..c9e44535 100644 --- a/webapp/src/components/ranking/RankingView.test.js +++ b/webapp/src/components/ranking/RankingView.test.js @@ -6,6 +6,7 @@ import i18en from 'i18next'; import RankingView from './RankingView'; import MockAdapter from 'axios-mock-adapter'; import { act } from 'react-dom/test-utils'; +import { UserContextProvider} from '../loginAndRegistration/UserContext'; i18en.use(initReactI18next).init({ resources: {}, lng: 'en', @@ -16,96 +17,31 @@ i18en.use(initReactI18next).init({ global.i18en = i18en; const mockAxios = new MockAdapter(axios); -describe('GameMenu component', () => { - beforeEach(() => { - mockAxios.reset(); - }); +describe('RankingView component', () => { + it('renders title', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText(i18en.t('ranking.ranking')); expect(text).toBeInTheDocument(); }); it('renders Loading if the call to the gateway has not been done', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText('Loading...'); expect(text).toBeInTheDocument(); }); - it('renders position all headers in the table',async ()=>{ - mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, - { - "usersCompetitiveStats": [ - { - "_id": "user", - "totalPoints": 1000, - "totalCompetitiveGames": 4 - }, - { - "_id": "user2", - "totalPoints": 900, - "totalCompetitiveGames": 2 - }, - { - "_id": "user3", - "totalPoints": 800, - "totalCompetitiveGames": 3 - }, - { - "_id": "user4", - "totalPoints": 700, - "totalCompetitiveGames": 5 - }, - { - "_id": "user5", - "totalPoints": 600, - "totalCompetitiveGames": 6 - }, - { - "_id": "user6", - "totalPoints": 500, - "totalCompetitiveGames": 7 - }, - { - "_id": "user7", - "totalPoints": 400, - "totalCompetitiveGames": 8 - }, - { - "_id": "user8", - "totalPoints": 300, - "totalCompetitiveGames": 9 - }, - { - "_id": "user9", - "totalPoints": 200, - "totalCompetitiveGames": 10 - }, - { - "_id": "user10", - "totalPoints": 100, - "totalCompetitiveGames": 11 - } - ] - }); - await act(async () =>{ - await render(); - - }) - await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); - expect(screen.getByText(i18en.t('ranking.username'))).toBeInTheDocument() - expect(screen.getByText(i18en.t('ranking.points'))).toBeInTheDocument() - expect(screen.getByText(i18en.t('ranking.num_games'))).toBeInTheDocument() - }); - it('renders position all users usernames',async ()=>{ - mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, +}); + + describe('RankingView component with endpoint', ()=>{ + mockAxios.onGet('http://localhost:8000/record/ranking/top10').reply(200, { "usersCompetitiveStats": [ { - "_id": "user1", + "_id": "user", "totalPoints": 1000, "totalCompetitiveGames": 4 }, @@ -155,182 +91,87 @@ describe('GameMenu component', () => { "totalCompetitiveGames": 11 } ] - }); + }); + + mockAxios.onGet('http://localhost:8000/record/ranking/user').reply(200, + { + "_id": "myUser", + "totalPoints": 250, + "totalCompetitiveGames": 1 + } + ); + + it('renders position all headers in the table',async ()=>{ + await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); - //expect(screen.getByText("user1")).toBeInTheDocument() - expect(screen.getByText("user2")).toBeInTheDocument() - expect(screen.getByText("user3")).toBeInTheDocument() - expect(screen.getByText("user4")).toBeInTheDocument() - expect(screen.getByText("user5")).toBeInTheDocument() - expect(screen.getByText("user6")).toBeInTheDocument() - expect(screen.getByText("user7")).toBeInTheDocument() - expect(screen.getByText("user8")).toBeInTheDocument() - expect(screen.getByText("user9")).toBeInTheDocument() - expect(screen.getByText("user10")).toBeInTheDocument() + expect(screen.getByText(i18en.t('ranking.username'))).toBeInTheDocument() + expect(screen.getByText(i18en.t('ranking.points'))).toBeInTheDocument() + expect(screen.getByText(i18en.t('ranking.num_games'))).toBeInTheDocument() }); - it('renders position all users totalPoints',async ()=>{ - mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, - { - "usersCompetitiveStats": [ - { - "_id": "user1", - "totalPoints": 1000, - "totalCompetitiveGames": 4 - }, - { - "_id": "user2", - "totalPoints": 900, - "totalCompetitiveGames": 2 - }, - { - "_id": "user3", - "totalPoints": 800, - "totalCompetitiveGames": 3 - }, - { - "_id": "user4", - "totalPoints": 700, - "totalCompetitiveGames": 5 - }, - { - "_id": "user5", - "totalPoints": 600, - "totalCompetitiveGames": 6 - }, - { - "_id": "user6", - "totalPoints": 500, - "totalCompetitiveGames": 7 - }, - { - "_id": "user7", - "totalPoints": 400, - "totalCompetitiveGames": 8 - }, - { - "_id": "user8", - "totalPoints": 300, - "totalCompetitiveGames": 9 - }, - { - "_id": "user9", - "totalPoints": 200, - "totalCompetitiveGames": 10 - }, - { - "_id": "user10", - "totalPoints": 100, - "totalCompetitiveGames": 11 - } - ] - }); + it('renders position all users usernames',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); - expect(screen.getByText("1000")).toBeInTheDocument() - expect(screen.getByText("900")).toBeInTheDocument() - expect(screen.getByText("800")).toBeInTheDocument() - expect(screen.getByText("700")).toBeInTheDocument() - expect(screen.getByText("600")).toBeInTheDocument() - expect(screen.getByText("500")).toBeInTheDocument() - expect(screen.getByText("400")).toBeInTheDocument() - expect(screen.getByText("300")).toBeInTheDocument() - expect(screen.getByText("200")).toBeInTheDocument() -}); -it('renders position all users competitive games',async ()=>{ - mockAxios.onGet('http://localhost:8000/ranking/top10').reply(200, - { - "usersCompetitiveStats": [ - { - "_id": "user1", - "totalPoints": 1000, - "totalCompetitiveGames": 4 - }, - { - "_id": "user2", - "totalPoints": 900, - "totalCompetitiveGames": 2 - }, - { - "_id": "user3", - "totalPoints": 800, - "totalCompetitiveGames": 3 - }, - { - "_id": "user4", - "totalPoints": 700, - "totalCompetitiveGames": 5 - }, - { - "_id": "user5", - "totalPoints": 600, - "totalCompetitiveGames": 6 - }, - { - "_id": "user6", - "totalPoints": 500, - "totalCompetitiveGames": 7 - }, - { - "_id": "user7", - "totalPoints": 400, - "totalCompetitiveGames": 8 - }, - { - "_id": "user8", - "totalPoints": 300, - "totalCompetitiveGames": 9 - }, - { - "_id": "user9", - "totalPoints": 200, - "totalCompetitiveGames": 10 - }, - { - "_id": "user10", - "totalPoints": 100, - "totalCompetitiveGames": 11 - } - ] - }); + //expect(screen.getByText("user1")).toBeInTheDocument() + expect(screen.getByText("user2")).toBeInTheDocument() + expect(screen.getByText("user3")).toBeInTheDocument() + expect(screen.getByText("user4")).toBeInTheDocument() + expect(screen.getByText("user5")).toBeInTheDocument() + expect(screen.getByText("user6")).toBeInTheDocument() + expect(screen.getByText("user7")).toBeInTheDocument() + expect(screen.getByText("user8")).toBeInTheDocument() + expect(screen.getByText("user9")).toBeInTheDocument() + expect(screen.getByText("user10")).toBeInTheDocument() + }); + it('renders position all users totalPoints',async ()=>{ + await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); - /* - expect(screen.getByText("2")).toBeInTheDocument() - expect(screen.getByText("5")).toBeInTheDocument() - expect(screen.getByText("6")).toBeInTheDocument() - expect(screen.getByText("7")).toBeInTheDocument()*/ -}); -it('renders position all users competitive games',async ()=>{ - mockAxios.onGet('http://localhost:8000/ranking/user').reply(200, - { - "_id": "myUser", - "totalPoints": 250, - "totalCompetitiveGames": 1 - } - ); + expect(screen.getByText("1000")).toBeInTheDocument() + expect(screen.getByText("900")).toBeInTheDocument() + expect(screen.getByText("800")).toBeInTheDocument() + expect(screen.getByText("700")).toBeInTheDocument() + expect(screen.getByText("600")).toBeInTheDocument() + expect(screen.getByText("500")).toBeInTheDocument() + expect(screen.getByText("400")).toBeInTheDocument() + expect(screen.getByText("300")).toBeInTheDocument() + expect(screen.getByText("200")).toBeInTheDocument() + }); + it('renders position all users competitive games',async ()=>{ + await act(async () =>{ - await render(); - + await render(); + }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); - expect(screen.getByText("myUser")).toBeInTheDocument() - expect(screen.getByText("250")).toBeInTheDocument() - //should be one if only your rank is shown - expect(screen.getAllByText(/1/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion - -}); + expect(screen.getAllByText(/2/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + expect(screen.getAllByText(/5/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + expect(screen.getAllByText(/6/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + expect(screen.getAllByText(/7/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + }); + it('renders position all users competitive games',async ()=>{ + + await act(async () =>{ + await render(); + + }) + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); -}); + expect(screen.getByText("myUser")).toBeInTheDocument() + expect(screen.getByText("250")).toBeInTheDocument() + //should be one if only your rank is shown + expect(screen.getAllByText(/1/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion + }); +}) diff --git a/webapp/src/custom.css b/webapp/src/custom.css index d0349386..836d49af 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1351,30 +1351,7 @@ h2{ box-sizing: border-box; } -.terminal-controls { - float: right; -} - -.control { - display: inline-block; - width: 0.6em; - height: 0.6em; - margin-left: 0.4em; - border-radius: 50%; - background-color: #777; -} - -.control.close { - background-color: #e33; -} - -.control.minimize { - background-color: #ee0; -} -.control.maximize { - background-color: #0b0; -} .terminal-title { float: left; @@ -1394,6 +1371,25 @@ h2{ /*--------------------------------------------------Configurator---------------------------------*/ /* Estilo para el elemento select */ +.buttonRandomize{ + display: flex; + justify-content: center; + align-items: center; + width: 15em; + height: 45px; + background:#00b8ff; + border: none; + outline: none; + border-radius: 40px; + box-shadow: 0 0 10px black; + cursor: pointer; + font-size: 1em; + color: black; + font-weight: 700; + margin-top: 1.5em; + margin-bottom: 1em; + text-decoration: none; +} .select-style { width: 200px; height: 35px; @@ -1427,6 +1423,16 @@ h2{ left: 50%; top: 50%; } +/* +.GameConfiguratorDiv { + display: flex; + flex-direction: column; +} + +.GameConfiguratorDiv > *:not(:first-child):not(:nth-child(4)) { + text-align: left; +}*/ + .hr-style { border: 0; border-top: 1px solid #8e888885; /* Color gris */ diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 2c824262..2afde28c 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -79,12 +79,14 @@ "play_custom":"Play Customized Game", "rules_competi":"Play with all kinds of questions and a quantity of 5", "play_competi":"Play competitive Game", + "option_all":"All", "option_population":"Population", "option_capital":"Capital", "option_language":"Language", "option_size":"Size", "custo_game":"Create custom game", - "competi_game":"Play Competitive" + "competi_game":"Play Competitive", + "randomize":"Randomize Parameters" }, "ranking":{ "ranking":"Ranking", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 1692648c..9f888395 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -82,12 +82,14 @@ "play_custom":"Jugar personalizado", "rules_competi":"Jugar con todo tipo de preguntas siendo estas 5", "play_competi":"Jugar Competitivo", + "option_all":"Todas", "option_population":"Población", "option_capital":"Capital", "option_language":"Lenguaje", "option_size":"Extensión", "custo_game":"Crea una partida personalizada", - "competi_game":"Juega en modo Competitivo" + "competi_game":"Juega en modo Competitivo", + "randomize":"Randomiza Parámetros" }, "ranking":{ "ranking":"Ranking", From c51a18d9949da70bcbb4b5577fce25f024adf218 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Thu, 18 Apr 2024 09:44:55 +0200 Subject: [PATCH 45/74] Modified sonar cloud properties to not check code duplication on tests files --- sonar-project.properties | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sonar-project.properties b/sonar-project.properties index 6db6137a..fad83281 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -14,4 +14,5 @@ sonar.coverage.exclusions=**/*.test.js sonar.sources=webapp/src/components,users/authservice,users/userservice,gatewayservice sonar.sourceEncoding=UTF-8 sonar.exclusions=node_modules/** -sonar.javascript.lcov.reportPaths=**/coverage/lcov.info \ No newline at end of file +sonar.javascript.lcov.reportPaths=**/coverage/lcov.info +sonar.cpd.exclusions=**/*.test.js,**/*steps.js,**/*Tests.java From 1664dd528cb9a9e4327daeb8d482a8459c825004 Mon Sep 17 00:00:00 2001 From: ErdemYabaci Date: Thu, 18 Apr 2024 10:59:08 +0200 Subject: [PATCH 46/74] changing the ip --- gatewayservice/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gatewayservice/openapi.yaml b/gatewayservice/openapi.yaml index 7129380e..47615d2a 100644 --- a/gatewayservice/openapi.yaml +++ b/gatewayservice/openapi.yaml @@ -9,7 +9,7 @@ info: servers: - url: http://localhost:8000 description: Development server - - url: http://172.203.216.60:8000 + - url: http://wiqen1b.serveminecraft.net:8000 description: Production server paths: /adduser: From 47cb8df1bc019de9a68c682f99057928dc3e5514 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Thu, 18 Apr 2024 11:36:38 +0200 Subject: [PATCH 47/74] Added prod profile to prometheus and grafana --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 26949792..89ffbe46 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -113,7 +113,7 @@ prometheus: image: prom/prometheus container_name: prometheus-${teamname:-defaultASW} - profiles: ["dev"] + profiles: ["dev", "prod"] networks: - mynetwork volumes: @@ -127,7 +127,7 @@ grafana: image: grafana/grafana container_name: grafana-${teamname:-defaultASW} - profiles: ["dev"] + profiles: ["dev", "prod"] networks: - mynetwork volumes: From 4c54efca1e1e90da7fe5f78cc721aa1d14c063a5 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 11:48:56 +0200 Subject: [PATCH 48/74] Resolved jest tests problems --- webapp/src/components/questionView/QuestionGenerator.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index 2add8f05..9b5a38b9 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -9,7 +9,7 @@ class QuestionGenerator{ } async generateQuestions(lang, type, amount) { - + /* try { //const response = await fetch(this.apiUrl); //const receivedQuestions = await response.json(); @@ -35,8 +35,8 @@ class QuestionGenerator{ throw new Error(error); } + */ - /* try { let response; if(type==="COMPETITIVE"){ @@ -55,7 +55,7 @@ class QuestionGenerator{ } catch (error) { throw new Error(error); } - */ + } } From 70a03a19f0ac084aeab9a0b92e9ce490ccc5a0a3 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 12:03:05 +0200 Subject: [PATCH 49/74] Added user for userContext in tests --- webapp/src/components/ranking/RankingView.js | 2 +- webapp/src/components/ranking/RankingView.test.js | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index 33a239fb..6d172024 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -17,7 +17,7 @@ const RankingView = () => { try { var ranking = await retriever.getTopTen(); setRankingData(ranking.usersCompetitiveStats); - var myrank = await retriever.getMyPosition("user"); + var myrank = await retriever.getMyPosition(user.username); setMyRankingData(myrank); console.log(myrank) } catch (error) { diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js index c9e44535..dd0c05fd 100644 --- a/webapp/src/components/ranking/RankingView.test.js +++ b/webapp/src/components/ranking/RankingView.test.js @@ -22,7 +22,7 @@ describe('RankingView component', () => { it('renders title', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText(i18en.t('ranking.ranking')); expect(text).toBeInTheDocument(); @@ -93,18 +93,19 @@ describe('RankingView component', () => { ] }); - mockAxios.onGet('http://localhost:8000/record/ranking/user').reply(200, + mockAxios.onGet('http://localhost:8000/record/ranking/myUser').reply(200, { "_id": "myUser", "totalPoints": 250, "totalCompetitiveGames": 1 } ); + const user = { username: 'myUser' }; it('renders position all headers in the table',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -114,7 +115,7 @@ describe('RankingView component', () => { }); it('renders position all users usernames',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -132,7 +133,7 @@ describe('RankingView component', () => { it('renders position all users totalPoints',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -149,7 +150,7 @@ describe('RankingView component', () => { it('renders position all users competitive games',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -162,7 +163,7 @@ describe('RankingView component', () => { it('renders position all users competitive games',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); From 50b07810096b25eb9770982fcede6895d82ae79f Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 12:57:39 +0200 Subject: [PATCH 50/74] Added new style for ranking --- .../components/ranking/RankingRetriever.js | 16 ++++---- webapp/src/components/ranking/RankingView.js | 4 +- webapp/src/custom.css | 41 ++++++++++++++++++- webapp/src/translations/en/global.json | 2 +- webapp/src/translations/es/global.json | 2 +- 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index c455854a..d2c11001 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -8,7 +8,7 @@ class RankingRetriever{ } async getTopTen() { - + /* try { const response = await axios.get(this.apiUrl + '/top10');//finding the top ten const receivedTopTenRanking = await response.data; @@ -18,8 +18,8 @@ class RankingRetriever{ console.log(error) throw new Error(error); - } - /* + }*/ + return { "usersCompetitiveStats": [ { @@ -73,10 +73,10 @@ class RankingRetriever{ "totalCompetitiveGames": 11 } ] - }*/ + } } async getMyPosition(user){ - + /* try { const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten const receivedMyRanking = await response.data; @@ -86,13 +86,13 @@ class RankingRetriever{ console.log(error) throw new Error(error); - } - /* + }*/ + return { "_id": "myUser", "totalPoints": 250, "totalCompetitiveGames": 1 - };*/ + }; } diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index 6d172024..2737ccf1 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -17,7 +17,7 @@ const RankingView = () => { try { var ranking = await retriever.getTopTen(); setRankingData(ranking.usersCompetitiveStats); - var myrank = await retriever.getMyPosition(user.username); + var myrank = await retriever.getMyPosition("user.username"); setMyRankingData(myrank); console.log(myrank) } catch (error) { @@ -32,7 +32,7 @@ const RankingView = () => { return (
-

{t("ranking.ranking")}

+

{t("ranking.ranking")}🏅

{rankingData && rankingData.length > 0 && myRankingData ? (
diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 836d49af..dab0a61c 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1447,6 +1447,9 @@ h2{ .table tbody tr.penultimate-row td { background-color: black; padding: 15px; + /* + border: 1px solid rgba(205, 187, 187, 0.455); + box-shadow: 0 0 10px rgba(197, 191, 191, 0.726);*/ } .table th,td{ @@ -1459,7 +1462,7 @@ h2{ } /* Estilo para todos los td excepto el penúltimo */ .table tbody tr:not(:nth-last-child(2)) td:nth-child(1) { - background-color: #69276f; + background-color: #4d1e51a8; } @@ -1470,3 +1473,39 @@ h2{ } .table tr:hover {background-color: #a59c9ca2;} +.table table{ + border-collapse: collapse; +} +/* Estilo para la fila de headers */ +.table th { + border-top: 2px solid rgba(205, 187, 187, 0.455); /* Borde superior para todas las celdas de la fila de headers */ + border-left: 2px solid rgba(205, 187, 187, 0.455); /* Borde izquierdo en la primera celda */ + border-right: 2px solid rgba(205, 187, 187, 0.455); /* Borde derecho en la última celda */ +} + +/* Estilo para las filas de datos (excepto la penúltima) */ +.table tbody tr:not(:nth-last-child(2)) td { + border-left: 2px solid rgba(205, 187, 187, 0.455); /* Borde izquierdo en la primera celda */ + border-right: 2px solid rgba(205, 187, 187, 0.455); /* Borde derecho en la última celda */ +} + +/* Borde superior e inferior para la penúltima fila */ +.table tbody tr:nth-last-child(2) td { + border-top: 2px solid rgba(205, 187, 187, 0.455); /* Borde superior */ + border-bottom: 2px solid rgba(205, 187, 187, 0.455); /* Borde inferior */ +} + +/* Borde inferior para todas las celdas de la última fila */ +.table tbody tr:last-child td { + border-bottom: 2px solid rgba(205, 187, 187, 0.455); /* Borde inferior */ +} + +/* Borde izquierdo para la primera celda de la última fila */ +.table tbody tr:last-child td:first-child { + border-left: 2px solid rgba(205, 187, 187, 0.455); /* Borde izquierdo */ +} + +/* Borde derecho para la última celda de la última fila */ +.table tbody tr:last-child td:last-child { + border-right: 2px solid rgba(205, 187, 187, 0.455); /* Borde derecho */ +} diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 2afde28c..a952ad7e 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -93,7 +93,7 @@ "position":"Position", "username":"Username", "points":"Points", - "num_games":"Num Competitive games" + "num_games":"Competitive games" } } diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index 9f888395..a0888aff 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -96,7 +96,7 @@ "position":"Posición", "username":"Username", "points":"Puntos", - "num_games":"Nº juegos Competitivos" + "num_games":"Juegos Competitivos" } } From 8c0b47f31d9d86525295c7a7c257506d19b75571 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 13:24:19 +0200 Subject: [PATCH 51/74] Added gameMenu tests to e2e --- webapp/e2e/features/gameMenu.feature | 4 ++++ webapp/e2e/steps/gameMenu.steps.js | 36 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 webapp/e2e/features/gameMenu.feature create mode 100644 webapp/e2e/steps/gameMenu.steps.js diff --git a/webapp/e2e/features/gameMenu.feature b/webapp/e2e/features/gameMenu.feature new file mode 100644 index 00000000..3953d899 --- /dev/null +++ b/webapp/e2e/features/gameMenu.feature @@ -0,0 +1,4 @@ +Feature: Game Menu page functionality + Scenario: There should be visible three links + Given I am on the game menu + Then three buttons should be visible diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js new file mode 100644 index 00000000..879e63db --- /dev/null +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -0,0 +1,36 @@ +const puppeteer = require('puppeteer'); +const { defineFeature, loadFeature } = require('jest-cucumber'); +const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; + +const feature = loadFeature('./features/gameMenu.feature'); + +let page; +let browser; + +defineFeature(feature, test => { + + beforeAll(async () => { + browser = await puppeteer.launch({ + slowMo: 20, + defaultViewport: { width: 1920, height: 1080 }, + args: ['--window-size=1920,1080'] + }); + + page = await browser.newPage(); + setDefaultOptions({ timeout: 10000 }); + }); + + test('There should be visible three links', ({ given, then }) => { + given('I am on the the game menu', async () => { + await page.goto('http://localhost:3000/menu'); + await page.waitForSelector('.divMenu'); + }); + + then('Then three buttons should be visible', async () => { + //await expect(page).toMatchElement('.linkButton'); + const elements = await page.$$('.linkButton'); + expect(elements.length).toBeGreaterThan(0); // At least one element with class 'linkButton' + }); + }); + +}); From 6d29ff78a491337012d9596fd122853237db8235 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 13:25:38 +0200 Subject: [PATCH 52/74] Removed mock from ranking --- .../src/components/ranking/RankingRetriever.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index d2c11001..41953d22 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -8,7 +8,7 @@ class RankingRetriever{ } async getTopTen() { - /* + try { const response = await axios.get(this.apiUrl + '/top10');//finding the top ten const receivedTopTenRanking = await response.data; @@ -18,8 +18,8 @@ class RankingRetriever{ console.log(error) throw new Error(error); - }*/ - + } + /* return { "usersCompetitiveStats": [ { @@ -73,10 +73,10 @@ class RankingRetriever{ "totalCompetitiveGames": 11 } ] - } + }*/ } async getMyPosition(user){ - /* + try { const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten const receivedMyRanking = await response.data; @@ -86,13 +86,13 @@ class RankingRetriever{ console.log(error) throw new Error(error); - }*/ - + } + /* return { "_id": "myUser", "totalPoints": 250, "totalCompetitiveGames": 1 - }; + };*/ } From 8b72579419bf557d5faa956a8d7bae91181d4407 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Thu, 18 Apr 2024 13:32:04 +0200 Subject: [PATCH 53/74] Fixing tests --- webapp/src/components/ranking/RankingView.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index 2737ccf1..6d172024 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -17,7 +17,7 @@ const RankingView = () => { try { var ranking = await retriever.getTopTen(); setRankingData(ranking.usersCompetitiveStats); - var myrank = await retriever.getMyPosition("user.username"); + var myrank = await retriever.getMyPosition(user.username); setMyRankingData(myrank); console.log(myrank) } catch (error) { @@ -32,7 +32,7 @@ const RankingView = () => { return (
-

{t("ranking.ranking")}🏅

+

{t("ranking.ranking")}

{rankingData && rankingData.length > 0 && myRankingData ? (
From 9279c8d576be3796a8ff1fa7f52b0787d4cda865 Mon Sep 17 00:00:00 2001 From: Mister-Mario <102600139+Mister-Mario@users.noreply.github.com> Date: Thu, 18 Apr 2024 19:03:36 +0200 Subject: [PATCH 54/74] Update README.md Changed the url of the app --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5575e13d..3d262ac9 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ docker compose --profile dev up --build ``` ### Deployed in Cloud -In order to view the application deploy in the cloud click [here](http://172.203.216.60:3000) +In order to view the application deploy in the cloud click [here](http://wiqen1b.serveminecraft.net:3000) ### Members - Lucía Ruiz Núñez uo289267@uniovi.es From cd62261107be384719a743ec7b830212656466b1 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 20 Apr 2024 14:14:30 +0200 Subject: [PATCH 55/74] Corrected Game Menu e2e --- package-lock.json | 17 ----------------- webapp/e2e/steps/gameMenu.steps.js | 4 ++-- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8e7dbe69..b785ca01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -560,23 +560,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { "version": "7.23.9", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz", diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 879e63db..2f66b817 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -21,12 +21,12 @@ defineFeature(feature, test => { }); test('There should be visible three links', ({ given, then }) => { - given('I am on the the game menu', async () => { + given('I am on the game menu', async () => { await page.goto('http://localhost:3000/menu'); await page.waitForSelector('.divMenu'); }); - then('Then three buttons should be visible', async () => { + then('three buttons should be visible', async () => { //await expect(page).toMatchElement('.linkButton'); const elements = await page.$$('.linkButton'); expect(elements.length).toBeGreaterThan(0); // At least one element with class 'linkButton' From 34945b4456a89aa98f1bb899a571ea0f6977e5b0 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 20 Apr 2024 18:07:03 +0200 Subject: [PATCH 56/74] GameMenu e2e --- webapp/e2e/steps/gameMenu.steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 2f66b817..7f09955e 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -2,7 +2,7 @@ const puppeteer = require('puppeteer'); const { defineFeature, loadFeature } = require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; -const feature = loadFeature('./features/gameMenu.feature'); +const feature = loadFeature('../features/gameMenu.feature'); let page; let browser; From a5032570491bb614bb51bb691a45b46b25e0612b Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 20 Apr 2024 18:23:27 +0200 Subject: [PATCH 57/74] e2e fixes --- webapp/e2e/steps/gameMenu.steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 7f09955e..2f66b817 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -2,7 +2,7 @@ const puppeteer = require('puppeteer'); const { defineFeature, loadFeature } = require('jest-cucumber'); const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; -const feature = loadFeature('../features/gameMenu.feature'); +const feature = loadFeature('./features/gameMenu.feature'); let page; let browser; From 504232b43559917d8e2d40a4bfe11b3c8427cd6a Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 20 Apr 2024 19:18:02 +0200 Subject: [PATCH 58/74] Added e2e test --- webapp/e2e/features/gameMenu.feature | 4 ++++ webapp/e2e/steps/gameMenu.steps.js | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/webapp/e2e/features/gameMenu.feature b/webapp/e2e/features/gameMenu.feature index 3953d899..a156fbbc 100644 --- a/webapp/e2e/features/gameMenu.feature +++ b/webapp/e2e/features/gameMenu.feature @@ -2,3 +2,7 @@ Feature: Game Menu page functionality Scenario: There should be visible three links Given I am on the game menu Then three buttons should be visible + Scenario: New Game should go to game configurator + Given I am on the game menu + When I click on New Game + Then I should be in the game configurator diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 2f66b817..cc4230c2 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -32,5 +32,17 @@ defineFeature(feature, test => { expect(elements.length).toBeGreaterThan(0); // At least one element with class 'linkButton' }); }); + test('New Game should go to game configurator', ({ given, when, then }) => { + given('I am on the game menu', async () => { + await page.goto('http://localhost:3000/menu'); + await page.waitForSelector('.divMenu'); + }); + when('I click on New Game', async () => { + await page.click('.linkButton:first-child'); + }); + then('I should be in the game configurator', async () => { + await expect(page).toMatchElement('.GameConfiguratorDiv'); + }); + }); }); From b7249ce4cdc7a44448e715f1da7a05f2d3a4a3cb Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 20 Apr 2024 19:23:04 +0200 Subject: [PATCH 59/74] Added enpoint functionalities to return the ranking position of each user --- users/recordservice/record-service.js | 78 ++++++++++++---------- users/recordservice/record-service.test.js | 1 + 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/users/recordservice/record-service.js b/users/recordservice/record-service.js index 4528a2b6..dace39b2 100644 --- a/users/recordservice/record-service.js +++ b/users/recordservice/record-service.js @@ -1,7 +1,8 @@ const express = require('express'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); -const Record = require('./record-model') +const Record = require('./record-model'); +const { SystemUpdate } = require('@material-ui/icons'); const app = express(); const port = 8004; @@ -9,6 +10,10 @@ const port = 8004; // Middleware to parse JSON in request body app.use(bodyParser.json()); +var ranking = []; +var lastTime = new Date(); +const minTimeDifferenceInMiliseconds = 120_000; + // Connect to MongoDB const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/userdb'; mongoose.connect(mongoUri); @@ -44,24 +49,8 @@ app.post('/record', async (req, res) => { app.get('/record/ranking/top10', async (req, res) => { try { - const usersCompetitiveStats = await Record.aggregate([ - // Unwind the games array to work with each game separately - { $unwind: "$games" }, - // Match only competitive games - { $match: { "games.competitive": true } }, - // Group by user and calculate total points and total competitive games per user - { - $group: { - _id: "$user", - totalPoints: { $sum: "$games.points" }, - totalCompetitiveGames: { $sum: 1 } // Count the number of competitive games - } - }, - // Sort by total points in descending order (top 1 will have the highest points) - { $sort: { totalPoints: -1 } }, - // Limit to the top 10 - { $limit: 10 } - ]); + let usersRanking = await getRanking(); + let usersCompetitiveStats = usersRanking.slice(0, 10); res.json({usersCompetitiveStats: usersCompetitiveStats }); } catch (err) { @@ -72,25 +61,13 @@ app.get('/record/ranking/top10', async (req, res) => { app.get('/record/ranking/:user', async (req, res) => { try { const user = req.params.user.toString(); - //Gives back an array of 1 user - const userCompetitiveStats = await Record.aggregate([ - { $match: { user: user } }, - // Unwind the games array to work with each game separately - { $unwind: "$games" }, - // Match only competitive games - { $match: { "games.competitive": true } }, - // Group by user and calculate total points and total competitive games per user - { - $group: { - _id: "$user", - totalPoints: { $sum: "$games.points" }, - totalCompetitiveGames: { $sum: 1 } // Count the number of competitive games - } - } - ]); + + let usersRanking = await getRanking(); + let userCompetitiveStats = usersRanking.filter(userData => userData._id === user); res.json({userCompetitiveStats: userCompetitiveStats[0] }); } catch (err) { + console.log(err) res.status(500).send(); } }); @@ -108,6 +85,37 @@ app.get('/record/:user', async (req, res) => { } }); + +async function getRanking(){ + const nowTime = new Date(); + let timeDifferenceInMiliseconds = nowTime - lastTime; + if(ranking.length == 0 || timeDifferenceInMiliseconds > minTimeDifferenceInMiliseconds){ + ranking = await Record.aggregate([ + // Unwind the games array to work with each game separately + { $unwind: "$games" }, + // Match only competitive games + { $match: { "games.competitive": true } }, + // Group by user and calculate total points and total competitive games per user + { + $group: { + _id: "$user", + totalPoints: { $sum: "$games.points" }, + totalCompetitiveGames: { $sum: 1 } // Count the number of competitive games + } + }, + // Sort by total points in descending order (top 1 will have the highest points) + { $sort: { totalPoints: -1 } } + ]); + + //The operator ... dumps the user json making it {_id , totalPoints, totalCompetitiveGames, position} + ranking = ranking.map((user, index) => ({ ...user, position: index + 1 })); + lastTime = new Date(); + } + + return ranking; + +} + const server = app.listen(port, () => { console.log(`Record Service listening at http://localhost:${port}`); }); diff --git a/users/recordservice/record-service.test.js b/users/recordservice/record-service.test.js index a42d1978..5594f781 100644 --- a/users/recordservice/record-service.test.js +++ b/users/recordservice/record-service.test.js @@ -239,6 +239,7 @@ describe('Record Service', () => { const userStats = responseGet.body.userCompetitiveStats; expect(userStats).toHaveProperty('_id', 'user1'); + expect(userStats).toHaveProperty('position', 10); expect(userStats).toHaveProperty('totalCompetitiveGames', 2); expect(userStats).toHaveProperty('totalPoints', 20); //i * 10 * totalCompetitiveGames , i = 2 }); From 28946581af210b7424e412392a7d3c1cf5774b69 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sat, 20 Apr 2024 19:24:38 +0200 Subject: [PATCH 60/74] Fixed e2e --- webapp/e2e/steps/gameMenu.steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index cc4230c2..215af2f7 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -38,7 +38,7 @@ defineFeature(feature, test => { await page.waitForSelector('.divMenu'); }); when('I click on New Game', async () => { - await page.click('.linkButton:first-child'); + await page.click('.linkButton'); }); then('I should be in the game configurator', async () => { await expect(page).toMatchElement('.GameConfiguratorDiv'); From 489a6875d6c01e7c9078fc940ea9fb862d28be70 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sat, 20 Apr 2024 19:28:33 +0200 Subject: [PATCH 61/74] Removed weird import, probably VS Code stuff --- users/recordservice/record-service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/users/recordservice/record-service.js b/users/recordservice/record-service.js index dace39b2..cf94544a 100644 --- a/users/recordservice/record-service.js +++ b/users/recordservice/record-service.js @@ -2,7 +2,6 @@ const express = require('express'); const mongoose = require('mongoose'); const bodyParser = require('body-parser'); const Record = require('./record-model'); -const { SystemUpdate } = require('@material-ui/icons'); const app = express(); const port = 8004; From 2f4e6a3198207a3d91023c808519dde6a6e2c8b9 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 21 Apr 2024 09:33:59 +0200 Subject: [PATCH 62/74] Made view work with the new endpoint --- .../questionView/CreationHistoricalRecord.js | 37 +++++++++---------- .../components/ranking/RankingRetriever.js | 4 +- webapp/src/components/ranking/RankingView.js | 4 +- .../components/ranking/RankingView.test.js | 7 +++- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/webapp/src/components/questionView/CreationHistoricalRecord.js b/webapp/src/components/questionView/CreationHistoricalRecord.js index 68ceb4f5..3bdb6370 100644 --- a/webapp/src/components/questionView/CreationHistoricalRecord.js +++ b/webapp/src/components/questionView/CreationHistoricalRecord.js @@ -39,29 +39,26 @@ class CreationHistoricalRecord{ async sendRecord(user) { - const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/record"; - - const body = { - user:user, - game:this.record.game - } - try { + const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/record"; + + const body = { + user: user, + game: this.record.game + }; + + try { const response = await axios.post(apiUrl, body, { - headers: { - 'Content-Type': 'application/json' - } + headers: { + 'Content-Type': 'application/json' + } }); - - if (!response.ok) { - throw new Error('Error al enviar el registro'); - } - - const data = await response.json(); - console.log(data); - } catch (error) { - console.error('Error:', error); - } + + + console.log('Registro enviado:', response.data); + } catch (error) { + console.error('Error al enviar el registro:', error.message); } + } } export default CreationHistoricalRecord; diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index 41953d22..e22d2e08 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -12,7 +12,6 @@ class RankingRetriever{ try { const response = await axios.get(this.apiUrl + '/top10');//finding the top ten const receivedTopTenRanking = await response.data; - console.log(receivedTopTenRanking) return receivedTopTenRanking; } catch (error) { console.log(error) @@ -80,8 +79,7 @@ class RankingRetriever{ try { const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten const receivedMyRanking = await response.data; - console.log(receivedMyRanking) - return receivedMyRanking; + return receivedMyRanking.userCompetitiveStats; } catch (error) { console.log(error) throw new Error(error); diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index 6d172024..f59a53ce 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -25,7 +25,7 @@ const RankingView = () => { } } - if(rankingData==null){ + if(rankingData==null || myRankingData == null){ getRanking(); } @@ -57,7 +57,7 @@ const RankingView = () => { - + diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js index dd0c05fd..a0a5762a 100644 --- a/webapp/src/components/ranking/RankingView.test.js +++ b/webapp/src/components/ranking/RankingView.test.js @@ -94,10 +94,13 @@ describe('RankingView component', () => { }); mockAxios.onGet('http://localhost:8000/record/ranking/myUser').reply(200, - { + {usersCompetitiveStats: + { "_id": "myUser", "totalPoints": 250, - "totalCompetitiveGames": 1 + "totalCompetitiveGames": 1, + "position":10 + } } ); const user = { username: 'myUser' }; From e497488e9c0f4a54a8b104863fa21392b7e5a7af Mon Sep 17 00:00:00 2001 From: uo289267 Date: Sun, 21 Apr 2024 11:04:35 +0200 Subject: [PATCH 63/74] Added search in Ranking --- webapp/src/components/ranking/RankingView.js | 29 ++++++++++++++++++-- webapp/src/custom.css | 18 ++++++++++++ webapp/src/translations/en/global.json | 4 ++- webapp/src/translations/es/global.json | 4 ++- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index 6d172024..136a84aa 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -10,6 +10,8 @@ const retriever = new RankingRetriever(); const RankingView = () => { const [rankingData, setRankingData] = useState(null); const [myRankingData, setMyRankingData] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); // Nuevo estado para el término de búsqueda + const[t] = useTranslation("global"); const {user} = useUserContext(); @@ -17,14 +19,25 @@ const RankingView = () => { try { var ranking = await retriever.getTopTen(); setRankingData(ranking.usersCompetitiveStats); - var myrank = await retriever.getMyPosition(user.username); + var myrank = await retriever.getUser(user.username); setMyRankingData(myrank); console.log(myrank) } catch (error) { console.log(error); } } - + const handleSearch = async (e) => { + e.preventDefault(); + if(searchTerm.length!==0){ + try { + const rank = await retriever.getUser(searchTerm); + setMyRankingData(rank); + } catch (error) { + console.log(error); + } + } + + } if(rankingData==null){ getRanking(); } @@ -33,7 +46,18 @@ const RankingView = () => {

{t("ranking.ranking")}

+ + setSearchTerm(e.target.value)} + placeholder={t("ranking.enter_username")} + /> + + {rankingData && rankingData.length > 0 && myRankingData ? ( + <> +
0{myRankingData.position} {myRankingData._id} {myRankingData.totalPoints} {myRankingData.totalCompetitiveGames}
@@ -64,6 +88,7 @@ const RankingView = () => {
+ ) : ( < Loader /> )} diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 8771f7dc..4c819c13 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1655,3 +1655,21 @@ h2{ .table tbody tr:last-child td:last-child { border-right: 2px solid rgba(205, 187, 187, 0.455); /* Borde derecho */ } + +/* Estilo para el cuadro de búsqueda */ +input[type="text"] { + border: 2px solid #636262; /* Borde sólido */ + padding: 8px; /* Espaciado interno */ + font-size: 16px; /* Tamaño de letra */ + color: #e5e0e0; /* Color de letra */ + background-color: #2a2929; /* Color de fondo */ +} + +/* Estilo para el botón */ +.search > button{ + font-size: 18px; /* Tamaño de letra */ + width: 7em; + height: 2em; +} + + diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 688d269c..c076edbd 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -103,7 +103,9 @@ "position":"Position", "username":"Username", "points":"Points", - "num_games":"Competitive games" + "num_games":"Competitive games", + "search":"Search", + "enter_username":"Enter Username..." }, "error":{ "error":"Error", diff --git a/webapp/src/translations/es/global.json b/webapp/src/translations/es/global.json index f0a2c80c..898263d1 100644 --- a/webapp/src/translations/es/global.json +++ b/webapp/src/translations/es/global.json @@ -111,7 +111,9 @@ "position":"Posición", "username":"Username", "points":"Puntos", - "num_games":"Juegos Competitivos" + "num_games":"Juegos Competitivos", + "search":"Buscar", + "enter_username":"Inserta usuario..." } } From 3801d71f82fe5b8b10c0ca47203369f8e33fbc25 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 21 Apr 2024 11:46:26 +0200 Subject: [PATCH 64/74] Tested that the search user in ranking works --- .../components/ranking/RankingRetriever.js | 2 +- webapp/src/components/ranking/RankingView.js | 83 ++++++++++--------- webapp/src/custom.css | 7 +- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index e22d2e08..a84de7e6 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -74,7 +74,7 @@ class RankingRetriever{ ] }*/ } - async getMyPosition(user){ + async getUser(user){ try { const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index e7c76f45..e88581ec 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -46,48 +46,53 @@ const RankingView = () => {

{t("ranking.ranking")}

-
- setSearchTerm(e.target.value)} - placeholder={t("ranking.enter_username")} - /> - -
{rankingData && rankingData.length > 0 && myRankingData ? ( <> - - - - - - - - - - - - {rankingData.map((user, index) => ( - - - - - + +
{t("ranking.position")}{t("ranking.username")}{t("ranking.points")}{t("ranking.num_games")}
{index + 1}{user._id}{user.totalPoints}{user.totalCompetitiveGames}
+ + + + + + - ))} - {/* Blank row */} - - - - - - - - - - -
{t("ranking.position")}{t("ranking.username")}{t("ranking.points")}{t("ranking.num_games")}
{myRankingData.position}{myRankingData._id}{myRankingData.totalPoints}{myRankingData.totalCompetitiveGames}
+ + + {rankingData.map((user, index) => ( + + {index + 1} + {user._id} + {user.totalPoints} + {user.totalCompetitiveGames} + + ))} + {/* Blank row */} + + + setSearchTerm(e.target.value)} + placeholder={t("ranking.enter_username")} + /> + + +
+ +
+ + + + + {myRankingData.position} + {myRankingData._id} + {myRankingData.totalPoints} + {myRankingData.totalCompetitiveGames} + + + + ) : ( < Loader /> diff --git a/webapp/src/custom.css b/webapp/src/custom.css index 4c819c13..f6660f91 100644 --- a/webapp/src/custom.css +++ b/webapp/src/custom.css @@ -1598,6 +1598,11 @@ h2{ box-shadow: 0 0 10px rgba(197, 191, 191, 0.726);*/ } +.table tbody tr.penultimate-row td div{ + background-color: #171717; + padding: 0.5em; +} + .table th,td{ padding-left: 30px; padding-right: 30px; @@ -1666,7 +1671,7 @@ input[type="text"] { } /* Estilo para el botón */ -.search > button{ +#search { font-size: 18px; /* Tamaño de letra */ width: 7em; height: 2em; From e407b7b4d310d7a6af26efbb54addee092db9108 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 21 Apr 2024 12:12:37 +0200 Subject: [PATCH 65/74] Made the default search value the username and fixed bad mock on tests --- webapp/src/components/ranking/RankingView.js | 9 +++++---- webapp/src/components/ranking/RankingView.test.js | 15 ++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index e88581ec..f2aad16e 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -8,12 +8,14 @@ import { useUserContext } from '../loginAndRegistration/UserContext'; const retriever = new RankingRetriever(); const RankingView = () => { + const[t] = useTranslation("global"); + const {user} = useUserContext(); + const [rankingData, setRankingData] = useState(null); const [myRankingData, setMyRankingData] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); // Nuevo estado para el término de búsqueda + const [searchTerm, setSearchTerm] = useState(user.username); + - const[t] = useTranslation("global"); - const {user} = useUserContext(); const getRanking = async () => { try { @@ -21,7 +23,6 @@ const RankingView = () => { setRankingData(ranking.usersCompetitiveStats); var myrank = await retriever.getUser(user.username); setMyRankingData(myrank); - console.log(myrank) } catch (error) { console.log(error); } diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js index a0a5762a..76c0506a 100644 --- a/webapp/src/components/ranking/RankingView.test.js +++ b/webapp/src/components/ranking/RankingView.test.js @@ -18,24 +18,23 @@ global.i18en = i18en; const mockAxios = new MockAdapter(axios); describe('RankingView component', () => { - + const user = { username: 'myUser' }; it('renders title', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText(i18en.t('ranking.ranking')); expect(text).toBeInTheDocument(); }); it('renders Loading if the call to the gateway has not been done', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText('Loading...'); expect(text).toBeInTheDocument(); }); }); - describe('RankingView component with endpoint', ()=>{ mockAxios.onGet('http://localhost:8000/record/ranking/top10').reply(200, { @@ -94,7 +93,7 @@ describe('RankingView component', () => { }); mockAxios.onGet('http://localhost:8000/record/ranking/myUser').reply(200, - {usersCompetitiveStats: + {userCompetitiveStats: { "_id": "myUser", "totalPoints": 250, @@ -111,11 +110,12 @@ describe('RankingView component', () => { await render(); }) - await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); + await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); expect(screen.getByText(i18en.t('ranking.username'))).toBeInTheDocument() expect(screen.getByText(i18en.t('ranking.points'))).toBeInTheDocument() expect(screen.getByText(i18en.t('ranking.num_games'))).toBeInTheDocument() - }); + }); + it('renders position all users usernames',async ()=>{ await act(async () =>{ await render(); @@ -177,5 +177,6 @@ describe('RankingView component', () => { expect(screen.getAllByText(/1/).length).toBeGreaterThanOrEqual(2);//hay dos pq hay una posicion }); + }) From 25d65b92242ff6c0508b673797c40a72da4e73b0 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 21 Apr 2024 12:44:48 +0200 Subject: [PATCH 66/74] Added test case to increase test coverage --- .../questionView/QuestionView.test.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/webapp/src/components/questionView/QuestionView.test.js b/webapp/src/components/questionView/QuestionView.test.js index a3a34531..90248453 100644 --- a/webapp/src/components/questionView/QuestionView.test.js +++ b/webapp/src/components/questionView/QuestionView.test.js @@ -51,9 +51,9 @@ global.i18en = i18en; describe('Question View component', () => { - beforeEach(() => { - mockAxios.reset(); - }); + mockAxios.onGet('http://localhost:8000/questions/en').reply(200, + [{question: "What is the population of Oviedo?", + answers: ["225089","272357","267855","231841"]}]); it('shows the no_questions_message as the endpoint does not exist',async () => { render(); @@ -65,10 +65,6 @@ describe('Question View component', () => { // Test for sound functionality it('speaks the question when the speaker button is clicked', async () => { - const questionText = "What is the population of Oviedo?"; - mockAxios.onGet('http://localhost:8000/questions/en').reply(200, - [{question: questionText, - answers: ["225089","272357","267855","231841"]}]); await act(async () => { render(); @@ -83,10 +79,6 @@ describe('Question View component', () => { }); it('shows a question and answers',async () => { - - mockAxios.onGet('http://localhost:8000/questions/en').reply(200, - [{question: "What is the population of Oviedo?", - answers: ["225089","272357","267855","231841"]}]); //It gives an error as we are not wrapping it by act, however by doing this we simulate a no questions situation await act(async () =>{ @@ -104,9 +96,6 @@ describe('Question View component', () => { }); it('shows colors to reveal correct answer and it sounds', async () => { setupAudioMock(); - mockAxios.onGet('http://localhost:8000/questions/en').reply(200, - [{question: "What is the population of Oviedo?", - answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ await render(); @@ -125,9 +114,6 @@ describe('Question View component', () => { }); it('shows colors to reveal false answer and it sounds', async () => { setupAudioMock() - mockAxios.onGet('http://localhost:8000/questions/en').reply(200, - [{question: "What is the population of Oviedo?", - answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ await render(); @@ -146,9 +132,6 @@ describe('Question View component', () => { it('shows timer and tiktak sound', async () => { setupAudioMock() - mockAxios.onGet('http://localhost:8000/questions/en').reply(200, - [{question: "What is the population of Oviedo?", - answers: ["225089","272357","267855","231841"]}]); await act(async () =>{ await render(); @@ -158,7 +141,24 @@ describe('Question View component', () => { const timerElement = screen.getByText(new RegExp(`(\\d+) ${i18en.t('questionView.seconds')}`)); expect(timerElement).toBeInTheDocument(); // Verificar que el temporizador esté presente en el DOM - }); + }); + + it('shows finish game review',async () => { + mockAxios.onGet('http://localhost:8000/questions/en').reply(200, []); + mockAxios.onPost('http://localhost:8000/record').reply(200, {user:'myUser'}); + + const user = { username: 'myUser' }; + + //It gives an error as we are not wrapping it by act, however by doing this we simulate a no questions situation + await act(async () =>{ + await render(); + }) + + await waitFor(() => expect(screen.getByText(i18en.t('questionView.finished_game'))).toBeInTheDocument()); + + expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument() + + }); // it('renders end message when countdown completes', async() => { From 51ff8391dff9322cd1500ebf99059593dc3928a4 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Sun, 21 Apr 2024 19:08:51 +0200 Subject: [PATCH 67/74] Fixed all tests that did not use the Cookie and the email --- webapp/src/App.js | 4 +- webapp/src/App.test.js | 2 +- .../loginAndRegistration/AddUser.js | 246 ++++++++---------- .../loginAndRegistration/AddUser.test.js | 31 ++- .../questionView/QuestionGenerator.js | 2 +- .../questionView/QuestionView.test.js | 8 +- .../components/ranking/RankingRetriever.js | 8 +- webapp/src/components/ranking/RankingView.js | 12 +- .../components/ranking/RankingView.test.js | 21 +- webapp/src/translations/en/global.json | 3 +- 10 files changed, 156 insertions(+), 181 deletions(-) diff --git a/webapp/src/App.js b/webapp/src/App.js index 4c32df75..970ac846 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -11,7 +11,6 @@ import Container from '@mui/material/Container'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import './custom.css'; import HistoricalView from './components/HistoricalData/HistoricalView'; -import React from 'react'; import Cookies from 'js-cookie'; import GameConfigurator from './components/GameConfigurator/GameConfigurator'; import RankingView from './components/ranking/RankingView'; @@ -22,7 +21,8 @@ function App() { document.title = 'WIQ'; }, []); - const isLoggedIn = !!Cookies.get('user'); + //The double !! converts an expression that can be a boolean into an actual boolean + const isLoggedIn = !!Cookies.get('user'); return ( diff --git a/webapp/src/App.test.js b/webapp/src/App.test.js index 0d9927ca..3756fd6f 100644 --- a/webapp/src/App.test.js +++ b/webapp/src/App.test.js @@ -1,4 +1,4 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { render} from '@testing-library/react'; import App from './App'; diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index cb27a124..e5552072 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -7,67 +7,75 @@ import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import zxcvbn from "zxcvbn"; - const AddUser = () => { const navigate = useNavigate(); const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/adduser"; const { t } = useTranslation("global"); + const [email, setEmail] = useState(''); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [repeatPassword, setRepeatPassword] = useState(''); - const [passwordStrength, setPasswordStrength] = useState(undefined); + const [passwordStrength, setPasswordStrength] = useState(''); const [passwordStrengthText, setPasswordStrengthText] = useState(''); - const [submitError, setSubmitError] = useState(''); + const [submitErrors, setSubmitErrors] = useState([]); + const validateEmail = (email) => { + return String(email) + .toLowerCase() + .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); + }; const handleSubmit = async (event) => { event.preventDefault(); - try { - //Validations - //TODO: email validation - if(password !== repeatPassword){ - //User put the same password - setSubmitError("addUser.error_passwords_no_match"); - } else if(/\s/.test(password)){ - //User put spaces in password - setSubmitError("addUser.error_password_spaces"); - } else if(password.length < 8){ - //Password too short - setSubmitError("addUser.error_password_minimum_length"); - } else if(password.length > 64){ - //Password too long - setSubmitError("addUser.error_password_maximum_length"); - } else if(/\s/.test(username)){ - //Spaces in username - setSubmitError("addUser.error_username_spaces"); - } else{ - //Continue - setSubmitError(''); + + const newSubmitErrors = []; + + //Validations + + if(!validateEmail(email)){ + //User put a wrong format email + newSubmitErrors.push("addUser.error_wrong_email_format") + } + + if(password !== repeatPassword){ + //User put the same password + newSubmitErrors.push("addUser.error_passwords_no_match"); + } + if(/\s/.test(password)){ + //User put spaces in password + newSubmitErrors.push("addUser.error_password_spaces"); + } + if(password.length < 8){ + //Password too short + newSubmitErrors.push("addUser.error_password_minimum_length"); + } + + if(password.length > 64){ + //Password too long + newSubmitErrors.push("addUser.error_password_maximum_length"); + } + + if(/\s/.test(username)){ + //Spaces in username + newSubmitErrors.push("addUser.error_username_spaces"); + } + + setSubmitErrors(newSubmitErrors); + + if (newSubmitErrors.length === 0) { + try { const response = await axios.post(apiUrl, { email, username, password }); - console.log("Registered user: " + response.data.username); navigate('/login'); + } catch (error) { + if (error.response.data.error === "Username already in use") { + setSubmitErrors(["addUser.error_username_in_use"]); + } + console.error("Error adding user:", error); } - - } catch (error) { - if(error.response.data.error === "Username already in use"){ //TODO: Improve - setSubmitError("addUser.error_username_in_use"); - } - console.error('Error adding user:', error); } }; - //Possible email validation - /** - const validateEmail = (email) => { - return String(email) - .toLowerCase() - .match( - /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - ); - }; - */ - const handlePasswordChange = (e) => { const newPassword = e.target.value; setPassword(newPassword); @@ -97,74 +105,79 @@ const AddUser = () => { setPasswordStrength(newStrength); }; + const showErrors = () => { + if (submitErrors.length > 0) { + return submitErrors.map((error, index) => ( +

{t(error)}

+ )); + } + return null; + }; + return (
-
-
-
-

{t("addUser.title")}

-
-

{t("addUser.email_placeholder")}:

- +
+ +

{t("addUser.title")}

+ {showErrors()} +
+

{t("addUser.email_placeholder")}:

+ setEmail(e.target.value)} /> -
-
-

{t("addUser.username_placeholder")}:

- +
+

{t("addUser.username_placeholder")}:

+ setUsername(e.target.value)} /> -
-
-

{t("addUser.password_placeholder")}:

- +
+

{t("addUser.password_placeholder")}:

+ -
-
- - {t(passwordStrengthText.toString())} - - -
-
-

{t("addUser.repeat_password_placeholder")}:

- setRepeatPassword(e.target.value)} - /> -
- {submitError &&

{t(submitError)}

} - - - - - - -
-
+
+
+ {t(passwordStrengthText)} + +
+
+

{t("addUser.repeat_password_placeholder")}:

+ setRepeatPassword(e.target.value)} + /> +
+ + + + +
+
); }; @@ -172,61 +185,10 @@ const AddUser = () => { function LinkLogin() { const { t } = useTranslation("global"); return ( - + {t("addUser.login_link")} ); } export default AddUser; - -// const [username, setUsername] = useState(''); -// const [password, setPassword] = useState(''); -// const [error, setError] = useState(''); -// const [openSnackbar, setOpenSnackbar] = useState(false); - -// const addUser = async () => { -// try { -// await axios.post(`${apiEndpoint}/adduser`, { username, password }); -// setOpenSnackbar(true); -// } catch (error) { -// setError(error.response.data.error); -// } -// }; - -// const handleCloseSnackbar = () => { -// setOpenSnackbar(false); -// }; - -// return ( -// -// -// Add User -// -// setUsername(e.target.value)} -// /> -// setPassword(e.target.value)} -// /> -// -// -// {error && ( -// setError('')} message={`Error: ${error}`} /> -// )} -// -// ); -// }; diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js index 70493840..cd36c68c 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.test.js +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -33,7 +33,10 @@ describe('', () => { }); - const fillFormAndSubmit = (username, password, repeatPassword) => { + const fillFormAndSubmit = (email, username, password, repeatPassword) => { + const emailInput = screen.getByPlaceholderText('addUser.email_placeholder'); + fireEvent.change(emailInput, { target: { value: email } }); + const usernameInput = screen.getByPlaceholderText('addUser.username_placeholder'); fireEvent.change(usernameInput, { target: { value: username } }); @@ -48,28 +51,40 @@ describe('', () => { }; test('displays correct error messages', async () => { + //Wrong email format lacks @ + fillFormAndSubmit('userexample.com', 'username', '12345678', '123456789'); + expect(screen.getByText('addUser.error_wrong_email_format')).toBeInTheDocument(); + //Wrong email format lacks domain + fillFormAndSubmit('user@example', 'username', '12345678', '123456789'); + expect(screen.getByText('addUser.error_wrong_email_format')).toBeInTheDocument(); //Passwords do not match - fillFormAndSubmit('username', '12345678', '123456789'); + fillFormAndSubmit('user@example.com', 'username', '12345678', '123456789'); expect(screen.getByText('addUser.error_passwords_no_match')).toBeInTheDocument(); //Password with spaces - fillFormAndSubmit('username', '1234 5678', '1234 5678'); + fillFormAndSubmit('user@example.com', 'username', '1234 5678', '1234 5678'); expect(screen.getByText('addUser.error_password_spaces')).toBeInTheDocument(); //Password too short - fillFormAndSubmit('username', '1234567', '1234567'); + fillFormAndSubmit('user@example.com', 'username', '1234567', '1234567'); expect(screen.getByText('addUser.error_password_minimum_length')).toBeInTheDocument(); //Password too long - fillFormAndSubmit('username', '01234567890123456789012345678901234567890123456789012345678901234', '01234567890123456789012345678901234567890123456789012345678901234'); + fillFormAndSubmit('user@example.com', 'username', '01234567890123456789012345678901234567890123456789012345678901234', '01234567890123456789012345678901234567890123456789012345678901234'); expect(screen.getByText('addUser.error_password_maximum_length')).toBeInTheDocument(); //Username with spaces - fillFormAndSubmit('user name', '12345678', '12345678'); + fillFormAndSubmit('user@example.com', 'user name', '12345678', '12345678'); + expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument(); + + //Show various errors + fillFormAndSubmit('userexample.com', 'user name', '12345678', '12345678'); expect(screen.getByText('addUser.error_username_spaces')).toBeInTheDocument(); + expect(screen.getByText('addUser.error_wrong_email_format')).toBeInTheDocument(); + //Username in use axios.post.mockRejectedValue({ response: { data: { error: 'Username already in use' } } }); - fillFormAndSubmit('existing_user', '12345678', '12345678'); + fillFormAndSubmit('user@example.com', 'existing_user', '12345678', '12345678'); await waitFor(() => { expect(screen.getByText('addUser.error_username_in_use')).toBeInTheDocument(); }); - expect(axios.post).toHaveBeenCalledWith(expect.any(String), { username: 'existing_user', password: '12345678' }); + expect(axios.post).toHaveBeenCalledWith(expect.any(String), { email: 'user@example.com' ,username: 'existing_user', password: '12345678' }); }); }); diff --git a/webapp/src/components/questionView/QuestionGenerator.js b/webapp/src/components/questionView/QuestionGenerator.js index b8ef243c..ec7a1b6d 100644 --- a/webapp/src/components/questionView/QuestionGenerator.js +++ b/webapp/src/components/questionView/QuestionGenerator.js @@ -42,7 +42,7 @@ class QuestionGenerator{ if(type==="COMPETITIVE"){ response = await axios.get(this.apiUrl + '/' + lang, {headers : {'token':token}}); }else{ - response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type); + response = await axios.get(this.apiUrl + '/' + lang + '/' +amount + '/' + type, {headers : {'token':token}}); } console.log(response) const receivedQuestions = await response.data; diff --git a/webapp/src/components/questionView/QuestionView.test.js b/webapp/src/components/questionView/QuestionView.test.js index cc0858c4..d00a36e9 100644 --- a/webapp/src/components/questionView/QuestionView.test.js +++ b/webapp/src/components/questionView/QuestionView.test.js @@ -67,7 +67,7 @@ describe('Question View component', () => { it('speaks the question when the speaker button is clicked', async () => { await act(async () => { - render(); + render(); }); fireEvent.click(screen.getByText('🔊')); @@ -147,11 +147,9 @@ describe('Question View component', () => { mockAxios.onGet('http://localhost:8000/questions/en').reply(200, []); mockAxios.onPost('http://localhost:8000/record').reply(200, {user:'myUser'}); - const user = { username: 'myUser' }; - //It gives an error as we are not wrapping it by act, however by doing this we simulate a no questions situation await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('questionView.finished_game'))).toBeInTheDocument()); @@ -168,7 +166,7 @@ describe('Question View component', () => { // [{question: "What is the population of Oviedo?", // answers: ["225089","272357","267855","231841"]}]); // await act(async () =>{ - // await render(); + // await render(); // }) // await waitFor(() => expect(screen.getByText('What is the population of Oviedo?')).toBeInTheDocument()); diff --git a/webapp/src/components/ranking/RankingRetriever.js b/webapp/src/components/ranking/RankingRetriever.js index a84de7e6..03ebcd6c 100644 --- a/webapp/src/components/ranking/RankingRetriever.js +++ b/webapp/src/components/ranking/RankingRetriever.js @@ -7,10 +7,10 @@ class RankingRetriever{ } - async getTopTen() { + async getTopTen(token) { try { - const response = await axios.get(this.apiUrl + '/top10');//finding the top ten + const response = await axios.get(this.apiUrl + '/top10', {headers : {'token':token}});//finding the top ten const receivedTopTenRanking = await response.data; return receivedTopTenRanking; } catch (error) { @@ -74,10 +74,10 @@ class RankingRetriever{ ] }*/ } - async getUser(user){ + async getUser(user, token){ try { - const response = await axios.get(this.apiUrl + '/'+user);//finding the top ten + const response = await axios.get(this.apiUrl + '/'+user, {headers : {'token':token}});//finding the top ten const receivedMyRanking = await response.data; return receivedMyRanking.userCompetitiveStats; } catch (error) { diff --git a/webapp/src/components/ranking/RankingView.js b/webapp/src/components/ranking/RankingView.js index f2aad16e..60fd368d 100644 --- a/webapp/src/components/ranking/RankingView.js +++ b/webapp/src/components/ranking/RankingView.js @@ -3,25 +3,25 @@ import RankingRetriever from './RankingRetriever'; import {useTranslation} from "react-i18next"; import Loader from "../fragments/Loader" import BackButton from '../fragments/BackButtonToGameMenu'; -import { useUserContext } from '../loginAndRegistration/UserContext'; +import Cookies from 'js-cookie' const retriever = new RankingRetriever(); const RankingView = () => { const[t] = useTranslation("global"); - const {user} = useUserContext(); + const cookie = JSON.parse(Cookies.get('user')) const [rankingData, setRankingData] = useState(null); const [myRankingData, setMyRankingData] = useState(null); - const [searchTerm, setSearchTerm] = useState(user.username); + const [searchTerm, setSearchTerm] = useState(cookie.username); const getRanking = async () => { try { - var ranking = await retriever.getTopTen(); + var ranking = await retriever.getTopTen(cookie.token); setRankingData(ranking.usersCompetitiveStats); - var myrank = await retriever.getUser(user.username); + var myrank = await retriever.getUser(cookie.username, cookie.token); setMyRankingData(myrank); } catch (error) { console.log(error); @@ -31,7 +31,7 @@ const RankingView = () => { e.preventDefault(); if(searchTerm.length!==0){ try { - const rank = await retriever.getUser(searchTerm); + const rank = await retriever.getUser(searchTerm, cookie.token); setMyRankingData(rank); } catch (error) { console.log(error); diff --git a/webapp/src/components/ranking/RankingView.test.js b/webapp/src/components/ranking/RankingView.test.js index 76c0506a..d8640b31 100644 --- a/webapp/src/components/ranking/RankingView.test.js +++ b/webapp/src/components/ranking/RankingView.test.js @@ -6,7 +6,8 @@ import i18en from 'i18next'; import RankingView from './RankingView'; import MockAdapter from 'axios-mock-adapter'; import { act } from 'react-dom/test-utils'; -import { UserContextProvider} from '../loginAndRegistration/UserContext'; +import Cookies from 'js-cookie' + i18en.use(initReactI18next).init({ resources: {}, lng: 'en', @@ -15,21 +16,20 @@ i18en.use(initReactI18next).init({ } }); global.i18en = i18en; - +Cookies.set('user', JSON.stringify({username:"myUser", token:"fasfda"})) const mockAxios = new MockAdapter(axios); describe('RankingView component', () => { - const user = { username: 'myUser' }; it('renders title', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText(i18en.t('ranking.ranking')); expect(text).toBeInTheDocument(); }); it('renders Loading if the call to the gateway has not been done', () => { act(()=>{ - render(); + render(); }) const text = screen.getByText('Loading...'); expect(text).toBeInTheDocument(); @@ -102,12 +102,11 @@ describe('RankingView component', () => { } } ); - const user = { username: 'myUser' }; it('renders position all headers in the table',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -118,7 +117,7 @@ describe('RankingView component', () => { it('renders position all users usernames',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -136,7 +135,7 @@ describe('RankingView component', () => { it('renders position all users totalPoints',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -153,7 +152,7 @@ describe('RankingView component', () => { it('renders position all users competitive games',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); @@ -166,7 +165,7 @@ describe('RankingView component', () => { it('renders position all users competitive games',async ()=>{ await act(async () =>{ - await render(); + await render(); }) await waitFor(() => expect(screen.getByText(i18en.t('ranking.position'))).toBeInTheDocument()); diff --git a/webapp/src/translations/en/global.json b/webapp/src/translations/en/global.json index 3024588d..54f087cf 100644 --- a/webapp/src/translations/en/global.json +++ b/webapp/src/translations/en/global.json @@ -62,7 +62,8 @@ "error_username_spaces": "Username cannot contain spaces", "error_password_minimum_length": "Password must be at least 8 characters long", "error_password_maximum_length": "Password cannot be over 64 characters long", - "error_username_in_use": "Username already in use" + "error_username_in_use": "Username already in use", + "error_wrong_email_format": "Wrong email format (user@example.com)" }, "gameMenu":{ "history_button":"View Historical Data", From de0e44d8bf937890a3fe4e94bff9c026e2b17765 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 12:54:42 +0200 Subject: [PATCH 68/74] Now registering is the same as login, added e2e tests --- users/userservice/package.json | 1 + users/userservice/user-service.js | 8 +++-- webapp/e2e/steps/gameMenu.steps.js | 17 +++++++--- webapp/e2e/steps/home.steps.js | 7 +++-- webapp/e2e/utils.js | 31 +++++++++++++++++++ webapp/src/App.js | 2 +- webapp/src/components/fragments/NavBar.js | 2 +- .../loginAndRegistration/AddUser.js | 7 ++++- 8 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 webapp/e2e/utils.js diff --git a/users/userservice/package.json b/users/userservice/package.json index 71cc1cdb..a2efb649 100644 --- a/users/userservice/package.json +++ b/users/userservice/package.json @@ -21,6 +21,7 @@ "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.4" }, "devDependencies": { diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 34cfb7e0..9b058aa1 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -3,6 +3,7 @@ const express = require('express'); const mongoose = require('mongoose'); const bcrypt = require('bcrypt'); const bodyParser = require('body-parser'); +const jwt = require('jsonwebtoken'); const User = require('./user-model') const app = express(); @@ -58,8 +59,11 @@ app.post('/adduser', async (req, res) => { password: hashedPassword, }); - await newUser.save(); - res.json({username: newUser.username}); + const savedUser = await newUser.save(); + + const token = jwt.sign({ userId: savedUser._id }, (process.env.JWT_KEY??'my-key'), { expiresIn: '1h' }); + + res.json({ token: token, username: savedUser.username, email: savedUser.email}); } catch (error) { res.status(400).json({ error: error.message }); }}); diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 215af2f7..8cddc046 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -4,20 +4,29 @@ const setDefaultOptions = require('expect-puppeteer').setDefaultOptions; const feature = loadFeature('./features/gameMenu.feature'); +const { register, login, logout } = require("../utils"); + let page; let browser; +const email = "testUser@example.com"; +const username = "testUser" +const password = "testUserPassword" + defineFeature(feature, test => { beforeAll(async () => { browser = await puppeteer.launch({ - slowMo: 20, - defaultViewport: { width: 1920, height: 1080 }, - args: ['--window-size=1920,1080'] + headless: false, // Muestra el navegador + slowMo: 40, + defaultViewport: { width: 1920, height: 1080 }, + args: ['--window-size=1920,1080'] }); page = await browser.newPage(); setDefaultOptions({ timeout: 10000 }); + + await register(page, email, username, password); }); test('There should be visible three links', ({ given, then }) => { @@ -29,7 +38,7 @@ defineFeature(feature, test => { then('three buttons should be visible', async () => { //await expect(page).toMatchElement('.linkButton'); const elements = await page.$$('.linkButton'); - expect(elements.length).toBeGreaterThan(0); // At least one element with class 'linkButton' + expect(elements.length).toBe(3); }); }); test('New Game should go to game configurator', ({ given, when, then }) => { diff --git a/webapp/e2e/steps/home.steps.js b/webapp/e2e/steps/home.steps.js index 770b2a0a..3bae6848 100644 --- a/webapp/e2e/steps/home.steps.js +++ b/webapp/e2e/steps/home.steps.js @@ -11,9 +11,10 @@ defineFeature(feature, test => { beforeAll(async () => { browser = await puppeteer.launch({ - slowMo: 20, - defaultViewport: { width: 1920, height: 1080 }, - args: ['--window-size=1920,1080'] + headless: "new", + slowMo: 20, + defaultViewport: { width: 1920, height: 1080 }, + args: ['--window-size=1920,1080'] }); page = await browser.newPage(); diff --git a/webapp/e2e/utils.js b/webapp/e2e/utils.js new file mode 100644 index 00000000..27eff77d --- /dev/null +++ b/webapp/e2e/utils.js @@ -0,0 +1,31 @@ +async function register(page, email, username, password) { + await page.goto('http://localhost:3000/addUser'); + await page.waitForSelector('.general'); + + await page.type('input[name="email"]', email); + await page.type('input[name="username"]', username); + await page.type('input[name="password"]', password); + await page.type('input[name="repeat_password"]', password); + await page.click('button[type="submit"]'); +} + +async function login(page, username, password) { + await page.goto('http://localhost:3000/login'); + await page.waitForSelector('.general'); + + await page.type('input[type="text"]', username); + await page.type('input[type="password"]', password); + await page.click('button[type="submit"]'); +} + +async function logout(page){ + await page.click('.user-button'); + await page.waitForSelector('.MuiMenu-paper', { visible: true }); + await page.click('text=Log Out'); +} + +module.exports = { + register, + login, + logout + }; \ No newline at end of file diff --git a/webapp/src/App.js b/webapp/src/App.js index 970ac846..69c168cf 100644 --- a/webapp/src/App.js +++ b/webapp/src/App.js @@ -31,7 +31,7 @@ function App() { } /> - } /> + : } /> : } /> } /> : } /> diff --git a/webapp/src/components/fragments/NavBar.js b/webapp/src/components/fragments/NavBar.js index 11cf5642..5ce411cd 100644 --- a/webapp/src/components/fragments/NavBar.js +++ b/webapp/src/components/fragments/NavBar.js @@ -72,7 +72,7 @@ function Navbar() { onClose={handleUserMenuClose} disableAutoFocusItem > - removeCookie()}> {t("navBar.logout")} + removeCookie()}> {t("navBar.logout")} ) : null} diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index e5552072..103fbe44 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -6,6 +6,7 @@ import axios from 'axios'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import zxcvbn from "zxcvbn"; +import Cookies from 'js-cookie'; const AddUser = () => { const navigate = useNavigate(); @@ -66,7 +67,11 @@ const AddUser = () => { if (newSubmitErrors.length === 0) { try { const response = await axios.post(apiUrl, { email, username, password }); - navigate('/login'); + let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) + Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) + , {expires:oneHourAfter}); + navigate('/menu'); + window.location.reload(); } catch (error) { if (error.response.data.error === "Username already in use") { setSubmitErrors(["addUser.error_username_in_use"]); From 7d79eb40f78b3332767c6c14d9575eb925b07df0 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 12:57:37 +0200 Subject: [PATCH 69/74] Made npm install to sync package.json and package-lock.json --- users/userservice/package-lock.json | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/users/userservice/package-lock.json b/users/userservice/package-lock.json index f21b26cb..e2ccf4a2 100644 --- a/users/userservice/package-lock.json +++ b/users/userservice/package-lock.json @@ -12,6 +12,7 @@ "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.4" }, "devDependencies": { @@ -1656,6 +1657,11 @@ "node": "*" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2061,6 +2067,14 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3602,6 +3616,51 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", @@ -3646,6 +3705,41 @@ "node": ">=8" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", From 975cca09cd5bd4a9d7c0678564b3e6138e4e15bf Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 13:02:42 +0200 Subject: [PATCH 70/74] Forgot to remove headless mode --- webapp/e2e/steps/gameMenu.steps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 8cddc046..4d16b917 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -17,7 +17,7 @@ defineFeature(feature, test => { beforeAll(async () => { browser = await puppeteer.launch({ - headless: false, // Muestra el navegador + headless: "new", slowMo: 40, defaultViewport: { width: 1920, height: 1080 }, args: ['--window-size=1920,1080'] From 19a68d3dbcff093277ff1ce16f364ed5ac937585 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 13:37:33 +0200 Subject: [PATCH 71/74] Now e2e should work --- webapp/e2e/steps/gameMenu.steps.js | 7 ++++++- webapp/e2e/utils.js | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/webapp/e2e/steps/gameMenu.steps.js b/webapp/e2e/steps/gameMenu.steps.js index 4d16b917..9d00a32d 100644 --- a/webapp/e2e/steps/gameMenu.steps.js +++ b/webapp/e2e/steps/gameMenu.steps.js @@ -24,11 +24,16 @@ defineFeature(feature, test => { }); page = await browser.newPage(); - setDefaultOptions({ timeout: 10000 }); + setDefaultOptions({ timeout: 30000 }); await register(page, email, username, password); }); + beforeEach(async () => { + await logout(page); + await login(page, username, password); + }) + test('There should be visible three links', ({ given, then }) => { given('I am on the game menu', async () => { await page.goto('http://localhost:3000/menu'); diff --git a/webapp/e2e/utils.js b/webapp/e2e/utils.js index 27eff77d..b25f6aaa 100644 --- a/webapp/e2e/utils.js +++ b/webapp/e2e/utils.js @@ -7,6 +7,8 @@ async function register(page, email, username, password) { await page.type('input[name="password"]', password); await page.type('input[name="repeat_password"]', password); await page.click('button[type="submit"]'); + //Wait for menu to load + await page.waitForSelector('.divMenu'); } async function login(page, username, password) { @@ -16,12 +18,16 @@ async function login(page, username, password) { await page.type('input[type="text"]', username); await page.type('input[type="password"]', password); await page.click('button[type="submit"]'); + //Wait for menu to load + await page.waitForSelector('.divMenu'); } async function logout(page){ await page.click('.user-button'); await page.waitForSelector('.MuiMenu-paper', { visible: true }); await page.click('text=Log Out'); + //Wait for home to load + await page.waitForSelector('.general'); } module.exports = { From e27f8427be889aaab9995dca802c41e73b2feef6 Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 19:24:50 +0200 Subject: [PATCH 72/74] Added validations to solve sonarcloud security ranking --- gatewayservice/gateway-service.js | 148 +++++++++--------- gatewayservice/gateway-service.test.js | 21 +-- users/userservice/user-service.js | 41 ++++- users/userservice/user-service.test.js | 63 +++++++- .../loginAndRegistration/AddUser.js | 2 +- 5 files changed, 173 insertions(+), 102 deletions(-) diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index ce9298ce..298dbc6d 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -58,45 +58,23 @@ app.get('/questions', verifyToken, async (req, res) => { } }); -app.get('/questions/:lang/:amount/:type', async (req, res) => { - try { - const lang = req.params.lang.toString(); - const amount = req.params.amount.toString(); - const type = req.params.type.toString(); - // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type); - - res.json(questionResponse.data); - } catch (error) { - - res.status(error.response.status).json({ error: error.response.data.error }); - } -}); - - -app.get('/questions/:lang/:amount', verifyToken, async (req, res) => { - try { - const lang = req.params.lang.toString(); - const amount = req.params.amount.toString(); - // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount); - - res.json(questionResponse.data); - } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); - } -}); app.get('/questions/:lang/:amount/:type', verifyToken, async (req, res) => { try { - const lang = req.params.lang.toString(); - const amount = req.params.amount.toString(); - const type = req.params.type.toString(); - // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type); - - res.json(questionResponse.data); + if(!validateLang(req.params.lang.toString()) || + !validateAmount(req.params.amount.toString()) || + !validateType(req.params.type.toString())) + res.status(400).json({ error: 'Wrong values given' }); + else { + const lang = encodeURIComponent(req.params.lang.toString()); + const amount = encodeURIComponent(req.params.amount.toString()); + const type = encodeURIComponent(req.params.type.toString()); + // Forward the question request to the quetion service + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount + '/' + type); + + res.json(questionResponse.data); + } } catch (error) { res.status(error.response.status).json({ error: error.response.data.error }); @@ -104,27 +82,36 @@ app.get('/questions/:lang/:amount/:type', verifyToken, async (req, res) => { }); -app.get('/questions/:lang/:amount', async (req, res) => { +app.get('/questions/:lang/:amount', verifyToken, async (req, res) => { try { - const lang = req.params.lang.toString(); - const amount = req.params.amount.toString(); - // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount); - - res.json(questionResponse.data); + if(!validateLang(req.params.lang.toString()) || + !validateAmount(req.params.amount.toString())) + res.status(400).json({ error: 'Wrong values given' }); + else{ + const lang = encodeURIComponent(req.params.lang.toString()); + const amount = encodeURIComponent(req.params.amount.toString()); + // Forward the question request to the quetion service + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang + '/' + amount); + + res.json(questionResponse.data); + } } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); } }); app.get('/questions/:lang', verifyToken, async (req, res) => { try { - const lang = req.params.lang.toString(); - // Forward the question request to the quetion service - const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang.toString()); - - res.json(questionResponse.data); + if(!validateLang(req.params.lang.toString())) + res.status(400).json({ error: 'Wrong values given' }); + else{ + const lang = encodeURIComponent(req.params.lang.toString()); + // Forward the question request to the quetion service + const questionResponse = await axios.get(questionServiceUrl+'/questions/' + lang.toString()); + + res.json(questionResponse.data); + } + } catch (error) { res.status(error.response.status).json({ error: error.response.data.error }); @@ -132,7 +119,7 @@ app.get('/questions/:lang', verifyToken, async (req, res) => { }); app.post('/record', verifyToken, async(req, res) => { - console.log("in") + try { // Forward the record request to the record service const recordResponse = await axios.post(recordServiceUrl+'/record', req.body); @@ -154,31 +141,14 @@ app.get('/record/ranking/top10', verifyToken, async(req, res)=>{ app.get('/record/ranking/:user', verifyToken, async(req, res)=>{ try { - const user = req.params.user; - // Forward the record request to the record service - const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user); - res.json(recordResponse.data); - } catch (error) { - res.send(error); - } -}); - -app.get('/record/ranking/top10', verifyToken, async(req, res)=>{ - try { - // Forward the record request to the record service - const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/top10'); - res.json(recordResponse.data); - } catch (error) { - res.send(error); - } -}); - -app.get('/record/ranking/:user', async(req, res)=>{ - try { - const user = req.params.user; - // Forward the record request to the record service - const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user); - res.json(recordResponse.data); + if(!validateUser(req.params.user.toString())) + res.status(400).json({ error: 'Wrong values given' }); + else{ + const user = encodeURIComponent(req.params.user.toString()); + // Forward the record request to the record service + const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/' + user); + res.json(recordResponse.data); + } } catch (error) { res.send(error); } @@ -186,10 +156,14 @@ app.get('/record/ranking/:user', async(req, res)=>{ app.get('/record/:user', verifyToken, async(req, res)=>{ try { - const user = req.params.user; - // Forward the record request to the record service - const recordResponse = await axios.get(recordServiceUrl + '/record/' + user); - res.json(recordResponse.data); + if(!validateUser(req.params.user.toString())) + res.status(400).json({ error: 'Wrong values given' }); + else{ + const user = encodeURIComponent(req.params.user.toString()); + // Forward the record request to the record service + const recordResponse = await axios.get(recordServiceUrl + '/record/' + user); + res.json(recordResponse.data); + } } catch (error) { res.send(error); } @@ -230,4 +204,22 @@ function verifyToken(req, res, next) { }); } +function validateLang(lang){ + return ['en', 'es', 'tk'].includes(lang); +} + +function validateAmount(amount) { + const parsed = parseInt(amount, 10); + // We only accept integers and positive ones + return !isNaN(parsed) && parsed > 0; +} + +function validateType(type){ + return ['POPULATION', 'CAPITAL', 'LANGUAGE', 'SIZE'].includes(type); +} + +function validateUser(user){ + return !(/\s/.test(user)) //True if there are no spaces +} + module.exports = server diff --git a/gatewayservice/gateway-service.test.js b/gatewayservice/gateway-service.test.js index 3bf212df..550c4024 100644 --- a/gatewayservice/gateway-service.test.js +++ b/gatewayservice/gateway-service.test.js @@ -100,7 +100,7 @@ describe('Gateway Service with token mock', () => { // Test /questions/:lang/:amount endpoint it('should forward questions request to question service', async () => { const response = await request(app) - .get('/questions/es/1'); + .get('/questions/es/1').set('token', 'valorDelToken'); checkQuestion(response); }); @@ -108,7 +108,7 @@ describe('Gateway Service with token mock', () => { // Test /questions/:lang/:amount/:type endpoint it('should forward questions request to question service', async () => { const response = await request(app) - .get('/questions/es/1/CAPITAL'); + .get('/questions/es/1/CAPITAL').set('token', 'valorDelToken'); checkQuestion(response); }); @@ -126,18 +126,6 @@ describe('Gateway Service with token mock', () => { it('should forward record request to record service', async () => { const response = await request(app) .get('/record/testuser').set('token', 'valorDelToken'); - expect(response.statusCode).toBe(200); - expect(response.body).toHaveProperty('record', "undefined"); - }); - - -}); - -describe('Gateway Service without token mock', () => { - // Test /record/:user endpoint - it('should not verify the token', async () => { - const response = await request(app) - .get('/record/testuser'); checkRecord(response); }); @@ -145,7 +133,7 @@ describe('Gateway Service without token mock', () => { // Test /record/ranking/:user endpoint it('should forward record request to record service', async () => { const response = await request(app) - .get('/record/ranking/testuser'); + .get('/record/ranking/testuser').set('token', 'valorDelToken'); checkRecord(response); }); @@ -153,10 +141,11 @@ describe('Gateway Service without token mock', () => { // Test /record/ranking/top10 endpoint it('should forward record request to record service', async () => { const response = await request(app) - .get('/record/ranking/top10'); + .get('/record/ranking/top10').set('token', 'valorDelToken'); checkRecord(response); }); + }); function checkRecord(response){ diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index 9b058aa1..f57a0aa5 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -17,6 +17,11 @@ const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/userdb'; mongoose.connect(mongoUri); +const validateEmail = (email) => { + return String(email) + .toLowerCase() + .match(/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); +}; // Function to validate required fields in the request body function validateRequiredFields(req, requiredFields) { @@ -25,13 +30,47 @@ function validateRequiredFields(req, requiredFields) { throw new Error(`Missing required field: ${field}`); } } + + let email = req.body.email.toString(); + let username = req.body.username.toString(); + let password = req.body.password.toString(); + let repeatPassword = req.body.repeatPassword.toString(); + + if(!validateEmail(email)){ + //User put a wrong format email + throw new Error("Wrong email format (example@example.com)") + } + + if(password !== repeatPassword){ + //User put the same password + throw new Error("Passwords dont match"); + } + if(/\s/.test(password)){ + //User put spaces in password + throw new Error("Password cannot have spaces"); + } + if(password.length < 8){ + //Password too short + throw new Error("Password must be at least 8 characters long"); + } + + if(password.length > 64){ + //Password too long + throw new Error("Password must less than 64 characters long"); + } + + if(/\s/.test(username)){ + //Spaces in username + throw new Error("Username cannot have spaces"); + } + } app.post('/adduser', async (req, res) => { try { // Check if required fields are present in the request body try{ - validateRequiredFields(req, ['email', 'username', 'password']); + validateRequiredFields(req, ['email', 'username', 'password', 'repeatPassword']); } catch(error){ res.status(400).json({ error : error.message }); diff --git a/users/userservice/user-service.test.js b/users/userservice/user-service.test.js index d5352626..ab9fa21e 100644 --- a/users/userservice/user-service.test.js +++ b/users/userservice/user-service.test.js @@ -5,9 +5,10 @@ let mongoServer; let app; let newUser = { - email: 'Nice@g.com', + email: 'example@example.com', username: 'testuser', - password: 'testpassword' + password: 'testpassword', + repeatPassword: 'testpassword' }; beforeAll(async () => { @@ -24,9 +25,10 @@ afterAll(async () => { afterEach(async () => { newUser = { - email: 'Nice@g.com', + email: 'example@example.com', username: 'testuser', - password: 'testpassword' + password: 'testpassword', + repeatPassword: 'testpassword' }; }) @@ -37,14 +39,14 @@ describe('User Service', () => { expect(response.body).toHaveProperty('username', 'testuser'); }); - it('Should show missing field user /adduser', async () => { + it('Should show missing field email /adduser', async () => { const response = await request(app).post('/adduser').send(); expect(response.status).toBe(400); expect(response.body).toHaveProperty('error', 'Missing required field: email'); }); it('Should not register user /adduser', async () => { - newUser.email = 'Nice2@g.com'; + newUser.email = 'example2@example.com'; const response = await request(app).post('/adduser').send(newUser); expect(response.status).toBe(400); @@ -60,3 +62,52 @@ describe('User Service', () => { }); }); + +describe('User service validations', () => { + it('shows error message on wrong formed email', async () => { + newUser.email = "test" + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Wrong email format (example@example.com)'); + }); + + it('shows error message on not equal passwords', async () => { + newUser.repeatPassword = newUser.repeatPassword + "n"; + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Passwords dont match'); + }); + + it('shows error message on password have spaces', async () => { + setPassword("1234 56789") + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Password cannot have spaces'); + }); + + it('shows error message on password length is less than 8', async () => { + setPassword("12") + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Password must be at least 8 characters long'); + }); + + it('shows error message on password length is more than 64', async () => { + setPassword("01234567890123456789012345678901234567890123456789012345678901234") + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Password must less than 64 characters long'); + }); + + it('shows error message on username has spaces', async () => { + newUser.username = newUser.username + " yes" + const response = await request(app).post('/adduser').send(newUser); + expect(response.status).toBe(400); + expect(response.body).toHaveProperty('error', 'Username cannot have spaces'); + }); +}); + +function setPassword(newPassword){ + newUser.password = newPassword; + newUser.repeatPassword = newPassword; +} \ No newline at end of file diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index 103fbe44..bccffdbc 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -66,7 +66,7 @@ const AddUser = () => { if (newSubmitErrors.length === 0) { try { - const response = await axios.post(apiUrl, { email, username, password }); + const response = await axios.post(apiUrl, { email, username, password, repeatPassword }); let oneHourAfter = new Date().getTime() + (1 * 60 * 60 * 1000) Cookies.set('user', JSON.stringify({username : response.data.username, token : response.data.token}) , {expires:oneHourAfter}); From 1b0592585c09cd4923ae1c489b2034b5bba1d1ef Mon Sep 17 00:00:00 2001 From: Mister-Mario Date: Mon, 22 Apr 2024 19:55:03 +0200 Subject: [PATCH 73/74] Fixed addUser test failing cause the new endpoint --- gatewayservice/gateway-service.js | 28 ++++++++++++------- users/userservice/user-service.js | 1 + .../loginAndRegistration/AddUser.js | 9 +++--- .../loginAndRegistration/AddUser.test.js | 2 +- webapp/src/utils/manageError.js | 17 +++++++++++ 5 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 webapp/src/utils/manageError.js diff --git a/gatewayservice/gateway-service.js b/gatewayservice/gateway-service.js index 298dbc6d..c08d0e37 100644 --- a/gatewayservice/gateway-service.js +++ b/gatewayservice/gateway-service.js @@ -33,7 +33,7 @@ app.post('/login', async (req, res) => { const authResponse = await axios.post(authServiceUrl+'/login', req.body); res.json(authResponse.data); } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error) } }); @@ -43,7 +43,8 @@ app.post('/adduser', async (req, res) => { const userResponse = await axios.post(userServiceUrl+'/adduser', req.body); res.json(userResponse.data); } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error); + } }); @@ -54,7 +55,7 @@ app.get('/questions', verifyToken, async (req, res) => { const questionResponse = await axios.get(questionServiceUrl+'/questions'); res.json(questionResponse.data); } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error) } }); @@ -77,7 +78,7 @@ app.get('/questions/:lang/:amount/:type', verifyToken, async (req, res) => { } } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error) } }); @@ -96,7 +97,7 @@ app.get('/questions/:lang/:amount', verifyToken, async (req, res) => { res.json(questionResponse.data); } } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error) } }); @@ -114,7 +115,7 @@ app.get('/questions/:lang', verifyToken, async (req, res) => { } catch (error) { - res.status(error.response.status).json({ error: error.response.data.error }); + manageError(error) } }); @@ -125,7 +126,7 @@ app.post('/record', verifyToken, async(req, res) => { const recordResponse = await axios.post(recordServiceUrl+'/record', req.body); res.json(recordResponse.data); } catch (error) { - res.send(error); + manageError(error) } }); @@ -135,7 +136,7 @@ app.get('/record/ranking/top10', verifyToken, async(req, res)=>{ const recordResponse = await axios.get(recordServiceUrl + '/record/ranking/top10'); res.json(recordResponse.data); } catch (error) { - res.send(error); + manageError(error) } }); @@ -150,7 +151,7 @@ app.get('/record/ranking/:user', verifyToken, async(req, res)=>{ res.json(recordResponse.data); } } catch (error) { - res.send(error); + manageError(error) } }); @@ -165,7 +166,7 @@ app.get('/record/:user', verifyToken, async(req, res)=>{ res.json(recordResponse.data); } } catch (error) { - res.send(error); + manageError(error) } }); @@ -222,4 +223,11 @@ function validateUser(user){ return !(/\s/.test(user)) //True if there are no spaces } +function manageError(error){ + if(error.response) //Some microservice responded with an error + res.status(error.response.status).json({ error: error.response.data.error }); + else //Some other error + res.status(500).json({error : "Interanl server error"}) +} + module.exports = server diff --git a/users/userservice/user-service.js b/users/userservice/user-service.js index f57a0aa5..a6ea8a09 100644 --- a/users/userservice/user-service.js +++ b/users/userservice/user-service.js @@ -74,6 +74,7 @@ app.post('/adduser', async (req, res) => { } catch(error){ res.status(400).json({ error : error.message }); + console.log(res) return } diff --git a/webapp/src/components/loginAndRegistration/AddUser.js b/webapp/src/components/loginAndRegistration/AddUser.js index bccffdbc..66683cc6 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.js +++ b/webapp/src/components/loginAndRegistration/AddUser.js @@ -8,6 +8,8 @@ import { useNavigate } from 'react-router-dom'; import zxcvbn from "zxcvbn"; import Cookies from 'js-cookie'; +import { manageError } from "../../utils/manageError"; + const AddUser = () => { const navigate = useNavigate(); const apiUrl = (process.env.REACT_APP_API_ENDPOINT || 'http://localhost:8000') + "/adduser"; @@ -73,10 +75,9 @@ const AddUser = () => { navigate('/menu'); window.location.reload(); } catch (error) { - if (error.response.data.error === "Username already in use") { - setSubmitErrors(["addUser.error_username_in_use"]); - } - console.error("Error adding user:", error); + let processedError = manageError(error); + if(processedError.status !== 500) + setSubmitErrors(['addUser.error_username_in_use']); } } }; diff --git a/webapp/src/components/loginAndRegistration/AddUser.test.js b/webapp/src/components/loginAndRegistration/AddUser.test.js index cd36c68c..f95b52ef 100644 --- a/webapp/src/components/loginAndRegistration/AddUser.test.js +++ b/webapp/src/components/loginAndRegistration/AddUser.test.js @@ -84,7 +84,7 @@ describe('', () => { await waitFor(() => { expect(screen.getByText('addUser.error_username_in_use')).toBeInTheDocument(); }); - expect(axios.post).toHaveBeenCalledWith(expect.any(String), { email: 'user@example.com' ,username: 'existing_user', password: '12345678' }); + expect(axios.post).toHaveBeenCalledWith(expect.any(String), { email: 'user@example.com' ,username: 'existing_user', password: '12345678', repeatPassword: "12345678" }); }); }); diff --git a/webapp/src/utils/manageError.js b/webapp/src/utils/manageError.js new file mode 100644 index 00000000..ea56f89c --- /dev/null +++ b/webapp/src/utils/manageError.js @@ -0,0 +1,17 @@ +/** + * Receives an error found in a catch after doing some axios petition in the try + * returns {status: "4xx", error: "error message"} + * or {status: "500", error: "Internal server error"} + * @param {*} error + */ + +function manageError(error){ + if(error.response) + return {status : error.response.status, error : error.response.data.error}; + else //Some other error + return {status : 500, error : "Interanl server error"}; +} + +module.exports = { + manageError +}; \ No newline at end of file From 28d2fabf73bd350a309cd0325dac9bef2bc3a162 Mon Sep 17 00:00:00 2001 From: uo289267 Date: Tue, 23 Apr 2024 21:46:57 +0200 Subject: [PATCH 74/74] Fixed Bug --- .../questionView/CreationHistoricalRecord.js | 5 ++++- webapp/src/components/questionView/QuestionView.js | 1 + webapp/src/components/questionView/test.html | 11 ----------- 3 files changed, 5 insertions(+), 12 deletions(-) delete mode 100644 webapp/src/components/questionView/test.html diff --git a/webapp/src/components/questionView/CreationHistoricalRecord.js b/webapp/src/components/questionView/CreationHistoricalRecord.js index b10f0690..cac292c9 100644 --- a/webapp/src/components/questionView/CreationHistoricalRecord.js +++ b/webapp/src/components/questionView/CreationHistoricalRecord.js @@ -3,6 +3,9 @@ import axios from 'axios' class CreationHistoricalRecord{ constructor() { + this.initRecord(); + } + initRecord(){ this.record = { game: { questions: [] @@ -54,7 +57,7 @@ class CreationHistoricalRecord{ } }); - + this.initRecord(); console.log('Registro enviado:', response.data); } catch (error) { console.error('Error al enviar el registro:', error.message); diff --git a/webapp/src/components/questionView/QuestionView.js b/webapp/src/components/questionView/QuestionView.js index 7b757576..b84cc18b 100644 --- a/webapp/src/components/questionView/QuestionView.js +++ b/webapp/src/components/questionView/QuestionView.js @@ -28,6 +28,7 @@ function QuestionView({type= "COMPETITIVE", amount=5}){ var generatedQuestions = await questionGenerator.generateQuestions(i18n.language, type, amount, cookie.token); setQuestions(generatedQuestions); + points=0; setnumQuestion(0); } catch (error) { //Como hacer que funcione esto diff --git a/webapp/src/components/questionView/test.html b/webapp/src/components/questionView/test.html deleted file mode 100644 index b1ee2c8e..00000000 --- a/webapp/src/components/questionView/test.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - -

Hola

- - - \ No newline at end of file