feat: class join req controller + fixes tests
This commit is contained in:
parent
3093a6c131
commit
f679a324ab
11 changed files with 116 additions and 41 deletions
|
@ -122,7 +122,7 @@ export async function createStudentRequestHandler(req: Request, res: Response):
|
||||||
requireFields({ username, classId });
|
requireFields({ username, classId });
|
||||||
|
|
||||||
await createClassJoinRequest(username, classId);
|
await createClassJoinRequest(username, classId);
|
||||||
res.status(201).send();
|
res.status(201);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> {
|
export async function getStudentRequestHandler(req: Request, res: Response): Promise<void> {
|
||||||
|
@ -144,12 +144,12 @@ export async function updateClassJoinRequestHandler(req: Request, res: Response)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteClassJoinRequestHandler(req: Request, res: Response) {
|
export async function deleteClassJoinRequestHandler(req: Request, res: Response) {
|
||||||
const username = req.query.username as string;
|
const username = req.params.username as string;
|
||||||
const classId = req.params.classId;
|
const classId = req.params.classId;
|
||||||
requireFields({ username, classId });
|
requireFields({ username, classId });
|
||||||
|
|
||||||
await deleteClassJoinRequest(username, classId);
|
await deleteClassJoinRequest(username, classId);
|
||||||
res.status(204).send();
|
res.status(204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,9 @@ export class ClassJoinRequestRepository extends DwengoEntityRepository<ClassJoin
|
||||||
public findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> {
|
public findAllOpenRequestsTo(clazz: Class): Promise<ClassJoinRequest[]> {
|
||||||
return this.findAll({ where: { class: clazz } });
|
return this.findAll({ where: { class: clazz } });
|
||||||
}
|
}
|
||||||
|
public findByStudentAndClass(requester: Student, clazz: Class): Promise<ClassJoinRequest | null> {
|
||||||
|
return this.findOne({ requester, class: clazz });
|
||||||
|
}
|
||||||
public deleteBy(requester: Student, clazz: Class): Promise<void> {
|
public deleteBy(requester: Student, clazz: Class): Promise<void> {
|
||||||
return this.deleteWhere({ requester: requester, class: clazz });
|
return this.deleteWhere({ requester: requester, class: clazz });
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import express from "express";
|
||||||
import {
|
import {
|
||||||
createStudentRequestHandler, deleteClassJoinRequestHandler,
|
createStudentRequestHandler, deleteClassJoinRequestHandler,
|
||||||
getStudentRequestHandler,
|
getStudentRequestHandler,
|
||||||
updateClassJoinRequestHandler
|
|
||||||
} from "../controllers/students";
|
} from "../controllers/students";
|
||||||
|
|
||||||
const router = express.Router({ mergeParams: true });
|
const router = express.Router({ mergeParams: true });
|
||||||
|
@ -11,8 +10,6 @@ router.get('/', getStudentRequestHandler);
|
||||||
|
|
||||||
router.post('/:classId', createStudentRequestHandler);
|
router.post('/:classId', createStudentRequestHandler);
|
||||||
|
|
||||||
router.put('/:classId', updateClassJoinRequestHandler);
|
|
||||||
|
|
||||||
router.delete('/:classId', deleteClassJoinRequestHandler);
|
router.delete('/:classId', deleteClassJoinRequestHandler);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -39,6 +39,6 @@ router.get('/:username/groups', getStudentGroupsHandler);
|
||||||
// A list of questions a user has created
|
// A list of questions a user has created
|
||||||
router.get('/:username/questions', getStudentQuestionsHandler);
|
router.get('/:username/questions', getStudentQuestionsHandler);
|
||||||
|
|
||||||
router.use('/:username/join-requests', joinRequestRouter)
|
router.use('/:username/joinRequests', joinRequestRouter)
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -140,6 +140,12 @@ export async function createClassJoinRequest(studentUsername: string, classId: s
|
||||||
throw new NotFoundException("Class with id not found");
|
throw new NotFoundException("Class with id not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const req = await requestRepo.findByStudentAndClass(student, cls);
|
||||||
|
|
||||||
|
if (req){
|
||||||
|
throw new ConflictException("Request with student and class already exist");
|
||||||
|
}
|
||||||
|
|
||||||
const request = requestRepo.create({
|
const request = requestRepo.create({
|
||||||
requester: student,
|
requester: student,
|
||||||
class: cls,
|
class: cls,
|
||||||
|
@ -159,6 +165,7 @@ export async function getJoinRequestsByStudent(studentUsername: string) {
|
||||||
return requests.map(mapToStudentRequestDTO);
|
return requests.map(mapToStudentRequestDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO naar teacher
|
||||||
export async function updateClassJoinRequestStatus( studentUsername: string, classId: string, accepted: boolean = true) {
|
export async function updateClassJoinRequestStatus( studentUsername: string, classId: string, accepted: boolean = true) {
|
||||||
const requestRepo = getClassJoinRequestRepository();
|
const requestRepo = getClassJoinRequestRepository();
|
||||||
const classRepo = getClassRepository();
|
const classRepo = getClassRepository();
|
||||||
|
@ -193,7 +200,7 @@ export async function deleteClassJoinRequest(studentUsername: string, classId: s
|
||||||
throw new NotFoundException('Class not found');
|
throw new NotFoundException('Class not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const request = await requestRepo.findOne({ requester: student, class: cls });
|
const request = await requestRepo.findByStudentAndClass(student, cls);
|
||||||
|
|
||||||
if (!request) {
|
if (!request) {
|
||||||
throw new NotFoundException('Join request not found');
|
throw new NotFoundException('Join request not found');
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
deleteClassJoinRequestHandler
|
deleteClassJoinRequestHandler
|
||||||
} from '../../src/controllers/students.js';
|
} from '../../src/controllers/students.js';
|
||||||
import {TEST_STUDENTS} from "../test_assets/users/students.testdata";
|
import {TEST_STUDENTS} from "../test_assets/users/students.testdata";
|
||||||
import {BadRequestException, NotFoundException} from "../../src/exceptions";
|
import {BadRequestException, ConflictException, NotFoundException} from "../../src/exceptions";
|
||||||
|
|
||||||
describe('Student controllers', () => {
|
describe('Student controllers', () => {
|
||||||
let req: Partial<Request>;
|
let req: Partial<Request>;
|
||||||
|
@ -71,7 +71,21 @@ describe('Student controllers', () => {
|
||||||
|
|
||||||
// TODO create duplicate student id
|
// TODO create duplicate student id
|
||||||
|
|
||||||
it('Create student no body 400', async () => {
|
it('Create duplicate student', async () => {
|
||||||
|
req = {
|
||||||
|
body: {
|
||||||
|
username: 'DireStraits',
|
||||||
|
firstName: 'dupe',
|
||||||
|
lastName: 'dupe'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(() => createStudentHandler(req as Request, res as Response))
|
||||||
|
.rejects
|
||||||
|
.toThrowError(ConflictException);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Create student no body', async () => {
|
||||||
req = { body: {} };
|
req = { body: {} };
|
||||||
|
|
||||||
await expect(() => createStudentHandler(req as Request, res as Response))
|
await expect(() => createStudentHandler(req as Request, res as Response))
|
||||||
|
@ -179,44 +193,37 @@ describe('Student controllers', () => {
|
||||||
|
|
||||||
it('Create join request', async () => {
|
it('Create join request', async () => {
|
||||||
req = {
|
req = {
|
||||||
params: { username: 'DireStraits', classId: '' },
|
params: { username: 'Noordkaap', classId: 'id02' },
|
||||||
};
|
};
|
||||||
|
|
||||||
await createStudentRequestHandler(req as Request, res as Response);
|
await createStudentRequestHandler(req as Request, res as Response);
|
||||||
|
|
||||||
expect(statusMock).toHaveBeenCalledWith(201);
|
expect(statusMock).toHaveBeenCalledWith(201);
|
||||||
expect(jsonMock).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
it('Create join request duplicate', async () => {
|
||||||
|
|
||||||
it('Update join request status (accept)', async () => {
|
|
||||||
req = {
|
req = {
|
||||||
params: { classId },
|
params: { username: 'Tool', classId: 'id02' },
|
||||||
query: { username },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await updateClassJoinRequestHandler(req as Request, res as Response);
|
await expect(() => createStudentRequestHandler(req as Request, res as Response))
|
||||||
|
.rejects
|
||||||
expect(statusMock).toHaveBeenCalledWith(200);
|
.toThrow(ConflictException);
|
||||||
expect(jsonMock).toHaveBeenCalled();
|
|
||||||
const result = jsonMock.mock.lastCall?.[0];
|
|
||||||
console.log('[UPDATED REQUEST]', result);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it('Delete join request', async () => {
|
it('Delete join request', async () => {
|
||||||
req = {
|
req = {
|
||||||
params: { classId },
|
params: { username: 'Noordkaap', classId: 'id02' },
|
||||||
query: { username },
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await deleteClassJoinRequestHandler(req as Request, res as Response);
|
await deleteClassJoinRequestHandler(req as Request, res as Response);
|
||||||
|
|
||||||
expect(statusMock).toHaveBeenCalledWith(204);
|
expect(statusMock).toHaveBeenCalledWith(204);
|
||||||
expect(sendMock).toHaveBeenCalled();
|
|
||||||
|
await expect(() => deleteClassJoinRequestHandler(req as Request, res as Response))
|
||||||
|
.rejects
|
||||||
|
.toThrow(NotFoundException);
|
||||||
});
|
});
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,12 +28,17 @@ export class BaseController {
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async post<T>(path: string, body: unknown): Promise<T> {
|
protected async post<T>(path: string, body?: unknown): Promise<T> {
|
||||||
const res = await fetch(`${this.baseUrl}${path}`, {
|
const options: RequestInit = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(body),
|
};
|
||||||
});
|
|
||||||
|
if (body !== undefined) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`${this.baseUrl}${path}`, options);
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const errorData = await res.json().catch(() => ({}));
|
const errorData = await res.json().catch(() => ({}));
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BaseController } from "@/controllers/base-controller.ts";
|
||||||
|
|
||||||
export class StudentController extends BaseController {
|
export class StudentController extends BaseController {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("students");
|
super("student");
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(full = true) {
|
getAll(full = true) {
|
||||||
|
@ -10,15 +10,15 @@ export class StudentController extends BaseController {
|
||||||
}
|
}
|
||||||
|
|
||||||
getByUsername(username: string) {
|
getByUsername(username: string) {
|
||||||
return this.get<any>(`/${username}`);
|
return this.get<{ student: any }>(`/${username}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
createStudent(data: any) {
|
createStudent(data: any) {
|
||||||
return this.post<any>("/", data);
|
return this.post<{ student: any }>("/", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteStudent(username: string) {
|
deleteStudent(username: string) {
|
||||||
return this.delete<any>(`/${username}`);
|
return this.delete<{ student: any }>(`/${username}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getClasses(username: string, full = true) {
|
getClasses(username: string, full = true) {
|
||||||
|
@ -40,4 +40,16 @@ export class StudentController extends BaseController {
|
||||||
getQuestions(username: string, full = true) {
|
getQuestions(username: string, full = true) {
|
||||||
return this.get<{ questions: any[] }>(`/${username}/questions`, { full });
|
return this.get<{ questions: any[] }>(`/${username}/questions`, { full });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getJoinRequests(username: string) {
|
||||||
|
return this.get<{ requests: any[] }>(`/${username}/joinRequests`);
|
||||||
|
}
|
||||||
|
|
||||||
|
createJoinRequest(username: string, classId: string) {
|
||||||
|
return this.post<any>(`/${username}/joinRequests/${classId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteJoinRequest(username: string, classId: string) {
|
||||||
|
return this.delete<any>(`/${username}/joinRequests/${classId}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { BaseController } from "@/controllers/base-controller.ts";
|
||||||
|
|
||||||
export class TeacherController extends BaseController {
|
export class TeacherController extends BaseController {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("teachers");
|
super("teacher");
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(full = false) {
|
getAll(full = false) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { computed, toValue } from "vue";
|
import { computed, toValue } from "vue";
|
||||||
import type { MaybeRefOrGetter } from "vue";
|
import type { MaybeRefOrGetter } from "vue";
|
||||||
import { useQuery } from "@tanstack/vue-query";
|
import {useMutation, useQuery, useQueryClient} from "@tanstack/vue-query";
|
||||||
import { getStudentController } from "@/controllers/controllers.ts";
|
import { getStudentController } from "@/controllers/controllers.ts";
|
||||||
|
|
||||||
const studentController = getStudentController();
|
const studentController = getStudentController();
|
||||||
|
@ -75,7 +75,7 @@ export function useCreateStudentMutation() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (data: any) => studentController.createStudent(data),
|
mutationFn: (data: any) => studentController.createStudent(data),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['students'] });
|
await queryClient.invalidateQueries({ queryKey: ['students'] });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
alert("Create student failed:", err);
|
alert("Create student failed:", err);
|
||||||
|
@ -92,10 +92,13 @@ export function useDeleteStudentMutation() {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (username: string) => studentController.deleteStudent(username),
|
mutationFn: (username: string) => studentController.deleteStudent(username),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['students'] });
|
await queryClient.invalidateQueries({ queryKey: ['students'] });
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
alert("Delete student failed:", err);
|
alert("Delete student failed:", err);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
41
frontend/tests/controllers/student.test.ts
Normal file
41
frontend/tests/controllers/student.test.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { describe, it, expect, beforeAll } from 'vitest';
|
||||||
|
import {getStudentController} from "../../src/controllers/controllers";
|
||||||
|
|
||||||
|
const controller = getStudentController();
|
||||||
|
|
||||||
|
describe('StudentController', () => {
|
||||||
|
const newStudent = {
|
||||||
|
username: 'TestStudent',
|
||||||
|
firstName: 'Testy',
|
||||||
|
lastName: 'McTestface',
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
// Zet eventueel mock server op hier als je dat gebruikt
|
||||||
|
});
|
||||||
|
|
||||||
|
it('creates a student and fetches it by username', async () => {
|
||||||
|
// Create student
|
||||||
|
const created = await controller.createStudent(newStudent);
|
||||||
|
|
||||||
|
expect(created).toBeDefined();
|
||||||
|
expect(created.username).toBe(newStudent.username);
|
||||||
|
|
||||||
|
|
||||||
|
// Fetch same student
|
||||||
|
const fetched = await controller.getByUsername(newStudent.username);
|
||||||
|
|
||||||
|
expect(fetched).toBeDefined();
|
||||||
|
expect(fetched.student).toBeDefined();
|
||||||
|
|
||||||
|
const student = fetched.student;
|
||||||
|
expect(student.username).toBe(newStudent.username);
|
||||||
|
expect(student.firstName).toBe(newStudent.firstName);
|
||||||
|
expect(student.lastName).toBe(newStudent.lastName);
|
||||||
|
|
||||||
|
await controller.deleteStudent(newStudent.username);
|
||||||
|
|
||||||
|
await expect(controller.getByUsername(newStudent.username)).rejects.toThrow();
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue