Skip to content

Commit ae5b099

Browse files
Merge pull request #345 from tcet-opensource/316-all-endpoints-for-exam
Implement Exam Endpoints
2 parents 1e188bd + 392bbb1 commit ae5b099

File tree

7 files changed

+302
-5
lines changed

7 files changed

+302
-5
lines changed

_apidoc.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,35 @@
672672
* attainment as per Bloom's Taxanomy (L1-L6).
673673
*/
674674

675+
// ------------------------------------------------------------------------------------------
676+
// Exam.
677+
// ------------------------------------------------------------------------------------------
678+
679+
/**
680+
* @api {post} /exam/add Add Exam
681+
* @apiName AddExam
682+
* @apiExam Exam
683+
* @apiDescription Add a new exam.
684+
*
685+
* @apiBody {String} title Exam title.
686+
* @apiBody {ObjectId[]} students Array of student ObjectIDs.
687+
*
688+
* @apiSuccess {String} res Response message.
689+
* @apiError (Error 500) ExamAddError Error while adding the exam
690+
*
691+
* @apiSuccessExample Success-Response:
692+
* HTTP/1.1 200 OK
693+
* {
694+
* "res": "added exam Example Exam"
695+
* }
696+
*
697+
* @apiErrorExample Error-Response:
698+
* HTTP/1.1 500 Internal Server Error
699+
* {
700+
* "err": "Error while inserting in DB"
701+
* }
702+
*/
703+
675704
/**
676705
* @api {post} /module/add Add Module
677706
* @apiName AddModule
@@ -983,6 +1012,18 @@
9831012
* }
9841013
*/
9851014

1015+
/**
1016+
* @api {delete} /exam/delete/:id Delete Exam
1017+
* @apiName DeleteExam
1018+
* @apiExam Exam
1019+
*
1020+
* @apiParam {ObjectId} id The ObjectID of the exam to delete.
1021+
*
1022+
* @apiSuccess {String} res Success message indicating the deletion.
1023+
* @apiError (Error 500) ExamDeleteError Error while deleting the exam
1024+
*
1025+
*/
1026+
9861027
/**
9871028
* @api {delete} /assignment/delete/:assignmentId To delete Assignment
9881029
* @apiName DeleteAssignment
@@ -1034,6 +1075,21 @@
10341075
*
10351076
*/
10361077

1078+
/**
1079+
* @api {post} /exam/update/:id Update Exam Details
1080+
* @apiName UpdateExam
1081+
* @apiExam Exam
1082+
* @apiDescription Update existing exam details.
1083+
*
1084+
* @apiParam {ObjectId} id The ObjectID of the exam to update.
1085+
* @apiBody {String} [title] Exam title.
1086+
* @apiBody {ObjectId[]} [students] Array of student ObjectIDs.
1087+
*
1088+
* @apiSuccess {String} res Exam updated.
1089+
* @apiError (Error 500) ExamUpdateError Error in updating database
1090+
*
1091+
*/
1092+
10371093
/**
10381094
* @api {get} assignment/list Get Assignment List
10391095
* @apiName GetAssignment
@@ -1185,6 +1241,19 @@
11851241
*
11861242
*/
11871243

