Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Exam Endpoints #345

Merged
merged 5 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions _apidoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,35 @@
* attainment as per Bloom's Taxanomy (L1-L6).
*/

// ------------------------------------------------------------------------------------------
// Exam.
// ------------------------------------------------------------------------------------------

/**
* @api {post} /exam/add Add Exam
* @apiName AddExam
* @apiExam Exam
* @apiDescription Add a new exam.
*
* @apiBody {String} title Exam title.
* @apiBody {ObjectId[]} students Array of student ObjectIDs.
*
* @apiSuccess {String} res Response message.
* @apiError (Error 500) ExamAddError Error while adding the exam
*
* @apiSuccessExample Success-Response:
* HTTP/1.1 200 OK
* {
* "res": "added exam Example Exam"
* }
*
* @apiErrorExample Error-Response:
* HTTP/1.1 500 Internal Server Error
* {
* "err": "Error while inserting in DB"
* }
*/

/**
* @api {post} /module/add Add Module
* @apiName AddModule
Expand Down Expand Up @@ -983,6 +1012,18 @@
* }
*/

/**
* @api {delete} /exam/delete/:id Delete Exam
* @apiName DeleteExam
* @apiExam Exam
*
* @apiParam {ObjectId} id The ObjectID of the exam to delete.
*
* @apiSuccess {String} res Success message indicating the deletion.
* @apiError (Error 500) ExamDeleteError Error while deleting the exam
*
*/

/**
* @api {delete} /assignment/delete/:assignmentId To delete Assignment
* @apiName DeleteAssignment
Expand Down Expand Up @@ -1034,6 +1075,21 @@
*
*/

/**
* @api {post} /exam/update/:id Update Exam Details
* @apiName UpdateExam
* @apiExam Exam
* @apiDescription Update existing exam details.
*
* @apiParam {ObjectId} id The ObjectID of the exam to update.
* @apiBody {String} [title] Exam title.
* @apiBody {ObjectId[]} [students] Array of student ObjectIDs.
*
* @apiSuccess {String} res Exam updated.
* @apiError (Error 500) ExamUpdateError Error in updating database
*
*/

/**
* @api {get} assignment/list Get Assignment List
* @apiName GetAssignment
Expand Down Expand Up @@ -1185,6 +1241,19 @@
*
*/

/**
* @api {get} /exam/list Get Exam List
* @apiName GetExamList
* @apiExam Exam
*
* @apiQuery {String} [title] Title of the exam.
*
* @apiSuccess {Exam[]} res Array of filtered exam documents.
* @apiSuccess {ObjectId} exam._id ObjectID of the exam document in the database.
* @apiSuccess {String} exam.title Title of the exam.
* @apiSuccess {ObjectId[]} exam.students Array of student ObjectIDs in the exam.
*/

