diff --git a/back-end/app.js b/back-end/app.js index a74b2c4..58fc4a1 100644 --- a/back-end/app.js +++ b/back-end/app.js @@ -7,7 +7,6 @@ import url from "url"; import path from "path"; import login from "./src/routes/login.js"; import studentIssues from "./src/routes/studentIssues.js"; -import studentIssueViewDetails from "./src/routes/studentIssueViewDetails.js"; import studentIssueUpdate from "./src/routes/studentIssueUpdate.js"; import adminIssues from "./src/routes/adminIssues.js"; import adminIssueViewDetails from "./src/routes/adminIssueViewDetails.js"; diff --git a/back-end/package.json b/back-end/package.json index 1362df7..3e62595 100644 --- a/back-end/package.json +++ b/back-end/package.json @@ -4,11 +4,11 @@ "description": "The back-end of your project will live in this directory.", "main": "index.js", "scripts": { - "test": "NODE_ENV=test mocha test/**/*.test.js", + "test": "mocha test/**/*.test.js", "start": "node server.js", "dev": "nodemon server.js", "lint": "eslint **/*.js", - "coverage": "NODE_ENV=test c8 mocha test/**/*.test.js" + "coverage": "c8 mocha test/**/*.test.js" }, "keywords": [], "author": "", diff --git a/back-end/src/controllers/adminIssuesHandler.js b/back-end/src/controllers/adminIssuesHandler.js index f71d5b4..23f178d 100644 --- a/back-end/src/controllers/adminIssuesHandler.js +++ b/back-end/src/controllers/adminIssuesHandler.js @@ -4,8 +4,8 @@ export async function issueRetrievalHandler(req, res) { const { paramName } = req.params; try { + // Check if issues exist for the department const issues = await IssueModel.find({ "departments": paramName }); - res.json(issues); } catch (error) { console.error("Error retrieving data:", error.message); diff --git a/back-end/src/controllers/studentIssueViewDetailsHandler.js b/back-end/src/controllers/studentIssueViewDetailsHandler.js index b996bbf..fe5f1dd 100644 --- a/back-end/src/controllers/studentIssueViewDetailsHandler.js +++ b/back-end/src/controllers/studentIssueViewDetailsHandler.js @@ -1,35 +1,29 @@ import Issue from '../../models/issueModel.js'; -// The function retrieves all the issues related to this student export async function studentIssueViewDetailsHandler(req, res) { - const { paramName } = req.params; - const { studentNetID } = req.params; + const { paramName } = req.params; // Get the issue index from request params + const { studentNetID } = req.params; // Get the studentNetID from request params + // Check if studentNetID is missing or invalid if (!studentNetID) { return res.status(400).send("Missing or invalid studentNetID."); } + // Check if paramName (issue index) is missing or invalid if (!paramName) { return res.status(400).send("Missing or invalid issue index."); } try { + // Query the database to find issues that match both studentNetID and index + const response = await Issue.find({ studentNetID: studentNetID, index: paramName }); - const response = await Issue.find({ index: paramName }); - - // Check if any data is returned for the student + // Check if no matching issues are found if (!response || response.length === 0) { - return res.status(500).send("No issues found for the given studentNetID."); - } - - // Check if the specific issue index exists - if (response.length === 0) { - return res.status(500).send("Issue with the given index not found."); + return res.status(500).send("No issues found for the given studentNetID and index."); } - - res.json(response); // Send only the data that matches the specific issue index + res.json(response); } catch (error) { - // Log the error and send an appropriate response console.error("Error retrieving data:", error.message); res.status(500).send("An error occurred while retrieving the data."); } diff --git a/back-end/test/adminIssueRetrieval.test.js b/back-end/test/adminIssueRetrieval.test.js deleted file mode 100644 index f143e7a..0000000 --- a/back-end/test/adminIssueRetrieval.test.js +++ /dev/null @@ -1,106 +0,0 @@ -/* eslint-disable */ -// Unit Testing -import chai, { assert } from "chai"; -import sinon from "sinon"; -import axios from "axios"; -import fs from "fs/promises"; -import { issueRetrievalHandler } from "../src/controllers/adminIssuesHandler.js"; -import chaiHttp from "chai-http"; - -const data = await fs.readFile("./public/json/mockapi.json", "utf8"); -const mockData = JSON.parse(data); -const filteredData = mockData.filter((item) => item.departments.includes("IT")); - -describe("Unit Tests for Admin Dashboard", () => { - let req, res, axiosGetStub; - let sendSpy, jsonSpy; - - beforeEach(() => { - req = { params: { paramName: "IT" } }; - res = { - json: sinon.spy(), - status: sinon.stub().returns({ send: sinon.spy() }) - }; - sendSpy = res.status().send; - jsonSpy = res.json; - axiosGetStub = sinon.stub(axios, "get"); - }); - - afterEach(() => { - sinon.restore(); - }); - - it('should filter and return the correct number of data records for "IT" department', async () => { - axiosGetStub.resolves({ data: mockData }); - - await issueRetrievalHandler(req, res); - - assert.isTrue(axiosGetStub.calledOnce, "axios.get should be called once"); - assert.equal( - res.json.firstCall.args[0].length, - filteredData.length, - "Number of records should match the filtered data length" - ); - assert.deepEqual( - res.json.firstCall.args[0], - filteredData, - "Returned data should match the filtered data" - ); - }); - - // Error Handling Test Case - it('should handle errors when axios request fails', async () => { - axiosGetStub.rejects(new Error('Axios request failed')); - await issueRetrievalHandler(req, res); - assert.isTrue(axiosGetStub.calledOnce, "axios.get should be called once"); - assert.isTrue(sendSpy.calledOnce, "res.status().send should be called once"); - assert.equal(sendSpy.firstCall.args[0], "An error occurred while retrieving the data.", "Error message should be sent"); - }); - - // Edge Case Test Case - it('should return an empty array when paramName is not found in any data records', async () => { - req.params.paramName = "blackMagic"; - axiosGetStub.resolves({ data: mockData }); - await issueRetrievalHandler(req, res); - assert.isTrue(axiosGetStub.calledOnce, "axios.get should be called once"); - assert.equal( - res.json.firstCall.args[0].length, - 0, - "Number of records should be 0 when paramName is not found" - ); - }); - -}); - - -// Integration Testing - -chai.use(chaiHttp); - -describe("Integration Tests for Admin Issue Retrieval Endpoints", () => { - describe("GET /api/issues/admin/:paramName", () => { - it("should retrieve issues for a given department and match all records", async () => { - const currentDept = "IT"; - const res = await chai - .request("http://localhost:5000") - .get(`/api/issues/admin/${currentDept}`); - - assert.strictEqual(res.status, 200, "Response status should be 200"); - assert.isArray(res.body, "Response body should be an array"); - assert.lengthOf( - res.body, - filteredData.length, - `Response body should have ${filteredData.length} records` - ); - - res.body.forEach((record, index) => { - assert.deepEqual( - record, - filteredData[index], - `Record at index ${index} should match the expected data` - ); - }); - }); - }); -}); -/* eslint-enable */ diff --git a/back-end/test/adminIssues.test.js b/back-end/test/adminIssues.test.js new file mode 100644 index 0000000..94d39ab --- /dev/null +++ b/back-end/test/adminIssues.test.js @@ -0,0 +1,39 @@ +/* eslint-disable */ +import chai, { assert } from "chai"; +import IssueModel from "../models/issueModel.js"; +import chaiHttp from "chai-http"; +import server from "../app.js"; + +chai.use(chaiHttp); + +process.env.NODE_ENV = "test"; + +// Integration tests for the adminIssues.js file +describe("Integration Tests for Admin Issue Handler Endpoint", () => { + describe("GET /api/issues/admin/:paramName", () => { + it("should retrieve all issues for a valid admin user", async () => { + const paramName = "admin"; + const res = await chai + .request(server) + .get(`/api/issues/admin/${paramName}`); + // Check that the response is correct + assert.equal(res.status, 200); + // Check that the response is an array + assert.isArray(res.body); + // Check that the response is the same length as the number of issues + const userIssues = await IssueModel.find({ "departments": paramName }); + // Check that the response is the same length as the number of issues of that user + assert.equal(res.body.length, userIssues.length); + }); + + it("should handle errors gracefully for an invalid admin user", async () => { + const paramName = "invalid"; + const res = await chai + .request(server) + .get(`/api/issues/admin/${paramName}`); + assert.equal(res.status, 200); + assert.deepEqual(res.body, []); + }); + }); +}); +/* eslint-enable */ diff --git a/back-end/test/dummy.test.js b/back-end/test/dummy.test.js deleted file mode 100644 index db6f725..0000000 --- a/back-end/test/dummy.test.js +++ /dev/null @@ -1,13 +0,0 @@ -/*eslint-disable*/ -import { assert } from "chai"; - -describe("Sample Test Suite", () => { - it("should pass this test", () => { - assert.equal(1 + 1, 2); - }); - - it("should fail this test", () => { - assert.equal(2 * 2, 5); - }); -}); -/*eslint-enable*/ \ No newline at end of file diff --git a/back-end/test/login.test.js b/back-end/test/login.test.js index e4c4f8b..a7b6eaa 100644 --- a/back-end/test/login.test.js +++ b/back-end/test/login.test.js @@ -4,6 +4,8 @@ import chaiHttp from "chai-http"; import server from "../app.js"; chai.use(chaiHttp); +process.env.NODE_ENV = "test"; + // Integration tests for the login.js file describe("Integration Tests for Login Endpoints", () => { // Check student login diff --git a/back-end/test/studentIssueDetails.test.js b/back-end/test/studentIssueDetails.test.js deleted file mode 100644 index 5372561..0000000 --- a/back-end/test/studentIssueDetails.test.js +++ /dev/null @@ -1,182 +0,0 @@ -import chai, { assert } from "chai"; -import chaiHttp from "chai-http"; -import sinon from "sinon"; -import axios from "axios"; -import fs from "fs/promises"; -import {studentIssueViewDetailsHandler} from "../src/controllers/studentIssueViewDetailsHandler.js"; - -let server = "http://localhost:5000"; - -const data = await fs.readFile("./public/json/mockapi.json", "utf8"); -const mockResponse = JSON.parse(data); - -// Unit Testing - -describe("Unit Tests for studentIssueViewDetailsHandler", () => { - let req, res, axiosGetStub, sendSpy, jsonSpy, statusSpy; - - beforeEach(() => { - req = { params: { paramName: "123", studentNetID: "s123456" } }; - res = { - json: sinon.spy(), - status: sinon.stub().returns({ send: sinon.spy() }) - }; - sendSpy = res.status().send; - jsonSpy = res.json; - statusSpy = res.status; - axiosGetStub = sinon.stub(axios, "get"); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should return 400 error if studentNetID is missing", async () => { - delete req.params.studentNetID; - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(statusSpy.calledWith(400)); - assert.isTrue(sendSpy.calledWith("Missing or invalid studentNetID.")); - }); - - it("should return 400 error if paramName is missing", async () => { - delete req.params.paramName; - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(statusSpy.calledWith(400)); - assert.isTrue(sendSpy.calledWith("Missing or invalid issue index.")); - }); - - it("should return filtered data for a valid request", async () => { - const mockData = [{ index: "123", detail: "Issue details" }]; - axiosGetStub.resolves({ data: mockData }); - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(jsonSpy.calledWith([mockData[0]])); - }); - - it("should return 500 error if no data found for student", async () => { - axiosGetStub.resolves({ data: [] }); - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(statusSpy.calledWith(500)); - assert.isTrue(sendSpy.calledWith("No issues found for the given studentNetID.")); - }); - - it("should return 500 error if issue index not found", async () => { - const mockData = [{ index: "124", detail: "Issue details" }]; - axiosGetStub.resolves({ data: mockData }); - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(statusSpy.calledWith(500)); - assert.isTrue(sendSpy.calledWith("Issue with the given index not found.")); - }); - - it("should handle axios errors", async () => { - axiosGetStub.rejects(new Error("Axios error")); - await studentIssueViewDetailsHandler(req, res); - assert.isTrue(statusSpy.calledWith(500)); - assert.isTrue(sendSpy.calledWith("An error occurred while retrieving the data.")); - }); -}); - -// Integration Testing - -chai.use(chaiHttp); - -describe("Integration Tests for Student Issue Details", () => { - // Stub for axios - let req, res, axiosStub; - - before(() => { - req = { params: { paramName: "tm2005" } }; - res = { - json: sinon.spy(), - status: sinon.stub().returns({ send: sinon.spy() }) // Stubbed here - }; - axiosStub = sinon.stub(axios, 'get'); - }); - - after(() => { - axiosStub.restore(); - }); - - it("should retrieve and filter specific student issue correctly", async () => { - // const mockResponse = { - // data: [ - // { - // "index": 6, - // "studentNetID": ["tm2005"], - // "studentName": ["Ted Mosby"], - // "title": "Global Programs Information Session", - // "description": "When is the next information session for global education programs?", - // "attachments": [null], - // "departments": ["GlobalEd"], - // "comments": [ - // "Our international student office can assist you with the visa process." - // ], - // "dateCreated": "05/01/2023", - // "timeCreated": "21:51", - // "currentStatus": "Action Required", - // "currentPriority": "High Priority" - // }, - // { - // "index": 7, - // "studentNetID": ["tm2005"], - // "studentName": ["Ted Mosby"], - // "title": "Career Fair Event Details", - // "description": "Could you provide the details for the upcoming career fair event?", - // "attachments": [null], - // "departments": ["CDC", "Facilities"], - // "comments": ["Details for the career fair have been sent to your email."], - // "dateCreated": "11/09/2023", - // "timeCreated": "16:20", - // "currentStatus": "Action Required", - // "currentPriority": "High Priority" - // } - // ] - // }; - - // Execute the handler - await studentIssueViewDetailsHandler(req, res); - - axiosStub.resolves(mockResponse); - - const studentNetID = "tm2005"; - const paramName = "6"; - const response = await chai.request(server) - .get(`/api/issues/student/${studentNetID}/${paramName}`); // Update this with the correct route - - // response= response.filter( - // (item) => String(item.index) === String(paramName) - // ) - - // assert(axiosStub.called); - assert.equal(response.status, 200); - assert.deepEqual(response.body, [{ - "index": 6, - "studentNetID": ["tm2005"], - "studentName": ["Ted Mosby"], - "title": "Global Programs Information Session", - "description": "When is the next information session for global education programs?", - "attachments": [null], - "departments": ["GlobalEd"], - "comments": [ - "Our international student office can assist you with the visa process." - ], - "dateCreated": "05/01/2023", - "timeCreated": "21:51", - "currentStatus": "Action Required", - "currentPriority": "High Priority" - }]); - }); - - it("should handle errors gracefully", async () => { - // axiosStub.rejects(new Error("Network error")); - - const studentNetID = "tm2005"; - const paramName = "9999"; - const response = await chai.request(server) - .get(`/api/issues/student/${studentNetID}/${paramName}`) // Update this with the correct route - // .query({ studentNetID: 'tm2005', paramName: '6' }); - - // assert(axiosStub.called); - assert.equal(response.status, 500); - assert.equal(response.text, "Issue with the given index not found."); - }); -}); diff --git a/back-end/test/studentIssuesHandler.test.js b/back-end/test/studentIssues.test.js similarity index 50% rename from back-end/test/studentIssuesHandler.test.js rename to back-end/test/studentIssues.test.js index 4a0e61a..b3e33f6 100644 --- a/back-end/test/studentIssuesHandler.test.js +++ b/back-end/test/studentIssues.test.js @@ -3,9 +3,12 @@ import chai, { assert } from "chai"; import IssueModel from "../models/issueModel.js"; import chaiHttp from "chai-http"; import server from "../app.js"; + chai.use(chaiHttp); -// Integration tests for the studentIssuesHandler.js file +process.env.NODE_ENV = "test"; + +// Integration tests for the studentIssues.js file describe("Integration Tests for Student Issue Handler Endpoint", () => { describe("GET /api/issues/student/:paramName", () => { it("should retrieve all issues for a valid student NetID", async () => { @@ -32,6 +35,34 @@ describe("Integration Tests for Student Issue Handler Endpoint", () => { assert.equal(res.text, "User does not exist."); }); }); + + describe("GET /api/issues/student/:studentNetID/:paramName", () => { + it("should retrieve all issues for a valid student NetID and issue index", async () => { + const studentNetID = "student"; + const paramName = "101"; + const res = await chai + .request(server) + .get(`/api/issues/student/${studentNetID}/${paramName}`); + // Check that the response is correct + assert.equal(res.status, 200); + // Check that the response is an array + assert.isArray(res.body); + // Check that the response is the same length as the number of issues + const userIssues = await IssueModel.find({ "studentNetID": studentNetID, "index": paramName }); + // Check that the response is the same length as the number of issues of that user + assert.equal(res.body.length, userIssues.length); + }); + + it("should handle errors gracefully for an invalid issue index", async () => { + const studentNetID = "student"; + const paramName = "01"; + const res = await chai + .request(server) + .get(`/api/issues/student/${studentNetID}/${paramName}`); + assert.equal(res.status, 500); + assert.equal(res.text, "No issues found for the given studentNetID and index."); + }); + }); }); /* eslint-enable */ diff --git a/front-end/src/components/student/StudentIssueOverlay/DesktopIssueDetails.css b/front-end/src/components/student/StudentIssueOverlay/StudentIssueDetails.css similarity index 100% rename from front-end/src/components/student/StudentIssueOverlay/DesktopIssueDetails.css rename to front-end/src/components/student/StudentIssueOverlay/StudentIssueDetails.css diff --git a/front-end/src/components/student/StudentIssueOverlay/DesktopIssueDetails.js b/front-end/src/components/student/StudentIssueOverlay/StudentIssueDetails.js similarity index 95% rename from front-end/src/components/student/StudentIssueOverlay/DesktopIssueDetails.js rename to front-end/src/components/student/StudentIssueOverlay/StudentIssueDetails.js index 2b095e4..af9e1f0 100644 --- a/front-end/src/components/student/StudentIssueOverlay/DesktopIssueDetails.js +++ b/front-end/src/components/student/StudentIssueOverlay/StudentIssueDetails.js @@ -1,17 +1,13 @@ import { useState, useEffect } from 'react'; import axios from 'axios'; -import './DesktopIssueDetails.css'; +import './StudentIssueDetails.css'; -const DesktopIssueDetails = ({ index }) => { +const StudentIssueDetails = ({ studentNetID, index }) => { const [issue, setIssue] = useState(null); const [comment, setComment] = useState(''); // const [comments, setComments] = useState([]); // Assuming comments is an array const [changeOccured, setChangeOccured] = useState(false); // To force a re-render const BACKEND_BASE_URL = process.env.REACT_APP_BACKEND_URL; - const mockStudent = { - name: "Ted Mosby", - netid: "tm2005" - }; const handleCommentChange = (event) => { setComment(event.target.value); @@ -23,7 +19,7 @@ const DesktopIssueDetails = ({ index }) => { try { const response = await axios .post( - `${BACKEND_BASE_URL}/api/actions/student/${mockStudent.netid}/${index}`, + `${BACKEND_BASE_URL}/api/actions/student/${studentNetID}/${index}`, { issueindex: index, comments: comment @@ -49,7 +45,7 @@ const DesktopIssueDetails = ({ index }) => { const postMarkAsResolved = async () => { try { await axios.post( - `${BACKEND_BASE_URL}/api/actions/student/${mockStudent.netid}/${index}`, + `${BACKEND_BASE_URL}/api/actions/student/${studentNetID}/${index}`, { issueindex: index, currentStatus: "Resolved" @@ -62,7 +58,7 @@ const DesktopIssueDetails = ({ index }) => { const postReopen = async (event) => { try { await axios.post( - `${BACKEND_BASE_URL}/api/actions/student/${mockStudent.netid}/${index}`, + `${BACKEND_BASE_URL}/api/actions/student/${studentNetID}/${index}`, { issueindex: index, currentStatus: "Open" @@ -92,7 +88,7 @@ const DesktopIssueDetails = ({ index }) => { try { // Attempt to make an HTTP GET request using axios. const response = await axios.get( - `${BACKEND_BASE_URL}/api/issues/student/${mockStudent.netid}/${index}` + `${BACKEND_BASE_URL}/api/issues/student/${studentNetID}/${index}` ); // If the request is successful, take the first item from the response data // (assuming the response data is an array) and update the `issue` state with it. @@ -274,4 +270,4 @@ const DesktopIssueDetails = ({ index }) => { ); }; -export default DesktopIssueDetails; +export default StudentIssueDetails; diff --git a/front-end/src/layouts/StudentDashboard/StudentDashboard.js b/front-end/src/layouts/StudentDashboard/StudentDashboard.js index 9354a80..3ed9ba6 100644 --- a/front-end/src/layouts/StudentDashboard/StudentDashboard.js +++ b/front-end/src/layouts/StudentDashboard/StudentDashboard.js @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useContext } from "react"; import "./StudentDashboard.css"; import StudentNavbar from "../../components/student/StudentNavbar/StudentNavbar"; import StudentViewFilter from "../../components/student/StudentViewFilter/StudentViewFilter"; -import DesktopIssueDetails from "../../components/student/StudentIssueOverlay/DesktopIssueDetails"; +import StudentIssueDetails from "../../components/student/StudentIssueOverlay/StudentIssueDetails"; import SiteWideFooter from "../../components/general/SiteWideFooter/SiteWideFooter"; import { CreateRequest } from "../../components/student/CreateRequest/CreateRequest.js"; import { AuthContext } from '../../components/general/AuthContext/AuthContext'; @@ -617,7 +617,7 @@ const StudentDashboard = () => { X - + )}