diff --git a/backend/src/controllers/students.ts b/backend/src/controllers/students.ts index 2c200c9f..c825e8bb 100644 --- a/backend/src/controllers/students.ts +++ b/backend/src/controllers/students.ts @@ -116,7 +116,7 @@ export async function getStudentQuestionsHandler(req: Request, res: Response): P export async function createStudentRequestHandler(req: Request, res: Response): Promise { const username = req.params.username; - const classId = req.params.classId; + const classId = req.body.classId; requireFields({ username, classId }); await createClassJoinRequest(username, classId); diff --git a/backend/src/controllers/teachers.ts b/backend/src/controllers/teachers.ts index e3571d63..9c738817 100644 --- a/backend/src/controllers/teachers.ts +++ b/backend/src/controllers/teachers.ts @@ -91,11 +91,11 @@ export async function getStudentJoinRequestHandler(req: Request, res: Response) } export async function updateStudentJoinRequestHandler(req: Request, res: Response) { - const username = req.query.username as string; + const studentUsername = req.query.studentUsername as string; const classId = req.params.classId; - const accepted = req.query.accepted !== 'false'; // default = true - requireFields({ username, classId }); + const accepted = req.body.accepted !== 'false'; // default = true + requireFields({ studentUsername, classId }); - await updateClassJoinRequestStatus(username, classId, accepted); + await updateClassJoinRequestStatus(studentUsername, classId, accepted); res.status(200); } diff --git a/backend/src/routes/student-join-requests.ts b/backend/src/routes/student-join-requests.ts index 7147fd95..73532347 100644 --- a/backend/src/routes/student-join-requests.ts +++ b/backend/src/routes/student-join-requests.ts @@ -8,7 +8,7 @@ const router = express.Router({ mergeParams: true }); router.get('/', getStudentRequestHandler); -router.post('/:classId', createStudentRequestHandler); +router.post('/', createStudentRequestHandler); router.delete('/:classId', deleteClassJoinRequestHandler); diff --git a/backend/tests/controllers/student.test.ts b/backend/tests/controllers/students.test.ts similarity index 90% rename from backend/tests/controllers/student.test.ts rename to backend/tests/controllers/students.test.ts index e360460c..2d0aecbc 100644 --- a/backend/tests/controllers/student.test.ts +++ b/backend/tests/controllers/students.test.ts @@ -40,10 +40,18 @@ describe('Student controllers', () => { }; }); + it('Get student', async () => { + req = { params: { username: 'DireStraits' }}; + + await getStudentHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ student: expect.anything() })); + }); + it('Student not found', async () => { req = { params: { username: 'doesnotexist' } }; - await expect(() => deleteStudentHandler(req as Request, res as Response)) + await expect(() => getStudentHandler(req as Request, res as Response)) .rejects .toThrow(NotFoundException); }); @@ -56,10 +64,10 @@ describe('Student controllers', () => { .toThrowError(BadRequestException); }); - it('Create student', async () => { + it('Create and delete student', async () => { req = { body: { - username: 'NewstudentId21', + username: 'coolstudent', firstName: 'New', lastName: 'Student' } @@ -68,7 +76,12 @@ describe('Student controllers', () => { await createStudentHandler(req as Request, res as Response); expect(statusMock).toHaveBeenCalledWith(201); - expect(jsonMock).toHaveBeenCalled(); + + req = { params: { username: 'coolstudent' } }; + + await deleteStudentHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(200); }); @@ -108,7 +121,7 @@ describe('Student controllers', () => { expect(studentUsernames).toContain('DireStraits'); // check length, +1 because of create - expect(result.students).toHaveLength(TEST_STUDENTS.length + 1); + expect(result.students).toHaveLength(TEST_STUDENTS.length); }); it('Student classes', async () => { @@ -135,7 +148,7 @@ describe('Student controllers', () => { }); it('Student submissions', async () => { - req = { params: { username: 'DireStraits' } }; + req = { params: { username: 'DireStraits' }, query: { full: 'true' } }; await getStudentSubmissionsHandler(req as Request, res as Response); @@ -156,15 +169,6 @@ describe('Student controllers', () => { expect(result.questions).to.have.length.greaterThan(0); }); - it('Delete student', async () => { - req = { params: { username: 'coolstudent' } }; - - await deleteStudentHandler(req as Request, res as Response); - - expect(statusMock).toHaveBeenCalledWith(200); - expect(jsonMock).toHaveBeenCalled(); - }); - it('Deleting non-existent student', async () => { req = { params: { username: 'doesnotexist' } }; @@ -194,7 +198,8 @@ describe('Student controllers', () => { it('Create join request', async () => { req = { - params: { username: 'Noordkaap', classId: 'id02' }, + params: { username: 'Noordkaap' }, + body: { classId: 'id02' } }; await createStudentRequestHandler(req as Request, res as Response); @@ -204,7 +209,8 @@ describe('Student controllers', () => { it('Create join request duplicate', async () => { req = { - params: { username: 'Tool', classId: 'id02' }, + params: { username: 'Tool' }, + body: { classId: 'id02' } }; await expect(() => createStudentRequestHandler(req as Request, res as Response)) diff --git a/backend/tests/controllers/teachers.test.ts b/backend/tests/controllers/teachers.test.ts new file mode 100644 index 00000000..7b8668bb --- /dev/null +++ b/backend/tests/controllers/teachers.test.ts @@ -0,0 +1,210 @@ +import {beforeAll, beforeEach, describe, expect, it, Mock, vi} from "vitest"; +import {Request, Response} from "express"; +import {setupTestApp} from "../setup-tests"; +import {NotFoundException} from "../../src/exceptions/not-found-exception"; +import { + createTeacherHandler, + deleteTeacherHandler, + getAllTeachersHandler, getStudentJoinRequestHandler, getTeacherClassHandler, + getTeacherHandler, getTeacherQuestionHandler, getTeacherStudentHandler, updateStudentJoinRequestHandler +} from "../../src/controllers/teachers"; +import {BadRequestException} from "../../src/exceptions/bad-request-exception"; +import {EntityAlreadyExistsException} from "../../src/exceptions/entity-already-exists-exception"; +import {getStudentRequestHandler} from "../../src/controllers/students"; + +describe('Teacher controllers', () => { + let req: Partial; + let res: Partial; + + let jsonMock: Mock; + let statusMock: Mock; + + beforeAll(async () => { + await setupTestApp(); + }); + + beforeEach(() => { + jsonMock = vi.fn(); + statusMock = vi.fn().mockReturnThis(); + res = { + json: jsonMock, + status: statusMock, + }; + }); + + it('Get teacher', async () => { + req = { params: { username: 'FooFighters' }}; + + await getTeacherHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teacher: expect.anything() })); + }); + + it('Teacher not found', async () => { + req = {params: {username: 'doesnotexist'}}; + + await expect(() => getTeacherHandler(req as Request, res as Response)) + .rejects + .toThrow(NotFoundException); + }); + + it('No username', async () => { + req = { params: {} }; + + await expect(() => getTeacherHandler(req as Request, res as Response)) + .rejects + .toThrowError(BadRequestException); + }); + + it('Create and delete teacher', async () => { + req = { + body: { + username: 'coolteacher', + firstName: 'New', + lastName: 'Teacher' + } + }; + + await createTeacherHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(201); + + req = { params: { username: 'coolteacher' } }; + + await deleteTeacherHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(200); + }); + + it('Create duplicate student', async () => { + req = { + body: { + username: 'FooFighters', + firstName: 'Dave', + lastName: 'Grohl', + } + }; + + await expect(() => createTeacherHandler(req as Request, res as Response)) + .rejects + .toThrowError(EntityAlreadyExistsException); + }); + + it('Create teacher no body', async () => { + req = { body: {} }; + + await expect(() => createTeacherHandler(req as Request, res as Response)) + .rejects + .toThrowError(BadRequestException); + }); + + it('Teacher list', async () => { + req = { query: { full: 'true' } }; + + await getAllTeachersHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ teachers: expect.anything() })); + + const result = jsonMock.mock.lastCall?.[0]; + + const teacherUsernames = result.teachers.map((s: any) => s.username); + expect(teacherUsernames).toContain('FooFighters'); + + expect(result.teachers).toHaveLength(4); + }); + + it('Deleting non-existent student', async () => { + req = { params: { username: 'doesnotexist' } }; + + await expect(() => deleteTeacherHandler(req as Request, res as Response)) + .rejects + .toThrow(NotFoundException); + }); + + + it('Get teacher classes', async () => { + req = { + params: { username: 'FooFighters' }, + query: { full: 'true' }, + }; + + await getTeacherClassHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.anything()); + + const result = jsonMock.mock.lastCall?.[0]; + // console.log('[TEACHER CLASSES]', result); + expect(result.length).toBeGreaterThan(0); + }); + + it('Get teacher students', async () => { + req = { + params: { username: 'FooFighters' }, + query: { full: 'true' }, + }; + + await getTeacherStudentHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ students: expect.anything() })); + + const result = jsonMock.mock.lastCall?.[0]; + // console.log('[TEACHER STUDENTS]', result.students); + expect(result.students.length).toBeGreaterThan(0); + }); + + it('Get teacher questions', async () => { + req = { + params: { username: 'FooFighters' }, + query: { full: 'true' }, + }; + + await getTeacherQuestionHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ questions: expect.anything() })); + + const result = jsonMock.mock.lastCall?.[0]; + // console.log('[TEACHER QUESTIONS]', result.questions); + expect(result.questions.length).toBeGreaterThan(0); + }); + + it('Get join requests by class', async () => { + req = { + query: { username: 'LimpBizkit' }, + params: { classId: 'id02' }, + }; + + await getStudentJoinRequestHandler(req as Request, res as Response); + + expect(jsonMock).toHaveBeenCalledWith(expect.objectContaining({ joinRequests: expect.anything() })); + + const result = jsonMock.mock.lastCall?.[0]; + // console.log('[JOIN REQUESTS FOR CLASS]', result.joinRequests); + expect(result.joinRequests.length).toBeGreaterThan(0); + }); + + it('Update join request status', async () => { + req = { + query: { username: 'LimpBizkit', studentUsername: 'PinkFloyd' }, + params: { classId: 'id02' }, + body: { accepted: 'true' } + }; + + await updateStudentJoinRequestHandler(req as Request, res as Response); + + expect(statusMock).toHaveBeenCalledWith(200); + + req = { + params: { username: 'PinkFloyd' }, + }; + + await getStudentRequestHandler(req as Request, res as Response); + + const result = jsonMock.mock.lastCall?.[0]; + const status = result.requests[0].status + expect(status).toBeTruthy; + }); + + + + +}); diff --git a/backend/tests/services/students.test.ts b/backend/tests/services/students.test.ts deleted file mode 100644 index cf6870a3..00000000 --- a/backend/tests/services/students.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -import {describe, it, expect, vi, beforeEach, beforeAll} from 'vitest'; -import {Student} from "../../src/entities/users/student.entity"; -import {StudentDTO} from "../../src/interfaces/student"; -import {setupTestApp} from "../setup-tests"; -import {createStudent, deleteStudent, getAllStudents, getStudent} from "../../src/services/students"; - -const mockStudentRepo = { - findAll: vi.fn(), - findByUsername: vi.fn(), - create: vi.fn(), - save: vi.fn(), - deleteByUsername: vi.fn(), -}; - -describe('StudentService', () => { - - beforeAll(async () => { - await setupTestApp(); - }); - - beforeEach(() => { - vi.clearAllMocks(); - vi.mock('../../src/data/repositories', () => ({ - getStudentRepository: () => mockStudentRepo, - })); - }); - - it('Student list full', async () => { - mockStudentRepo.findAll.mockResolvedValue([ - new Student('DireStraits', 'Mark', 'Knopfler'), - ]); - - const result = await getAllStudents(true); - - expect(mockStudentRepo.findAll).toHaveBeenCalled(); - expect(result[0]).toHaveProperty('username', 'DireStraits'); - }); - - it('Student list ids', async () => { - mockStudentRepo.findAll.mockResolvedValue([ - new Student('Tool', 'Maynard', 'Keenan'), - ]); - - const result = await getAllStudents(false); - - expect(mockStudentRepo.findAll).toHaveBeenCalled(); - expect(result).toContain('Tool'); - expect(typeof result[0]).toBe('string'); - }); - - it('Student not found', async () => { - mockStudentRepo.findByUsername.mockResolvedValue(null); - - const result = await getStudent('doesnotexist'); - expect(result).toBeNull(); - }); - - it('Create + get student', async () => { - const dto: StudentDTO = { - username: 'SmashingPumpkins', - firstName: 'Billy', - lastName: 'Corgan', - }; - - const studentEntity = new Student(dto.username, dto.firstName, dto.lastName); - - mockStudentRepo.create.mockReturnValue(studentEntity); - mockStudentRepo.save.mockResolvedValue(undefined); - mockStudentRepo.findByUsername.mockResolvedValue(studentEntity); - - const created = await createStudent(dto); - const found = await getStudent(dto.username); - - expect(created).not.toBeNull(); - expect(found).not.toBeNull(); - expect(found?.username).toBe(dto.username); - }); - - it('Delete non existing student', async () => { - mockStudentRepo.findByUsername.mockResolvedValue(null); - - const result = await deleteStudent('ghost'); - expect(result).toBeNull(); - }); - - it('Delete student', async () => { - const student = new Student('TheDoors', 'Jim', 'Morisson'); - mockStudentRepo.findByUsername.mockResolvedValue(student); - mockStudentRepo.deleteByUsername.mockResolvedValue(undefined); - - const result = await deleteStudent('TheDoors'); - - expect(mockStudentRepo.deleteByUsername).toHaveBeenCalledWith('TheDoors'); - expect(result?.username).toBe('TheDoors'); - }); -});