diff --git a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx index 0332f1a..a0b99f8 100644 --- a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx @@ -10,7 +10,8 @@ import { MemoryRouter } from 'react-router-dom'; // import { mock } from 'node:test'; const mockGiftQuestions = parse( - `::Sample Question:: Sample Question {=Option A ~Option B}`); + `::Sample Question:: Sample Question {=Option A ~Option B} + ::Sample Question 2:: Sample Question 2 {T}`); describe('TeacherModeQuiz', () => { @@ -56,6 +57,7 @@ describe('TeacherModeQuiz', () => { expect(screen.getByText('Votre réponse est:')).toBeInTheDocument(); }); + test('handles disconnect button click', () => { act(() => { fireEvent.click(screen.getByText('Quitter')); diff --git a/server/__tests__/answers.test.js b/server/__tests__/answers.test.js new file mode 100644 index 0000000..27330f7 --- /dev/null +++ b/server/__tests__/answers.test.js @@ -0,0 +1,73 @@ +const request = require('supertest'); +const express = require('express'); +const bodyParser = require('body-parser'); +const answersRouter = require('../routers/answers.js'); +const Answer = require('../models/answer'); + +const app = express(); +const cors = require("cors"); +app.use(cors()); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(bodyParser.json()); +app.use('/api', answersRouter); + +describe('Answers API', () => { + beforeEach(() => { + // Reset the in-memory database before each test + while (Answer.getAll().length > 0) { + Answer.delete(Answer.getAll()[0].id); + } + }); + + test('should create a new answer', async () => { + const response = await request(app) + .post('/api/answers') + .send({ answerText: 'This is an answer', showFeedback: true, points: 10,goodAnswer: true}); + + expect(response.status).toBe(200); + expect(response.body.message).toBe('Answer created successfully.'); + expect(response.body.answer).toHaveProperty('id'); + expect(response.body.answer.answerText).toBe('This is an answer'); + expect(response.body.answer.showFeedback).toBe(true); + expect(response.body.answer.points).toBe(10); + expect(response.body.answer.goodAnswer).toBe(true); + + }); + + test('should get an answer by ID', async () => { + const answer = Answer.create('This is an answer', true, 10, true); + const response = await request(app).get(`/api/answers/${answer.id}`); + + expect(response.status).toBe(200); + expect(response.body.answerText).toBe('This is an answer'); + expect(response.body.showFeedback).toBe(true); + expect(response.body.points).toBe(10); + expect(response.body.goodAnswer).toBe(true); + + }); + + test('should return 404 if answer not found', async () => { + const response = await request(app).get('/api/answers/999'); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Answer not found'); + }); + + test('should delete an answer by ID', async () => { + const answer = Answer.create('This is an answer', true, 10); + const response = await request(app).delete(`/api/answers/${answer.id}`); + + expect(response.status).toBe(200); + expect(response.body.message).toBe('Answer deleted successfully.'); + + const getResponse = await request(app).get(`/api/answers/${answer.id}`); + expect(getResponse.status).toBe(404); + }); + + test('should return 404 if deleting an answer that does not exist', async () => { + const response = await request(app).delete('/api/answers/999'); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Answer not found'); + }); +}); \ No newline at end of file diff --git a/server/__tests__/students.test.js b/server/__tests__/students.test.js new file mode 100644 index 0000000..b5ea259 --- /dev/null +++ b/server/__tests__/students.test.js @@ -0,0 +1,64 @@ +const request = require('supertest'); +const express = require('express'); +const bodyParser = require('body-parser'); +const studentRoutes = require('../routers/students'); +const Student = require('../models/student'); + +const app = express(); +app.use(bodyParser.json()); +app.use('/api', studentRoutes); + +describe('Students API', () => { + beforeEach(() => { + // Reset the in-memory database before each test + while (Student.getAll().length > 0) { + Student.delete(Student.getAll()[0].id); + } + }); + + test('should create a new student', async () => { + const response = await request(app) + .post('/api/students') + .send({ name: 'John Doe', answers: ['Answer 1', 'Answer 2'] }); + + expect(response.status).toBe(200); + expect(response.body.message).toBe('Student created successfully.'); + expect(response.body.student).toHaveProperty('id'); + expect(response.body.student.name).toBe('John Doe'); + expect(response.body.student.answers).toEqual(['Answer 1', 'Answer 2']); + }); + + test('should get a student by ID', async () => { + const student = Student.create('Jane Doe', ['Answer 1', 'Answer 2']); + const response = await request(app).get(`/api/students/${student.id}`); + + expect(response.status).toBe(200); + expect(response.body.name).toBe('Jane Doe'); + expect(response.body.answers).toEqual(['Answer 1', 'Answer 2']); + }); + + test('should return 404 if student not found', async () => { + const response = await request(app).get('/api/students/999'); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Student not found'); + }); + + test('should delete a student by ID', async () => { + const student = Student.create('John Doe', ['Answer 1', 'Answer 2']); + const response = await request(app).delete(`/api/students/${student.id}`); + + expect(response.status).toBe(200); + expect(response.body.message).toBe('Student deleted successfully.'); + + const getResponse = await request(app).get(`/api/students/${student.id}`); + expect(getResponse.status).toBe(404); + }); + + test('should return 404 if deleting a student that does not exist', async () => { + const response = await request(app).delete('/api/students/999'); + + expect(response.status).toBe(404); + expect(response.body.message).toBe('Student not found'); + }); +}); \ No newline at end of file diff --git a/server/app.js b/server/app.js index f65fd66..297dbe6 100644 --- a/server/app.js +++ b/server/app.js @@ -18,6 +18,10 @@ const users = require('./models/users.js'); const userModel = new users(db, foldersModel); const images = require('./models/images.js'); const imageModel = new images(db); +const students = require('./models/students.js'); +const studentModel = new students(); +const answers = require('./models/answers.js'); +const answersModel = new answers(); // instantiate the controllers const usersController = require('./controllers/users.js'); @@ -28,18 +32,26 @@ const questionnaireController = require('./controllers/questionnaires.js'); const questionnaireControllerInstance = new questionnaireController(questionnaireModel, foldersModel); const imagesController = require('./controllers/images.js'); const imagesControllerInstance = new imagesController(imageModel); +const studentsController = require('./controllers/students.js'); +const studentsControllerInstance = new studentsController(studentModel); +const answersController = require('./controllers/answers.js'); +const answersControllerInstance = new answersController(answersModel); // export the controllers module.exports.users = usersControllerInstance; module.exports.folders = foldersControllerInstance; module.exports.questionnaires = questionnaireControllerInstance; module.exports.images = imagesControllerInstance; +module.exports.students = studentsControllerInstance; +module.exports.answers = answersControllerInstance; //import routers (instantiate controllers as side effect) const userRouter = require('./routers/users.js'); const folderRouter = require('./routers/folders.js'); const questionnaireRouter = require('./routers/questionnaires.js'); const imagesRouter = require('./routers/images.js'); +const studentsRouter = require('./routers/students.js'); +const answersRouter = require('./routers/answers.js'); // Setup environment dotenv.config(); @@ -84,6 +96,8 @@ app.use('/api/user', userRouter); app.use('/api/folder', folderRouter); app.use('/api/questionnaire', questionnaireRouter); app.use('/api/image', imagesRouter); +app.use('/api/students', studentsRouter); +app.use('/api/answers', answersRouter); app.use(errorHandler); diff --git a/server/controllers/answers.js b/server/controllers/answers.js new file mode 100644 index 0000000..3ed932c --- /dev/null +++ b/server/controllers/answers.js @@ -0,0 +1,74 @@ +const AppError = require('../middleware/AppError.js'); +const { MISSING_REQUIRED_PARAMETER, ANSWER_NOT_FOUND } = require('../constants/errorCodes.js'); + +class AnswersController { + constructor(answersModel) { + this.answers = answersModel; + } + + create = async (req, res, next) => { + try { + const { answerText, showFeedback, points, goodAnswer } = req.body; + + if (!answerText || typeof showFeedback !== 'boolean' || typeof points !== 'number'|| typeof goodAnswer !== 'boolean') { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const result = this.answers.create(answerText, showFeedback, points, goodAnswer); + + return res.status(200).json({ + message: 'Answer created successfully.', + answer: result + }); + + } catch (error) { + return next(error); + } + }; + + get = async (req, res, next) => { + try { + const { answerId } = req.params; + + if (!answerId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const answer = this.answers.get(parseInt(answerId)); + + if (!answer) { + throw new AppError(ANSWER_NOT_FOUND); + } + + return res.status(200).json(answer); + + } catch (error) { + return next(error); + } + }; + + delete = async (req, res, next) => { + try { + const { answerId } = req.params; + + if (!answerId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const result = this.answers.delete(parseInt(answerId)); + + if (!result) { + throw new AppError(ANSWER_NOT_FOUND); + } + + return res.status(200).json({ + message: 'Answer deleted successfully.' + }); + + } catch (error) { + return next(error); + } + }; +} + +module.exports = AnswersController; \ No newline at end of file diff --git a/server/controllers/questionnaires.js b/server/controllers/questionnaires.js index 3473f4e..33330cb 100644 --- a/server/controllers/questionnaires.js +++ b/server/controllers/questionnaires.js @@ -3,7 +3,7 @@ const emailer = require('../config/email.js'); const AppError = require('../middleware/AppError.js'); const { MISSING_REQUIRED_PARAMETER, NOT_IMPLEMENTED, QUESTIONNAIRE_NOT_FOUND, FOLDER_NOT_FOUND, QUESTIONNAIRE_ALREADY_EXISTS, GETTING_QUESTIONNAIRE_ERROR, DELETE_QUESTIONNAIRE_ERROR, UPDATE_QUESTIONNAIRE_ERROR, MOVING_QUESTIONNAIRE_ERROR } = require('../constants/errorCodes.js'); -class QuestionnaireController { +class QuestionnairesController { constructor(questionnaireModel, foldersModel) { this.questionnaires = questionnaireModel; @@ -314,4 +314,4 @@ class QuestionnaireController { } -module.exports = QuestionnaireController; +module.exports = QuestionnairesController; diff --git a/server/controllers/students.js b/server/controllers/students.js new file mode 100644 index 0000000..b169a28 --- /dev/null +++ b/server/controllers/students.js @@ -0,0 +1,82 @@ +const AppError = require('../middleware/AppError.js'); +const { MISSING_REQUIRED_PARAMETER, STUDENT_NOT_FOUND, STUDENT_ALREADY_EXISTS } = require('../constants/errorCodes.js'); + +class StudentsController { + constructor(studentModel) { + this.students = studentModel; + } + + create = async (req, res, next) => { + try { + const { name, answers } = req.body; + + if (!name || !Array.isArray(answers)) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const existingStudent = await this.students.findByName(name); + if (existingStudent) { + throw new AppError(STUDENT_ALREADY_EXISTS); + } + + const result = await this.students.create(name, answers); + + if (!result) { + throw new AppError(STUDENT_ALREADY_EXISTS); + } + + return res.status(200).json({ + message: 'Student created successfully.' + }); + + } catch (error) { + return next(error); + } + }; + + get = async (req, res, next) => { + try { + const { studentId } = req.params; + + if (!studentId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const student = await this.students.get(studentId); + + if (!student) { + throw new AppError(STUDENT_NOT_FOUND); + } + + return res.status(200).json(student); + + } catch (error) { + return next(error); + } + }; + + delete = async (req, res, next) => { + try { + const { studentId } = req.params; + + if (!studentId) { + throw new AppError(MISSING_REQUIRED_PARAMETER); + } + + const result = await this.students.delete(studentId); + + if (!result) { + throw new AppError(STUDENT_NOT_FOUND); + } + + return res.status(200).json({ + message: 'Student deleted successfully.' + }); + + } catch (error) { + return next(error); + } + }; +} + +module.exports = StudentsController; \ No newline at end of file diff --git a/server/models/answers.js b/server/models/answers.js new file mode 100644 index 0000000..540c1e0 --- /dev/null +++ b/server/models/answers.js @@ -0,0 +1,39 @@ +let answers = []; // Ceci agira comme notre base de données en mémoire + +class Answer { + constructor(answerText, showFeedback, points, goodAnswer) { + this.id = Answer.generateId(); + this.answerText = answerText; + this.showFeedback = showFeedback; + this.points = points; + this.goodAnswer = goodAnswer; + } + + static generateId() { + return answers.length ? answers[answers.length - 1].id + 1 : 1; + } + + static create(answerText, showFeedback, points, goodAnswer) { + const answer = new Answer(answerText, showFeedback, points, goodAnswer); + answers.push(answer); + return answer; + } + + static get(id) { + return answers.find(answer => answer.id === id); + } + + static getAll() { + return answers; + } + + static delete(id) { + const index = answers.findIndex(answer => answer.id === id); + if (index !== -1) { + return answers.splice(index, 1)[0]; + } + return null; + } +} + +module.exports = Answer; \ No newline at end of file diff --git a/server/models/students.js b/server/models/students.js new file mode 100644 index 0000000..9b99781 --- /dev/null +++ b/server/models/students.js @@ -0,0 +1,41 @@ +let students = []; // Ceci agira comme notre base de données en mémoire + +class Student { + constructor(name, answers = []) { + this.id = Student.generateId(); + this.name = name; + this.answers = answers; + } + + static generateId() { + return students.length ? students[students.length - 1].id + 1 : 1; + } + + static create(name, answers = []) { + const student = new Student(name, answers); + students.push(student); + return student; + } + + static findByName(name) { + return students.find(student => student.name === name); + } + + static get(id) { + return students.find(student => student.id === id); + } + + static getAll() { + return students; + } + + static delete(id) { + const index = students.findIndex(student => student.id === id); + if (index !== -1) { + return students.splice(index, 1)[0]; + } + return null; + } +} + +module.exports = Student; \ No newline at end of file diff --git a/server/routers/answers.js b/server/routers/answers.js new file mode 100644 index 0000000..48bab23 --- /dev/null +++ b/server/routers/answers.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +const answers = require('../app.js').answers; +const jwt = require('../middleware/jwtToken.js'); +const asyncHandler = require('./routerUtils.js'); + +router.post('/create',jwt.authenticate, asyncHandler(answers.create)); +router.get('/get/:answerId',jwt.authenticate, asyncHandler(answers.get)); +router.delete('/delete/:answerId',jwt.authenticate, asyncHandler(answers.delete)); + +module.exports = router; \ No newline at end of file diff --git a/server/routers/students.js b/server/routers/students.js new file mode 100644 index 0000000..4ecba92 --- /dev/null +++ b/server/routers/students.js @@ -0,0 +1,11 @@ +const express = require('express'); +const router = express.Router(); +const students = require('../app.js').students; +const jwt = require('../middleware/jwtToken.js'); +const asyncHandler = require('./routerUtils.js'); + +router.post('/create',jwt.authenticate, asyncHandler(students.create)); +router.get('/get/:studentId', jwt.authenticate,asyncHandler(students.get)); +router.delete('/delete/:studentId',jwt.authenticate, asyncHandler(students.delete)); + +module.exports = router; \ No newline at end of file