diff --git a/client/package-lock.json b/client/package-lock.json index e2c6890..cd70638 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4371,7 +4371,6 @@ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", "dev": true, - "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", diff --git a/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx b/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx new file mode 100644 index 0000000..2a878a5 --- /dev/null +++ b/client/src/__tests__/components/ShareQuizModal/ShareQuizModal.test.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import ShareQuizModal from '../../../components/ShareQuizModal/ShareQuizModal'; +import { QuizType } from '../../../Types/QuizType'; +import ApiService from '../../../services/ApiService'; + +jest.mock('../../../services/ApiService'); + +Object.assign(navigator, { + clipboard: { + writeText: jest.fn().mockResolvedValue(undefined), + }, +}); + +window.alert = jest.fn(); + +const mockQuiz: QuizType = { + _id: '1', + title: 'Sample Quiz', + content: ['::Question 1:: What is 2+2? {=4 ~3 ~5}'], + folderId: 'folder1', + folderName: 'Sample Folder', + userId: 'user1', + created_at: new Date(), + updated_at: new Date(), +}; + +describe('ShareQuizModal', () => { + + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should call ApiService.ShareQuiz when sharing by email', async () => { + render(); + + const shareButton = screen.getByRole('button', { name: /partager quiz/i }); + fireEvent.click(shareButton); + + const emailButton = screen.getByRole('button', { name: /partager par email/i }); + fireEvent.click(emailButton); + + const email = 'test@example.com'; + window.prompt = jest.fn().mockReturnValue(email); + + await fireEvent.click(emailButton); + + expect(ApiService.ShareQuiz).toHaveBeenCalledWith(mockQuiz._id, email); + }); + + it('copies the correct URL to the clipboard when sharing by URL', async () => { + render(); + + // Open the modal + const shareButton = screen.getByRole('button', { name: /partager quiz/i }); + fireEvent.click(shareButton); + + // Click the "Share by URL" button + const shareByUrlButton = screen.getByRole('button', { name: /partager par url/i }); + fireEvent.click(shareByUrlButton); + + // Check if the correct URL was copied + const expectedUrl = `${window.location.origin}/teacher/share/${mockQuiz._id}`; + expect(navigator.clipboard.writeText).toHaveBeenCalledWith(expectedUrl); + + // Check if the alert is shown + await waitFor(() => { + expect(window.alert).toHaveBeenCalledWith('URL a été copiée avec succès.'); + }); + }); + +}); \ No newline at end of file diff --git a/client/src/__tests__/services/ShareQuizService.test.tsx b/client/src/__tests__/services/ShareQuizService.test.tsx new file mode 100644 index 0000000..c1e473e --- /dev/null +++ b/client/src/__tests__/services/ShareQuizService.test.tsx @@ -0,0 +1,83 @@ +import axios from 'axios'; +import ApiService from '../../services/ApiService'; +import { ENV_VARIABLES } from '../../constants'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; + +describe('ApiService', () => { + describe('shareQuiz', () => { + it('should call the API to share a quiz and return true on success', async () => { + const quizId = '123'; + const email = 'test@example.com'; + const response = { status: 200 }; + mockedAxios.put.mockResolvedValue(response); + + const result = await ApiService.ShareQuiz(quizId, email); + + expect(mockedAxios.put).toHaveBeenCalledWith( + `${ENV_VARIABLES.VITE_BACKEND_URL}/api/quiz/Share`, + { quizId, email }, + { headers: expect.any(Object) } + ); + expect(result).toBe(true); + }); + + it('should return an error message if the API call fails', async () => { + const quizId = '123'; + const email = 'test@example.com'; + const errorMessage = 'An unexpected error occurred.'; + mockedAxios.put.mockRejectedValue({ response: { data: { error: errorMessage } } }); + + const result = await ApiService.ShareQuiz(quizId, email); + + expect(result).toBe(errorMessage); + }); + + it('should return a generic error message if an unexpected error occurs', async () => { + const quizId = '123'; + const email = 'test@example.com'; + mockedAxios.put.mockRejectedValue(new Error('Unexpected error')); + + const result = await ApiService.ShareQuiz(quizId, email); + + expect(result).toBe('An unexpected error occurred.'); + }); + }); + + describe('getSharedQuiz', () => { + it('should call the API to get a shared quiz and return the quiz data on success', async () => { + const quizId = '123'; + const quizData = 'Quiz data'; + const response = { status: 200, data: { data: quizData } }; + mockedAxios.get.mockResolvedValue(response); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(mockedAxios.get).toHaveBeenCalledWith( + `${ENV_VARIABLES.VITE_BACKEND_URL}/api/quiz/getShare/${quizId}`, + { headers: expect.any(Object) } + ); + expect(result).toBe(quizData); + }); + + it('should return an error message if the API call fails', async () => { + const quizId = '123'; + const errorMessage = 'An unexpected error occurred.'; + mockedAxios.get.mockRejectedValue({ response: { data: { error: errorMessage } } }); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(result).toBe(errorMessage); + }); + + it('should return a generic error message if an unexpected error occurs', async () => { + const quizId = '123'; + mockedAxios.get.mockRejectedValue(new Error('Unexpected error')); + + const result = await ApiService.getSharedQuiz(quizId); + + expect(result).toBe('An unexpected error occurred.'); + }); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/services/getAllQuizIdsService.test.tsx b/client/src/__tests__/services/getAllQuizIdsService.test.tsx new file mode 100644 index 0000000..222e7db --- /dev/null +++ b/client/src/__tests__/services/getAllQuizIdsService.test.tsx @@ -0,0 +1,42 @@ +import axios from 'axios'; +import ApiService from '../../services/ApiService'; +import { FolderType } from '../../Types/FolderType'; +import { QuizType } from '../../Types/QuizType'; + +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; + +describe('ApiService', () => { + describe('getAllQuizIds', () => { + it('should return all quiz IDs from all folders', async () => { + const folders: FolderType[] = [ + { _id: 'folder1', title: 'Folder 1', userId: 'user1', created_at: new Date().toISOString() }, + { _id: 'folder2', title: 'Folder 2', userId: 'user2', created_at: new Date().toISOString() } + ]; + const quizzesFolder1: QuizType[] = [ + { _id: 'quiz1', title: 'Quiz 1', content: [], folderId: 'folder1', folderName: 'Folder 1', userId: 'user1', created_at: new Date(), updated_at: new Date() }, + { _id: 'quiz2', title: 'Quiz 2', content: [], folderId: 'folder1', folderName: 'Folder 1', userId: 'user1', created_at: new Date(), updated_at: new Date() } + ]; + const quizzesFolder2: QuizType[] = [ + { _id: 'quiz3', title: 'Quiz 3', content: [], folderId: 'folder2', folderName: 'Folder 2', userId: 'user2', created_at: new Date(), updated_at: new Date() } + ]; + + mockedAxios.get + .mockResolvedValueOnce({ status: 200, data: { data: folders } }) + .mockResolvedValueOnce({ status: 200, data: { data: quizzesFolder1 } }) + .mockResolvedValueOnce({ status: 200, data: { data: quizzesFolder2 } }); + + const result = await ApiService.getAllQuizIds(); + + expect(result).toEqual(['quiz1', 'quiz2', 'quiz3']); + }); + + it('should return an empty array if no folders are found', async () => { + mockedAxios.get.mockResolvedValueOnce({ status: 200, data: { data: [] } }); + + const result = await ApiService.getAllQuizIds(); + + expect(result).toEqual([]); + }); + }); +}); \ No newline at end of file diff --git a/client/src/components/ShareQuizModal/ShareQuizModal.tsx b/client/src/components/ShareQuizModal/ShareQuizModal.tsx new file mode 100644 index 0000000..336aadb --- /dev/null +++ b/client/src/components/ShareQuizModal/ShareQuizModal.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import { Dialog, DialogTitle, DialogActions, Button, Tooltip, IconButton } from '@mui/material'; +import { Share } from '@mui/icons-material'; +import { QuizType } from '../../Types/QuizType'; +import ApiService from '../../services/ApiService'; + +interface ShareQuizModalProps { + quiz: QuizType; +} + +const ShareQuizModal: React.FC = ({ quiz }) => { + const [open, setOpen] = useState(false); + + const handleOpenModal = () => setOpen(true); + + const handleCloseModal = () => setOpen(false); + + const handleShareByEmail = async () => { + const email = prompt(`Veuillez saisir l'email de la personne avec qui vous souhaitez partager ce quiz`, ""); + + if (email) { + try { + const result = await ApiService.ShareQuiz(quiz._id, email); + + if (!result) { + window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`); + return; + } + + window.alert(`Quiz partagé avec succès!`); + } catch (error) { + console.error('Erreur lors du partage du quiz:', error); + } + } + + handleCloseModal(); + }; + + const handleShareByUrl = () => { + const quizUrl = `${window.location.origin}/teacher/share/${quiz._id}`; + navigator.clipboard.writeText(quizUrl) + .then(() => { + window.alert('URL a été copiée avec succès.'); + }) + .catch(() => { + window.alert('Une erreur est survenue lors de la copie de l\'URL.'); + }); + + handleCloseModal(); + }; + + return ( + <> + + + + + + + + Choisissez une méthode de partage + + + + + + + ); +}; + +export default ShareQuizModal; \ No newline at end of file diff --git a/client/src/pages/Teacher/Dashboard/Dashboard.tsx b/client/src/pages/Teacher/Dashboard/Dashboard.tsx index cdae619..d319515 100644 --- a/client/src/pages/Teacher/Dashboard/Dashboard.tsx +++ b/client/src/pages/Teacher/Dashboard/Dashboard.tsx @@ -39,9 +39,8 @@ import { FolderCopy, ContentCopy, Edit, - Share - // DriveFileMove } from '@mui/icons-material'; +import ShareQuizModal from 'src/components/ShareQuizModal/ShareQuizModal'; // Create a custom-styled Card component const CustomCard = styled(Card)({ @@ -401,28 +400,6 @@ const Dashboard: React.FC = () => { } }; - const handleShareQuiz = async (quiz: QuizType) => { - try { - const email = prompt( - `Veuillez saisir l'email de la personne avec qui vous souhaitez partager ce quiz`, - '' - ); - - if (email) { - const result = await ApiService.ShareQuiz(quiz._id, email); - - if (!result) { - window.alert(`Une erreur est survenue.\n Veuillez réessayer plus tard`); - return; - } - - window.alert(`Quiz partagé avec succès!`); - } - } catch (error) { - console.error('Erreur lors du partage du quiz:', error); - } - }; - return (
Tableau de bord
@@ -644,15 +621,9 @@ const Dashboard: React.FC = () => { - - handleShareQuiz(quiz)} - > - {' '} - {' '} - - +
+ +
))} diff --git a/client/src/pages/Teacher/Share/Share.tsx b/client/src/pages/Teacher/Share/Share.tsx index 0dc4fe7..613d290 100644 --- a/client/src/pages/Teacher/Share/Share.tsx +++ b/client/src/pages/Teacher/Share/Share.tsx @@ -1,18 +1,12 @@ -// EditorQuiz.tsx import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; - import { FolderType } from '../../../Types/FolderType'; - - import './share.css'; import { Button, NativeSelect } from '@mui/material'; import ReturnButton from 'src/components/ReturnButton/ReturnButton'; - import ApiService from '../../../services/ApiService'; const Share: React.FC = () => { - console.log('Component rendered'); const navigate = useNavigate(); const { id } = useParams(); @@ -23,7 +17,6 @@ const Share: React.FC = () => { useEffect(() => { const fetchData = async () => { - console.log("QUIZID : " + id) if (!id) { window.alert(`Une erreur est survenue.\n Le quiz n'a pas été trouvé\nVeuillez réessayer plus tard`) console.error('Quiz not found for id:', id); @@ -36,9 +29,17 @@ const Share: React.FC = () => { navigate("/login"); return; } + + const quizIds = await ApiService.getAllQuizIds(); + + if (quizIds.includes(id)) { + window.alert(`Le quiz que vous essayez d'importer existe déjà sur votre compte.`) + navigate('/teacher/dashboard'); + return; + } const userFolders = await ApiService.getUserFolders(); - + if (userFolders.length == 0) { window.alert(`Vous n'avez aucun dossier.\nVeuillez en créer un et revenir à ce lien`) navigate('/teacher/dashboard'); @@ -91,42 +92,39 @@ const Share: React.FC = () => { }; return ( -
- -
- - -
Importer quiz: {quizTitle}
- -
+
+
+ +
+
Importation du Quiz: {quizTitle}
+
+ Vous êtes sur le point d'importer le quiz {quizTitle}, choisissez un dossier dans lequel enregistrer ce nouveau quiz.
- -
- -
- - - - - {folders.map((folder: FolderType) => ( - - ))} - - - - -
- -
-
+
+
+ +
+
+ + + {folders.map((folder: FolderType) => ( + + ))} + + + +
+
+
); }; diff --git a/client/src/pages/Teacher/Share/share.css b/client/src/pages/Teacher/Share/share.css index 119b645..146f318 100644 --- a/client/src/pages/Teacher/Share/share.css +++ b/client/src/pages/Teacher/Share/share.css @@ -3,19 +3,58 @@ display: flex; flex-direction: row; justify-content: space-between; - align-content: stretch + align-content: stretch; } + .quizImport .importHeader .returnButton { flex-basis: 10%; - display: flex; justify-content: center; } -.quizImport .importHeader .title { +.quizImport .importHeader .titleContainer { flex-basis: auto; + text-align: center; /* Center the title and subtitle */ +} + +.quizImport .importHeader .mainTitle { + font-size: 44px; /* Larger font size for the main title */ + font-weight: bold; /* Remove bold */ + color: #333; /* Slightly paler color */ +} + +.quizImport .importHeader .subTitle { + font-size: 14px; /* Smaller font size for the subtitle */ + color: #666; /* Pale gray color */ + margin-top: 8px; /* Add some space between the title and subtitle */ } .quizImport .importHeader .dumb { flex-basis: 10%; +} + +.quizImport .editSection { + display: flex; + justify-content: center; + align-items: center; + margin-top: 20px; +} + +.quizImport .editSection .formContainer { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; /* Adds space between the select and button */ + width: 100%; + max-width: 400px; /* Limits the width of the form container */ +} + +.quizImport .editSection .folderSelect { + width: 100%; /* Ensures the select element takes the full width of its container */ +} + +.quizImport .editSection .saveButton { + width: 100%; /* Makes the button take the full width of its container */ + padding: 10px 20px; /* Increases the button's padding for a larger appearance */ + font-size: 16px; /* Increases the font size for better visibility */ } \ No newline at end of file diff --git a/client/src/services/ApiService.tsx b/client/src/services/ApiService.tsx index b13a369..fa2b4e7 100644 --- a/client/src/services/ApiService.tsx +++ b/client/src/services/ApiService.tsx @@ -74,8 +74,6 @@ class ApiService { return false; } - console.log("ApiService: isLoggedIn: Token:", token); - // Update token expiry this.saveToken(token); @@ -91,7 +89,6 @@ class ApiService { } try { - console.log("ApiService: isLoggedInTeacher: Token:", token); const decodedToken = jwtDecode(token) as { roles: string[] }; /////// REMOVE BELOW @@ -103,7 +100,6 @@ class ApiService { const userRoles = decodedToken.roles; const requiredRole = 'teacher'; - console.log("ApiService: isLoggedInTeacher: UserRoles:", userRoles); if (!userRoles || !userRoles.includes(requiredRole)) { return false; } @@ -178,7 +174,6 @@ class ApiService { const result: AxiosResponse = await axios.post(url, body, { headers: headers }); - console.log(result); if (result.status == 200) { //window.location.href = result.request.responseURL; window.location.href = '/login'; @@ -190,7 +185,6 @@ class ApiService { return true; } catch (error) { - console.log("Error details: ", error); if (axios.isAxiosError(error)) { const err = error as AxiosError; @@ -553,7 +547,6 @@ public async login(email: string, password: string): Promise { const headers = this.constructRequestHeaders(); const body = { folderId }; - console.log(headers); const result: AxiosResponse = await axios.post(url, body, { headers: headers }); if (result.status !== 200) { @@ -1169,6 +1162,33 @@ public async login(email: string, password: string): Promise { } // NOTE : Get Image pas necessaire + public async getAllQuizIds(): Promise { + try { + const folders = await this.getUserFolders(); + + const allQuizIds: string[] = []; + + if (Array.isArray(folders)) { + for (const folder of folders) { + const folderQuizzes = await this.getFolderContent(folder._id); + + if (Array.isArray(folderQuizzes)) { + allQuizIds.push(...folderQuizzes.map(quiz => quiz._id)); + } + } + } else { + console.error('Failed to get user folders:', folders); + } + + return allQuizIds; + } catch (error) { + console.error('Failed to get all quiz ids:', error); + throw error; + } + } + + + } const apiService = new ApiService(); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..144d2f8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,329 @@ +{ + "name": "EvalueTonSavoir", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "axios-mock-adapter": "^2.1.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "peer": true + }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "peer": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios-mock-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/axios-mock-adapter/-/axios-mock-adapter-2.1.0.tgz", + "integrity": "sha512-AZUe4OjECGCNNssH8SOdtneiQELsqTsat3SQQCWLPjN436/H+L9AjWfV7bF+Zg/YL9cgbhrz5671hoh+Tbn98w==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "is-buffer": "^2.0.5" + }, + "peerDependencies": { + "axios": ">= 0.17.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "peer": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "peer": true, + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "peer": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a8332a4 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "axios-mock-adapter": "^2.1.0" + } +} diff --git a/server/package-lock.json b/server/package-lock.json index 0f006f8..715837e 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -38,7 +38,7 @@ "supertest": "^6.3.4" }, "engines": { - "node": "20.x" + "node": "22.x" } }, "node_modules/@ampproject/remapping": { @@ -55,68 +55,20 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.13", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@babel/compat-data": { "version": "7.23.3", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", @@ -342,19 +294,21 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -369,88 +323,28 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", + "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/template": "^7.26.9", + "@babel/types": "^7.26.10" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", - "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz", + "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==", "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.10" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -636,14 +530,15 @@ } }, "node_modules/@babel/template": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", - "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "version": "7.26.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", + "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/parser": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.26.9", + "@babel/types": "^7.26.9" }, "engines": { "node": ">=6.9.0" @@ -704,14 +599,14 @@ "dev": true }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", + "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -4772,7 +4667,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -6683,15 +6579,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/server/package.json b/server/package.json index cd0348b..3f0cfd4 100644 --- a/server/package.json +++ b/server/package.json @@ -42,7 +42,7 @@ "supertest": "^6.3.4" }, "engines": { - "node": "20.x" + "node": "22.x" }, "jest": { "testEnvironment": "node",