/**
* @api {get} /group/list Get Group List
* @apiName GetGroupList
Expand Down
2 changes: 2 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import moduleRouter from "#routes/module";
import facultyRouter from "#routes/faculty";
import { identifyUser } from "#middleware/identifyUser";
import departmentRouter from "#routes/department";
import examRouter from "#routes/exam";
import paperRouter from "#routes/paper";
import groupRouter from "#routes/group";
import performarouter from "#routes/performance";
Expand Down Expand Up @@ -59,6 +60,7 @@ app.use("/timetable", timetableRouter);
app.use("/department", departmentRouter);
app.use("/coursework", courseworkRouter);
app.use("/module", moduleRouter);
app.use("/exam", examRouter);
app.use("/paper", paperRouter);
app.use("/group", groupRouter);
app.use("/semester", semesterRouter);
Expand Down
65 changes: 65 additions & 0 deletions controller/exam.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
createExam,
deleteExamById,
examList,
updateExamById,
} from "#services/exam";
import { logger } from "#util";

async function addExam(req, res) {
const {
date, startTime, duration, infrastructure, supervisor, course,
} = req.body;
try {
const exam = await createExam(
date,
startTime,
duration,
supervisor,
infrastructure,
course,
);
res.json({ res: `added exam ${exam.id}`, id: exam.id });
} catch (error) {
logger.error("Error while inserting", error);
res.status(500);
res.json({ err: "Error while inserting in DB" });
}
}

async function updateExam(req, res) {
const { id } = req.params;
const { ...data } = req.body;
try {
await updateExamById(id, data);
res.json({ res: `updated exam with id ${id}` });
} catch (error) {
logger.error("Error while updating", error);
res.status(500);
res.json({ err: "Error while updaing in DB" });
}
}

async function getExam(req, res) {
const filter = req.query;
const exam = await examList(filter);
res.json({ res: exam });
}

async function deleteExam(req, res) {
const { id } = req.params;
try {
await deleteExamById(id);
res.json({ res: `Deleted exam with ID ${id}` });
} catch (error) {
logger.error("Error while deleting", error);
res.status(500).json({ error: "Error while deleting from DB" });
}
}

export default {
addExam,
deleteExam,
getExam,
updateExam,
};
26 changes: 21 additions & 5 deletions models/exam.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,21 @@ const examSchema = {
date: { type: Date, required: true },
startTime: { type: Date, required: true },
duration: { type: Number, required: true },
supervisor: { type: connector.Schema.Types.ObjectId, ref: "Faculty", required: "true" },
infrastructure: { type: connector.Schema.Types.ObjectId, ref: "Infrastructure", required: "true" },
course: { type: connector.Schema.Types.ObjectId, ref: "Course", required: "true" },
supervisor: {
type: connector.Schema.Types.ObjectId,
ref: "Faculty",
required: "true",
},
infrastructure: {
type: connector.Schema.Types.ObjectId,
ref: "Infrastructure",
required: "true",
},
course: {
type: connector.Schema.Types.ObjectId,
ref: "Course",
required: "true",
},
};

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

async function update(filter, updateObject, options = { multi: true }) {
const updateResult = await Exam.updateMany(filter, { $set: updateObject }, options);
const updateResult = await Exam.updateMany(
filter,
{ $set: updateObject },
options,
);
return updateResult.acknowledged;
}

async function remove(filter) {
const deleteResult = await Exam.deleteMany(filter).exec();
const deleteResult = await Exam.deleteMany(filter);
return deleteResult.acknowledged;
}

Expand Down
10 changes: 10 additions & 0 deletions routes/exam.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import express from "express";
import examController from "#controller/exam";

const router = express.Router();
router.post("/add", examController.addExam);
router.get("/list", examController.getExam);
router.post("/update/:id", examController.updateExam);
router.delete("/delete/:id", examController.deleteExam);

export default router;
45 changes: 45 additions & 0 deletions services/exam.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import Exam from "#models/exam";
import databaseError from "#error/database";

export async function createExam(
date,
startTime,
duration,
supervisor,
infrastructure,
course,
) {
const newExam = await Exam.create({
date,
startTime,
duration,
supervisor,
infrastructure,
course,
});
if (Date(newExam.date) === Date(date)) {
return newExam;
}
throw new databaseError.DataEntryError("exam");
}

export async function updateExamById(id, data) {
const updated = await Exam.update({ _id: id }, data);
if (updated) {
return updated;
}
throw new databaseError.DataEntryError("exam");
}

export async function examList(filter) {
const exams = await Exam.read(filter, 0);
return exams;
}

export async function deleteExamById(examId) {
const deleted = await Exam.remove({ _id: examId });
if (deleted) {
return deleted;
}
throw new databaseError.DataDeleteError("exam");
}
90 changes: 90 additions & 0 deletions test/routes/exam.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { jest } from "@jest/globals"; // eslint-disable-line import/no-extraneous-dependencies
import request from "supertest";
import app from "#app";
import connector from "#models/databaseUtil";
import examModel from "#models/exam";

jest.mock("#util");

let server;
let agent;

beforeAll((done) => {
server = app.listen(null, () => {
agent = request.agent(server);
connector.set("debug", false);
done();
});
});

function cleanUp(callback) {
examModel
.remove({
supervisor: "60a0e7e9a09c3f001c834e07",
})
.then(() => {
connector.disconnect((DBerr) => {
if (DBerr) console.log("Database disconnect error: ", DBerr);
server.close((serverErr) => {
if (serverErr) console.log(serverErr);
callback();
});
});
});
}

afterAll((done) => {
cleanUp(done);
});

describe("exam API", () => {
it("should create exam", async () => {
const response = await agent.post("/exam/add").send({
date: "2023-06-18T14:11:30Z",
startTime: "2023-06-18T14:11:30Z",
duration: 5,
supervisor: ["5f8778b54b553439ac49a03a"],
infrastructure: ["5f8778b54b553439ac49a03a"],
course: ["5f8778b54b553439ac49a03a"],
});
expect(response.headers["content-type"]).toMatch(/json/);
expect(response.status).toBe(200);
expect(response.body.res).toMatch(/added exam/);
});

describe("after adding exam", () => {
let id;
beforeEach(async () => {
id = await agent.post("/exam/add").send({
date: "2023-06-18T14:11:30Z",
startTime: "2023-06-18T14:11:30Z",
duration: 5,
supervisor: "64453a62c8f2146f2f34c73a",
infrastructure: "64453a62c8f2146f2f34c73a",
course: "64453a62c8f2146f2f34c73a",
});
id = JSON.parse(id.res.text).id;
});

afterEach(async () => {
await examModel.remove({ supervisor: "64453a62c8f2146f2f34c73a" });
});

it("should read exam", async () => {
const response = await agent
.get("/exam/list")
.send({ supervisor: "64453a62c8f2146f2f34c73a" });
expect(response.status).toBe(200);
expect(response.body.res).toBeDefined();
});

it("should update exam", async () => {
const response = await agent
.post(`/exam/update/${id}`)
.send({ duration: 10 });
expect(response.headers["content-type"]).toMatch(/json/);
expect(response.status).toBe(200);
expect(response.body.res).toMatch(/updated exam/);
});
});
});