From 3093a6c1319fdfedb5f332d0ea4c834de2ffd001 Mon Sep 17 00:00:00 2001 From: Gabriellvl Date: Sat, 29 Mar 2025 12:21:48 +0100 Subject: [PATCH] feat: student send join req --- backend/src/controllers/error-helper.ts | 18 +++ backend/src/controllers/students.ts | 127 ++++++++-------- backend/src/controllers/teachers.ts | 31 ++-- backend/src/controllers/users.ts | 7 - backend/src/exceptions.ts | 14 ++ backend/src/interfaces/student-request.ts | 16 ++ backend/src/interfaces/student.ts | 6 +- backend/src/routes/student-join-requests.ts | 18 +++ backend/src/routes/students.ts | 5 +- backend/src/services/students.ts | 156 ++++++++++++++------ backend/src/services/teachers.ts | 17 ++- backend/tests/controllers/student.test.ts | 101 ++++++++++--- 12 files changed, 347 insertions(+), 169 deletions(-) create mode 100644 backend/src/controllers/error-helper.ts delete mode 100644 backend/src/controllers/users.ts create mode 100644 backend/src/interfaces/student-request.ts create mode 100644 backend/src/routes/student-join-requests.ts diff --git a/backend/src/controllers/error-helper.ts b/backend/src/controllers/error-helper.ts new file mode 100644 index 00000000..0bc3a9d4 --- /dev/null +++ b/backend/src/controllers/error-helper.ts @@ -0,0 +1,18 @@ +import { BadRequestException } from '../exceptions.js'; + +/** + * Checks for the presence of required fields and throws a BadRequestException + * if any are missing. + * + * @param fields - An object with key-value pairs to validate. + */ +export function requireFields(fields: Record): void { + const missing = Object.entries(fields) + .filter(([_, value]) => value === undefined || value === null || value === '') + .map(([key]) => key); + + if (missing.length > 0) { + const message = `Missing required field${missing.length > 1 ? 's' : ''}: ${missing.join(', ')}`; + throw new BadRequestException(message); + } +} diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index d1f424de..9a60ac7b 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -1,17 +1,19 @@ import { Request, Response } from 'express'; import { - createStudent, + createClassJoinRequest, + createStudent, deleteClassJoinRequest, deleteStudent, - getAllStudents, + getAllStudents, getJoinRequestsByStudent, getStudent, getStudentAssignments, getStudentClasses, getStudentGroups, getStudentQuestions, - getStudentSubmissions, + getStudentSubmissions, updateClassJoinRequestStatus, } from '../services/students.js'; -import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users.js'; import { StudentDTO } from '../interfaces/student.js'; +import {BadRequestException} from "../exceptions"; +import {requireFields} from "./error-helper"; export async function getAllStudentsHandler(req: Request, res: Response): Promise { @@ -19,69 +21,42 @@ export async function getAllStudentsHandler(req: Request, res: Response): Promis const students: StudentDTO[] | string[] = await getAllStudents(full); - if (!students) { - res.status(404).json({ error: `Students not found.` }); - return; - } - res.json({ students }); } export async function getStudentHandler(req: Request, res: Response): Promise { const username = req.params.username; + requireFields({ username }); - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + const student = await getStudent(username); - const user = await getStudent(username); - - if (!user) { - res.status(404).json(NAME_NOT_FOUND_ERROR(username)); - return; - } - - res.status(201).json(user); + res.status(201).json({ student }); } export async function createStudentHandler(req: Request, res: Response) { + const username = req.body.username; + const firstName = req.body.firstName; + const lastName = req.body.lastName; + requireFields({ username, firstName, lastName }); + const userData = req.body as StudentDTO; - if (!userData.username || !userData.firstName || !userData.lastName) { - res.status(400).json(MISSING_FIELDS_ERROR); - return; - } - - const newUser = await createStudent(userData); - res.status(201).json(newUser); + const student = await createStudent(userData); + res.status(201).json({ student }); } export async function deleteStudentHandler(req: Request, res: Response) { const username = req.params.username; + requireFields({ username }); - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } - - const deletedUser = await deleteStudent(username); - if (!deletedUser) { - res.status(404).json(NAME_NOT_FOUND_ERROR(username)); - return; - } - - res.status(200).json(deletedUser); + const student = await deleteStudent(username); + res.status(200).json({ student }); } export async function getStudentClassesHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const classes = await getStudentClasses(username, full); @@ -97,11 +72,7 @@ export async function getStudentClassesHandler(req: Request, res: Response): Pro export async function getStudentAssignmentsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const assignments = getStudentAssignments(username, full); @@ -113,11 +84,7 @@ export async function getStudentAssignmentsHandler(req: Request, res: Response): export async function getStudentGroupsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const groups = await getStudentGroups(username, full); @@ -128,11 +95,7 @@ export async function getStudentGroupsHandler(req: Request, res: Response): Prom export async function getStudentSubmissionsHandler(req: Request, res: Response): Promise { const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const submissions = await getStudentSubmissions(username); @@ -144,11 +107,7 @@ export async function getStudentSubmissionsHandler(req: Request, res: Response): export async function getStudentQuestionsHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const questions = await getStudentQuestions(username, full); @@ -156,3 +115,41 @@ export async function getStudentQuestionsHandler(req: Request, res: Response): P questions, }); } + +export async function createStudentRequestHandler(req: Request, res: Response): Promise { + const username = req.params.username; + const classId = req.params.classId; + requireFields({ username, classId }); + + await createClassJoinRequest(username, classId); + res.status(201).send(); +} + +export async function getStudentRequestHandler(req: Request, res: Response): Promise { + const username = req.params.username; + requireFields({ username }); + + const requests = await getJoinRequestsByStudent(username); + res.status(201).json({ requests }) +} + +export async function updateClassJoinRequestHandler(req: Request, res: Response) { + const username = req.query.username as string; + const classId = req.params.classId; + const accepted = req.query.accepted !== 'false'; // default = true + requireFields({ username, classId }); + + const result = await updateClassJoinRequestStatus(username, classId, accepted); + res.status(200).json(result); +} + +export async function deleteClassJoinRequestHandler(req: Request, res: Response) { + const username = req.query.username as string; + const classId = req.params.classId; + requireFields({ username, classId }); + + await deleteClassJoinRequest(username, classId); + res.status(204).send(); +} + + diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index f1a557b0..dd45ef02 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -1,10 +1,18 @@ import { Request, Response } from 'express'; -import { createTeacher, deleteTeacher, getAllTeachers, getClassesByTeacher, getStudentsByTeacher, getTeacher } from '../services/teachers.js'; +import { + createTeacher, + deleteTeacher, + getAllTeachers, + getClassesByTeacher, + getStudentsByTeacher, + getTeacher, + getTeacherQuestions +} from '../services/teachers.js'; import { ClassDTO } from '../interfaces/class.js'; import { StudentDTO } from '../interfaces/student.js'; import { QuestionDTO, QuestionId } from '../interfaces/question.js'; import { TeacherDTO } from '../interfaces/teacher.js'; -import { MISSING_FIELDS_ERROR, MISSING_USERNAME_ERROR, NAME_NOT_FOUND_ERROR } from './users'; +import {requireFields} from "./error-helper"; export async function getAllTeachersHandler(req: Request, res: Response): Promise { const full = req.query.full === 'true'; @@ -21,18 +29,11 @@ export async function getAllTeachersHandler(req: Request, res: Response): Promis export async function getTeacherHandler(req: Request, res: Response): Promise { const username = req.params.username; - - if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); - return; - } + requireFields({ username }); const user = await getTeacher(username); - if (!user) { - res.status(404).json(NAME_NOT_FOUND_ERROR(username)); - return; - } + res.status(201).json(user); } @@ -41,7 +42,6 @@ export async function createTeacherHandler(req: Request, res: Response) { const userData = req.body as TeacherDTO; if (!userData.username || !userData.firstName || !userData.lastName) { - res.status(400).json(MISSING_FIELDS_ERROR); return; } @@ -53,13 +53,11 @@ export async function deleteTeacherHandler(req: Request, res: Response) { const username = req.params.username; if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); return; } const deletedUser = await deleteTeacher(username); if (!deletedUser) { - res.status(404).json(NAME_NOT_FOUND_ERROR(username)); return; } @@ -71,7 +69,6 @@ export async function getTeacherClassHandler(req: Request, res: Response): Promi const full = req.query.full === 'true'; if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); return; } @@ -85,7 +82,6 @@ export async function getTeacherStudentHandler(req: Request, res: Response): Pro const full = req.query.full === 'true'; if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); return; } @@ -99,11 +95,10 @@ export async function getTeacherQuestionHandler(req: Request, res: Response): Pr const full = req.query.full === 'true'; if (!username) { - res.status(400).json(MISSING_USERNAME_ERROR); return; } - const questions: QuestionDTO[] | QuestionId[] = await getQuestionsByTeacher(username, full); + const questions: QuestionDTO[] | QuestionId[] = await getTeacherQuestions(username, full); res.json({ questions }); } diff --git a/backend/src/controllers/users.ts b/backend/src/controllers/users.ts deleted file mode 100644 index d2a6ce7c..00000000 --- a/backend/src/controllers/users.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const MISSING_USERNAME_ERROR = { error: 'Missing required field: username' }; - -export function NAME_NOT_FOUND_ERROR(username: string) { - return { error: `User with username '${username}' not found.` }; -} - -export const MISSING_FIELDS_ERROR = { error: 'Missing required fields: username, firstName, lastName' }; diff --git a/backend/src/exceptions.ts b/backend/src/exceptions.ts index e93a6c93..c9922d80 100644 --- a/backend/src/exceptions.ts +++ b/backend/src/exceptions.ts @@ -40,3 +40,17 @@ export class NotFoundException extends Error { super(error); } } + +export class ConflictException extends Error { + public status = 409; + constructor(message: string = 'Conflict') { + super(message); + } +} + +export class InternalServerError extends Error { + public status = 500; + constructor(message: string = 'Internal Server Error') { + super(message); + } +} diff --git a/backend/src/interfaces/student-request.ts b/backend/src/interfaces/student-request.ts new file mode 100644 index 00000000..9f496046 --- /dev/null +++ b/backend/src/interfaces/student-request.ts @@ -0,0 +1,16 @@ +import {mapToStudentDTO, StudentDTO} from "./student"; +import {ClassJoinRequest, ClassJoinRequestStatus} from "../entities/classes/class-join-request.entity"; + +export interface StudentRequestDTO { + requester: StudentDTO; + class: string; + status: ClassJoinRequestStatus +} + +export function mapToStudentRequestDTO(request: ClassJoinRequest): StudentRequestDTO { + return { + requester: mapToStudentDTO(request.requester), + class: request.class.classId!, + status: request.status, + }; +} diff --git a/backend/src/interfaces/student.ts b/backend/src/interfaces/student.ts index 82a06a1e..a0d425fe 100644 --- a/backend/src/interfaces/student.ts +++ b/backend/src/interfaces/student.ts @@ -1,4 +1,4 @@ -import { Student } from '../entities/users/student.entity.js'; +import {Student} from '../entities/users/student.entity.js'; export interface StudentDTO { id?: string; @@ -17,7 +17,5 @@ export function mapToStudentDTO(student: Student): StudentDTO { } export function mapToStudent(studentData: StudentDTO): Student { - const student = new Student(studentData.username, studentData.firstName, studentData.lastName); - - return student; + return new Student(studentData.username, studentData.firstName, studentData.lastName); } diff --git a/backend/src/routes/student-join-requests.ts b/backend/src/routes/student-join-requests.ts new file mode 100644 index 00000000..45d20f91 --- /dev/null +++ b/backend/src/routes/student-join-requests.ts @@ -0,0 +1,18 @@ +import express from "express"; +import { + createStudentRequestHandler, deleteClassJoinRequestHandler, + getStudentRequestHandler, + updateClassJoinRequestHandler +} from "../controllers/students"; + +const router = express.Router({ mergeParams: true }); + +router.get('/', getStudentRequestHandler); + +router.post('/:classId', createStudentRequestHandler); + +router.put('/:classId', updateClassJoinRequestHandler); + +router.delete('/:classId', deleteClassJoinRequestHandler); + +export default router; diff --git a/backend/src/routes/students.ts b/backend/src/routes/students.ts index 388f51a0..d443abd9 100644 --- a/backend/src/routes/students.ts +++ b/backend/src/routes/students.ts @@ -10,7 +10,8 @@ import { getStudentQuestionsHandler, getStudentSubmissionsHandler, } from '../controllers/students.js'; -import { getStudentGroups } from '../services/students.js'; +import joinRequestRouter from './student-join-requests.js' + const router = express.Router(); // Root endpoint used to search objects @@ -38,4 +39,6 @@ router.get('/:username/groups', getStudentGroupsHandler); // A list of questions a user has created router.get('/:username/questions', getStudentQuestionsHandler); +router.use('/:username/join-requests', joinRequestRouter) + export default router; diff --git a/backend/src/services/students.ts b/backend/src/services/students.ts index 3508b2d8..14816298 100644 --- a/backend/src/services/students.ts +++ b/backend/src/services/students.ts @@ -1,4 +1,5 @@ import { + getClassJoinRequestRepository, getClassRepository, getGroupRepository, getQuestionRepository, @@ -12,6 +13,10 @@ import { mapToStudent, mapToStudentDTO, StudentDTO } from '../interfaces/student import { mapToSubmissionDTO, SubmissionDTO } from '../interfaces/submission.js'; import { getAllAssignments } from './assignments.js'; import { mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId } from '../interfaces/question'; +import {ClassJoinRequestStatus} from "../entities/classes/class-join-request.entity"; +import {ConflictException, NotFoundException} from "../exceptions"; +import {Student} from "../entities/users/student.entity"; +import {mapToStudentRequestDTO} from "../interfaces/student-request"; export async function getAllStudents(full: boolean): Promise { const studentRepository = getStudentRepository(); @@ -23,24 +28,34 @@ export async function getAllStudents(full: boolean): Promise user.username); } -export async function getStudent(username: string): Promise { +export async function fetchStudent(username: string): Promise { const studentRepository = getStudentRepository(); const user = await studentRepository.findByUsername(username); - return user ? mapToStudentDTO(user) : null; + + if (!user) { + throw new NotFoundException("Student with username not found"); + } + + return user; +} + +export async function getStudent(username: string): Promise { + const user = await fetchStudent(username); + return mapToStudentDTO(user); } export async function createStudent(userData: StudentDTO): Promise { const studentRepository = getStudentRepository(); - try { - const newStudent = studentRepository.create(mapToStudent(userData)); - await studentRepository.save(newStudent); + const user = await studentRepository.findByUsername(userData.username); - return mapToStudentDTO(newStudent); - } catch (e) { - console.log(e); - return null; + if (user) { + throw new ConflictException("Username already exists"); } + + const newStudent = studentRepository.create(mapToStudent(userData)); + await studentRepository.save(newStudent); + return mapToStudentDTO(newStudent); } export async function deleteStudent(username: string): Promise { @@ -49,26 +64,15 @@ export async function deleteStudent(username: string): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return []; - } + const student = await fetchStudent(username); const classRepository = getClassRepository(); const classes = await classRepository.findByStudent(student); @@ -81,12 +85,7 @@ export async function getStudentClasses(username: string, full: boolean): Promis } export async function getStudentAssignments(username: string, full: boolean): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return []; - } + const student = await fetchStudent(username); const classRepository = getClassRepository(); const classes = await classRepository.findByStudent(student); @@ -95,12 +94,7 @@ export async function getStudentAssignments(username: string, full: boolean): Pr } export async function getStudentGroups(username: string, full: boolean): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return []; - } + const student = await fetchStudent(username); const groupRepository = getGroupRepository(); const groups = await groupRepository.findAllGroupsWithStudent(student); @@ -113,12 +107,7 @@ export async function getStudentGroups(username: string, full: boolean): Promise } export async function getStudentSubmissions(username: string): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return []; - } + const student = await fetchStudent(username); const submissionRepository = getSubmissionRepository(); const submissions = await submissionRepository.findAllSubmissionsForStudent(student); @@ -127,12 +116,7 @@ export async function getStudentSubmissions(username: string): Promise { - const studentRepository = getStudentRepository(); - const student = await studentRepository.findByUsername(username); - - if (!student) { - return []; - } + const student = await fetchStudent(username); const questionRepository = getQuestionRepository(); const questions = await questionRepository.findAllByAuthor(student); @@ -144,3 +128,79 @@ export async function getStudentQuestions(username: string, full: boolean): Prom return questionsDTO.map(mapToQuestionId); } + +export async function createClassJoinRequest(studentUsername: string, classId: string) { + const classRepo = getClassRepository(); + const requestRepo = getClassJoinRequestRepository(); + + const student = await fetchStudent(studentUsername); + const cls = await classRepo.findById(classId); + + if (!cls){ + throw new NotFoundException("Class with id not found"); + } + + const request = requestRepo.create({ + requester: student, + class: cls, + status: ClassJoinRequestStatus.Open, + }); + + await requestRepo.save(request); + return request; +} + +export async function getJoinRequestsByStudent(studentUsername: string) { + const requestRepo = getClassJoinRequestRepository(); + + const student = await fetchStudent(studentUsername); + + const requests = await requestRepo.findAllRequestsBy(student); + return requests.map(mapToStudentRequestDTO); +} + +export async function updateClassJoinRequestStatus( studentUsername: string, classId: string, accepted: boolean = true) { + const requestRepo = getClassJoinRequestRepository(); + const classRepo = getClassRepository(); + + const student = await fetchStudent(studentUsername); + const cls = await classRepo.findById(classId); + + if (!cls) { + throw new NotFoundException('Class not found'); + } + + const request = await requestRepo.findOne({ requester: student, class: cls }); + + if (!request) { + throw new NotFoundException('Join request not found'); + } + + request.status = accepted ? ClassJoinRequestStatus.Accepted : ClassJoinRequestStatus.Declined; + + await requestRepo.save(request); + return request; +} + +export async function deleteClassJoinRequest(studentUsername: string, classId: string) { + const requestRepo = getClassJoinRequestRepository(); + const classRepo = getClassRepository(); + + const student = await fetchStudent(studentUsername); + const cls = await classRepo.findById(classId); + + if (!cls) { + throw new NotFoundException('Class not found'); + } + + const request = await requestRepo.findOne({ requester: student, class: cls }); + + if (!request) { + throw new NotFoundException('Join request not found'); + } + + await requestRepo.deleteBy(student, cls); + return true; +} + + diff --git a/backend/src/services/teachers.ts b/backend/src/services/teachers.ts index cfe274c9..612cf6fb 100644 --- a/backend/src/services/teachers.ts +++ b/backend/src/services/teachers.ts @@ -2,15 +2,14 @@ import { getClassRepository, getLearningObjectRepository, getQuestionRepository, - getStudentRepository, getTeacherRepository, } from '../data/repositories.js'; import { ClassDTO, mapToClassDTO } from '../interfaces/class.js'; import { getClassStudents } from './class.js'; -import { mapToQuestionDTO, mapToQuestionId, QuestionDTO } from '../interfaces/question.js'; +import {mapToQuestionDTO, mapToQuestionId, QuestionDTO, QuestionId} from '../interfaces/question.js'; import { mapToTeacher, mapToTeacherDTO, TeacherDTO } from '../interfaces/teacher.js'; -export async function getAllTeachers(full: boolean): Promise { +export async function getAllTeachers(full: boolean): Promise { const teacherRepository = getTeacherRepository(); const users = await teacherRepository.findAll(); @@ -81,16 +80,17 @@ export async function getClassesByTeacher(username: string, full: boolean): Prom } export async function getStudentsByTeacher(username: string, full: boolean) { - const classes = await getClassesByTeacher(username, false); + const classes = await fetchClassesByTeacher(username); + const classIds = classes.map((cls) => cls.id); - const students = (await Promise.all(classes.map(async (id) => getClassStudents(id)))).flat(); + const students = (await Promise.all(classIds.map(async (id) => getClassStudents(id)))).flat(); if (full) { return students; } return students.map((student) => student.username); } -export async function getTeacherQuestions(username: string, full: boolean): Promise { +export async function getTeacherQuestions(username: string, full: boolean): Promise { const teacherRepository = getTeacherRepository(); const teacher = await teacherRepository.findByUsername(username); if (!teacher) { @@ -104,10 +104,11 @@ export async function getTeacherQuestions(username: string, full: boolean): Prom // Fetch all questions related to these learning objects const questionRepository = getQuestionRepository(); const questions = await questionRepository.findAllByLearningObjects(learningObjects); + const questionsDTO = questions.map(mapToQuestionDTO); if (full) { - return questions.map(mapToQuestionDTO); + return questionsDTO; } - return questions.map(mapToQuestionId); + return questionsDTO.map(mapToQuestionId); } diff --git a/backend/tests/controllers/student.test.ts b/backend/tests/controllers/student.test.ts index e9b85baa..89d146de 100644 --- a/backend/tests/controllers/student.test.ts +++ b/backend/tests/controllers/student.test.ts @@ -9,9 +9,14 @@ import { getStudentClassesHandler, getStudentGroupsHandler, getStudentSubmissionsHandler, - getStudentQuestionsHandler + getStudentQuestionsHandler, + createStudentRequestHandler, + getStudentRequestHandler, + updateClassJoinRequestHandler, + deleteClassJoinRequestHandler } from '../../src/controllers/students.js'; import {TEST_STUDENTS} from "../test_assets/users/students.testdata"; +import {BadRequestException, NotFoundException} from "../../src/exceptions"; describe('Student controllers', () => { let req: Partial; @@ -33,22 +38,20 @@ describe('Student controllers', () => { }; }); - it('Student not found 404', async () => { + it('Student not found', async () => { req = { params: { username: 'doesnotexist' } }; - await getStudentHandler(req as Request, res as Response); - - expect(statusMock).toHaveBeenCalledWith(404); - expect(jsonMock).toHaveBeenCalled(); + await expect(() => deleteStudentHandler(req as Request, res as Response)) + .rejects + .toThrow(NotFoundException); }); - it('No username 400', async () => { + it('No username', async () => { req = { params: {} }; - await getStudentHandler(req as Request, res as Response); - - expect(statusMock).toHaveBeenCalledWith(400); - expect(jsonMock).toHaveBeenCalled(); + await expect(() => getStudentHandler(req as Request, res as Response)) + .rejects + .toThrowError(BadRequestException); }); it('Create student', async () => { @@ -66,13 +69,14 @@ describe('Student controllers', () => { expect(jsonMock).toHaveBeenCalled(); }); + // TODO create duplicate student id + it('Create student no body 400', async () => { req = { body: {} }; - await createStudentHandler(req as Request, res as Response); - - expect(statusMock).toHaveBeenCalledWith(400); - expect(jsonMock).toHaveBeenCalled(); + await expect(() => createStudentHandler(req as Request, res as Response)) + .rejects + .toThrowError(BadRequestException); }); it('Student list', async () => { @@ -146,12 +150,73 @@ describe('Student controllers', () => { expect(jsonMock).toHaveBeenCalled(); }); - it('Deleting non-existent student 404', async () => { + it('Deleting non-existent student', async () => { req = { params: { username: 'doesnotexist' } }; - await deleteStudentHandler(req as Request, res as Response); + await expect(() => deleteStudentHandler(req as Request, res as Response)) + .rejects + .toThrow(NotFoundException); + }); - expect(statusMock).toHaveBeenCalledWith(404); + it('Get join requests by student', async () => { + req = { + params: { username: 'PinkFloyd' }, + }; + + await getStudentRequestHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(201); + expect(jsonMock).toHaveBeenCalledWith( + expect.objectContaining({ + requests: expect.anything(), + }) + ); + + const result = jsonMock.mock.lastCall?.[0]; + // console.log('[JOIN REQUESTS]', result.requests); + expect(result.requests.length).toBeGreaterThan(0); + }); + + it('Create join request', async () => { + req = { + params: { username: 'DireStraits', classId: '' }, + }; + + await createStudentRequestHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(201); expect(jsonMock).toHaveBeenCalled(); }); + + /* + + it('Update join request status (accept)', async () => { + req = { + params: { classId }, + query: { username }, + }; + + await updateClassJoinRequestHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(200); + expect(jsonMock).toHaveBeenCalled(); + const result = jsonMock.mock.lastCall?.[0]; + console.log('[UPDATED REQUEST]', result); + }); + + it('Delete join request', async () => { + req = { + params: { classId }, + query: { username }, + }; + + await deleteClassJoinRequestHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(204); + expect(sendMock).toHaveBeenCalled(); + }); + + */ + + });