1244+
/**
1245+
* @api {get} /exam/list Get Exam List
1246+
* @apiName GetExamList
1247+
* @apiExam Exam
1248+
*
1249+
* @apiQuery {String} [title] Title of the exam.
1250+
*
1251+
* @apiSuccess {Exam[]} res Array of filtered exam documents.
1252+
* @apiSuccess {ObjectId} exam._id ObjectID of the exam document in the database.
1253+
* @apiSuccess {String} exam.title Title of the exam.
1254+
* @apiSuccess {ObjectId[]} exam.students Array of student ObjectIDs in the exam.
1255+
*/
1256+
11881257
/**
11891258
* @api {get} /group/list Get Group List
11901259
* @apiName GetGroupList

app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import moduleRouter from "#routes/module";
2323
import facultyRouter from "#routes/faculty";
2424
import { identifyUser } from "#middleware/identifyUser";
2525
import departmentRouter from "#routes/department";
26+
import examRouter from "#routes/exam";
2627
import paperRouter from "#routes/paper";
2728
import groupRouter from "#routes/group";
2829
import performarouter from "#routes/performance";
@@ -59,6 +60,7 @@ app.use("/timetable", timetableRouter);
5960
app.use("/department", departmentRouter);
6061
app.use("/coursework", courseworkRouter);
6162
app.use("/module", moduleRouter);
63+
app.use("/exam", examRouter);
6264
app.use("/paper", paperRouter);
6365
app.use("/group", groupRouter);
6466
app.use("/semester", semesterRouter);

controller/exam.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import {
2+
createExam,
3+
deleteExamById,
4+
examList,
5+
updateExamById,
6+
} from "#services/exam";
7+
import { logger } from "#util";
8+
9+
async function addExam(req, res) {
10+
const {
11+
date, startTime, duration, infrastructure, supervisor, course,
12+
} = req.body;
13+
try {
14+
const exam = await createExam(
15+
date,
16+
startTime,
17+
duration,
18+
supervisor,
19+
infrastructure,
20+
course,
21+
);
22+
res.json({ res: `added exam ${exam.id}`, id: exam.id });
23+
} catch (error) {
24+
logger.error("Error while inserting", error);
25+
res.status(500);
26+
res.json({ err: "Error while inserting in DB" });
27+
}
28+
}
29+
30+
async function updateExam(req, res) {
31+
const { id } = req.params;
32+
const { ...data } = req.body;
33+
try {
34+
await updateExamById(id, data);
35+
res.json({ res: `updated exam with id ${id}` });
36+
} catch (error) {
37+
logger.error("Error while updating", error);
38+
res.status(500);
39+
res.json({ err: "Error while updaing in DB" });
40+
}
41+
}
42+
43+
async function getExam(req, res) {
44+
const filter = req.query;
45+
const exam = await examList(filter);
46+
res.json({ res: exam });
47+
}
48+
49+
async function deleteExam(req, res) {
50+
const { id } = req.params;
51+
try {
52+
await deleteExamById(id);
53+
res.json({ res: `Deleted exam with ID ${id}` });
54+
} catch (error) {
55+
logger.error("Error while deleting", error);
56+
res.status(500).json({ error: "Error while deleting from DB" });
57+
}
58+
}
59+
60+
export default {
61+
addExam,
62+
deleteExam,
63+
getExam,
64+
updateExam,
65+
};

models/exam.js

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,21 @@ const examSchema = {
44
date: { type: Date, required: true },
55
startTime: { type: Date, required: true },
66
duration: { type: Number, required: true },
7-
supervisor: { type: connector.Schema.Types.ObjectId, ref: "Faculty", required: "true" },
8-
infrastructure: { type: connector.Schema.Types.ObjectId, ref: "Infrastructure", required: "true" },
9-
course: { type: connector.Schema.Types.ObjectId, ref: "Course", required: "true" },
7+
supervisor: {
8+
type: connector.Schema.Types.ObjectId,
9+
ref: "Faculty",
10+
required: "true",
11+
},
12+
infrastructure: {
13+
type: connector.Schema.Types.ObjectId,
14+
ref: "Infrastructure",
15+
required: "true",
16+
},
17+
course: {
18+
type: connector.Schema.Types.ObjectId,
19+
ref: "Course",
20+
required: "true",
21+
},
1022
};
1123

1224
const Exam = connector.model("Exam", examSchema);
@@ -23,12 +35,16 @@ async function read(filter, limit = 1) {
2335
}
2436

2537
async function update(filter, updateObject, options = { multi: true }) {
26-
const updateResult = await Exam.updateMany(filter, { $set: updateObject }, options);
38+
const updateResult = await Exam.updateMany(
39+
filter,
40+
{ $set: updateObject },
41+
options,
42+
);
2743
return updateResult.acknowledged;
2844
}
2945

3046
async function remove(filter) {
31-
const deleteResult = await Exam.deleteMany(filter).exec();
47+
const deleteResult = await Exam.deleteMany(filter);
3248
return deleteResult.acknowledged;
3349
}
3450

routes/exam.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import express from "express";
2+
import examController from "#controller/exam";
3+
4+
const router = express.Router();
5+
router.post("/add", examController.addExam);
6+
router.get("/list", examController.getExam);
7+
router.post("/update/:id", examController.updateExam);
8+
router.delete("/delete/:id", examController.deleteExam);
9+
10+
export default router;

services/exam.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import Exam from "#models/exam";
2+
import databaseError from "#error/database";
3+
4+
export async function createExam(
5+
date,
6+
startTime,
7+
duration,
8+
supervisor,
9+
infrastructure,
10+
course,
11+
) {
12+
const newExam = await Exam.create({
13+
date,
14+
startTime,
15+
duration,
16+
supervisor,
17+
infrastructure,
18+
course,
19+
});
20+
if (Date(newExam.date) === Date(date)) {
21+
return newExam;
22+
}
23+
throw new databaseError.DataEntryError("exam");
24+
}
25+
26+
export async function updateExamById(id, data) {
27+
const updated = await Exam.update({ _id: id }, data);
28+
if (updated) {
29+
return updated;
30+
}
31+
throw new databaseError.DataEntryError("exam");
32+
}
33+
34+
export async function examList(filter) {
35+
const exams = await Exam.read(filter, 0);
36+
return exams;
37+
}
38+
39+
export async function deleteExamById(examId) {
40+
const deleted = await Exam.remove({ _id: examId });
41+
if (deleted) {
42+
return deleted;
43+
}
44+
throw new databaseError.DataDeleteError("exam");
45+
}

test/routes/exam.test.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { jest } from "@jest/globals"; // eslint-disable-line import/no-extraneous-dependencies
2+
import request from "supertest";
3+
import app from "#app";
4+
import connector from "#models/databaseUtil";
5+
import examModel from "#models/exam";
6+
7+
jest.mock("#util");
8+
9+
let server;
10+
let agent;
11+
12+
beforeAll((done) => {
13+
server = app.listen(null, () => {
14+
agent = request.agent(server);
15+
connector.set("debug", false);
16+
done();
17+
});
18+
});
19+
20+
function cleanUp(callback) {
21+
examModel
22+
.remove({
23+
supervisor: "60a0e7e9a09c3f001c834e07",
24+
})
25+
.then(() => {
26+
connector.disconnect((DBerr) => {
27+
if (DBerr) console.log("Database disconnect error: ", DBerr);
28+
server.close((serverErr) => {
29+
if (serverErr) console.log(serverErr);
30+
callback();
31+
});
32+
});
33+
});
34+
}
35+
36+
afterAll((done) => {
37+
cleanUp(done);
38+
});
39+
40+
describe("exam API", () => {
41+
it("should create exam", async () => {
42+
const response = await agent.post("/exam/add").send({
43+
date: "2023-06-18T14:11:30Z",
44+
startTime: "2023-06-18T14:11:30Z",
45+
duration: 5,
46+
supervisor: ["5f8778b54b553439ac49a03a"],
47+
infrastructure: ["5f8778b54b553439ac49a03a"],
48+
course: ["5f8778b54b553439ac49a03a"],
49+
});
50+
expect(response.headers["content-type"]).toMatch(/json/);
51+
expect(response.status).toBe(200);
52+
expect(response.body.res).toMatch(/added exam/);
53+
});
54+
55+
describe("after adding exam", () => {
56+
let id;
57+
beforeEach(async () => {
58+
id = await agent.post("/exam/add").send({
59+
date: "2023-06-18T14:11:30Z",
60+
startTime: "2023-06-18T14:11:30Z",
61+
duration: 5,
62+
supervisor: "64453a62c8f2146f2f34c73a",
63+
infrastructure: "64453a62c8f2146f2f34c73a",
64+
course: "64453a62c8f2146f2f34c73a",
65+
});
66+
id = JSON.parse(id.res.text).id;
67+
});
68+
69+
afterEach(async () => {
70+
await examModel.remove({ supervisor: "64453a62c8f2146f2f34c73a" });
71+
});
72+
73+
it("should read exam", async () => {
74+
const response = await agent
75+
.get("/exam/list")
76+
.send({ supervisor: "64453a62c8f2146f2f34c73a" });
77+
expect(response.status).toBe(200);
78+
expect(response.body.res).toBeDefined();
79+
});
80+
81+
it("should update exam", async () => {
82+
const response = await agent
83+
.post(`/exam/update/${id}`)
84+
.send({ duration: 10 });
85+
expect(response.headers["content-type"]).toMatch(/json/);
86+
expect(response.status).toBe(200);
87+
expect(response.body.res).toMatch(/updated exam/);
88+
});
89+
});
90+
});

0 commit comments

Comments
 (0)