Skip to content
Draft
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### 🚨 Breaking changes

- Added 404 status code return to `retrieveCourse` in `Controllers/Course`

### ✨ New features/enhancements

### 🐛 Bug fixes
Expand All @@ -14,6 +16,7 @@

- Refactor GraphDropdown component from being a child of Graph to being a child of NavBar
- Added test cases for the saveGraphJSON function in `Controllers/Graph`
- Remove unused variable from `Graph.js`

## [0.7.2] - 2025-12-10

Expand Down
6 changes: 4 additions & 2 deletions app/Controllers/Course.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import qualified Data.Text as T (Text, unlines)
import Database.Persist (Entity)
import Database.Persist.Sqlite (SqlPersistM, entityVal, selectList)
import Database.Tables as Tables (Courses, coursesCode)
import Happstack.Server (Response, ServerPart, lookText', toResponse)
import Happstack.Server (Response, ServerPart, lookText', notFound, ok, toResponse)
import Models.Course (getDeptCourses, returnCourse)
import Util.Happstack (createJSONResponse)

Expand All @@ -17,7 +17,9 @@ retrieveCourse :: ServerPart Response
retrieveCourse = do
name <- lookText' "name"
courses <- liftIO $ returnCourse name
return $ createJSONResponse courses
case courses of
Just x -> ok $ createJSONResponse x
Nothing -> notFound $ toResponse ("Course not found" :: String)

-- | Builds a list of all course codes in the database.
index :: ServerPart Response
Expand Down
30 changes: 18 additions & 12 deletions backend-test/Controllers/CourseControllerTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import Data.Maybe (fromMaybe)
import qualified Data.Text as T
import Database.Persist.Sqlite (SqlPersistM, insert_)
import Database.Tables (Courses (..))
import Happstack.Server (rsBody)
import Happstack.Server (rsBody, rsCode)
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (assertEqual, testCase)
import TestHelpers (mockGetRequest, clearDatabase, runServerPart, runServerPartWith, withDatabase)
import TestHelpers (clearDatabase, mockGetRequest, runServerPart, runServerPartWith, withDatabase)

-- | List of test cases as (input course name, course data, expected JSON output)
retrieveCourseTestCases :: [(String, T.Text, Map.Map T.Text T.Text, String)]
-- | List of test cases as (input course name, course data, status code, expected JSON output)
retrieveCourseTestCases :: [(String, T.Text, Map.Map T.Text T.Text, Int, String)]
retrieveCourseTestCases =
[ ("Course exists",
"STA238",
Expand All @@ -40,25 +40,28 @@ retrieveCourseTestCases =
("coreqs", "CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance."),
("videoUrls", "https://example.com/video1, https://example.com/video2")
],
200,
"{\"allMeetingTimes\":[],\"breadth\":null,\"coreqs\":\"CSC108H1/ CSC110Y1/ CSC148H1 *Note: the corequisite may be completed either concurrently or in advance.\",\"description\":\"An introduction to statistical inference and practice. Statistical models and parameters, estimators of parameters and their statistical properties, methods of estimation, confidence intervals, hypothesis testing, likelihood function, the linear model. Use of statistical computation for data analysis and simulation.\",\"distribution\":null,\"exclusions\":\"ECO220Y1/ ECO227Y1/ GGR270H1/ PSY201H1/ SOC300H1/ SOC202H1/ SOC252H1/ STA220H1/ STA221H1/ STA255H1/ STA248H1/ STA261H1/ STA288H1/ EEB225H1/ STAB22H3/ STAB27H3/ STAB57H3/ STA220H5/ STA221H5/ STA258H5/ STA260H5/ ECO220Y5/ ECO227Y5\",\"name\":\"STA238H1\",\"prereqString\":\"STA237H1/ STA247H1/ STA257H1/ STAB52H3/ STA256H5\",\"title\":\"Probability, Statistics and Data Analysis II\",\"videoUrls\":[\"https://example.com/video1\",\"https://example.com/video2\"]}"
),

("Course does not exist",
"STA238",
Map.empty,
"null"
404,
"Course not found"
),

("No course provided",
"",
Map.empty,
"null"
404,
"Course not found"
)
]

-- | Run a test case (case, input, expected output) on the retrieveCourse function.
runRetrieveCourseTest :: String -> T.Text -> Map.Map T.Text T.Text -> String -> TestTree
runRetrieveCourseTest label courseName courseData expected =
-- | Run a test case (case, input, expected status code, expected output) on the retrieveCourse function.
runRetrieveCourseTest :: String -> T.Text -> Map.Map T.Text T.Text -> Int -> String -> TestTree
runRetrieveCourseTest label courseName courseData expectedCode expectedBody =
testCase label $ do
let currCourseName = fromMaybe "" $ Map.lookup "name" courseData

Expand Down Expand Up @@ -86,12 +89,15 @@ runRetrieveCourseTest label courseName courseData expected =
insert_ courseToInsert

response <- runServerPartWith Controllers.Course.retrieveCourse $ mockGetRequest "/course" [("name", T.unpack courseName)] ""
let actual = BL.unpack $ rsBody response
assertEqual ("Unexpected response body for " ++ label) expected actual
let statusCode = rsCode response
assertEqual ("Unexpected status code for " ++ label) expectedCode statusCode

let actualBody = BL.unpack $ rsBody response
assertEqual ("Unexpected response body for " ++ label) expectedBody actualBody

-- | Run all the retrieveCourse test cases
runRetrieveCourseTests :: [TestTree]
runRetrieveCourseTests = map (\(label, courseName, courseData, expected) -> runRetrieveCourseTest label courseName courseData expected) retrieveCourseTestCases
runRetrieveCourseTests = map (\(label, courseName, courseData, expectedCode, expectedBody) -> runRetrieveCourseTest label courseName courseData expectedCode expectedBody) retrieveCourseTestCases

-- | Helper function to insert courses into the database
insertCourses :: [T.Text] -> SqlPersistM ()
Expand Down
10 changes: 10 additions & 0 deletions js/components/common/react_modal.js.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,16 @@ class CourseModal extends React.Component {
})
} else if (prevState.courseId !== this.state.courseId) {
getCourse(this.state.courseId).then(course => {
if (!course) {
this.setState({
course: {},
sessions: {},
courseTitle: "Course Not Found",
})
console.error(`Course with code ${this.state.courseId} not found`)
return
}

const newCourse = {
...course,
description: this.convertToLink(course.description),
Expand Down
15 changes: 13 additions & 2 deletions js/components/common/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
/**
* Retrieves a course from file.
* @param {string} courseName The course code. This + '.txt' is the name of the file.
* @returns {Promise} Promise object representing the JSON object containing course information.
* @returns {Promise} Promise object representing the JSON object containing course information
* or null if not found.
*/
export function getCourse(courseName) {
"use strict"

return fetch("course?name=" + courseName)
.then(response => response.json())
.then(response => {
if (response.status === 404) {
return null
}

if (!response.ok) {
throw new Error(`Failed to fetch course with name ${courseName}`)
}

return response.json()
})
.catch(error => {
throw error
})
Expand Down
3 changes: 0 additions & 3 deletions js/components/graph/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ const ZOOM_ENUM = {
ZOOM_OUT: -1,
ZOOM_IN: 1,
}
const TIMEOUT_NAMES_ENUM = {
INFOBOX: 0
}

export class Graph extends React.Component {
constructor(props) {
Expand Down
7 changes: 7 additions & 0 deletions js/components/grid/course_panel.js.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ function Course(props) {
S: [],
Y: [],
}

if (!data) {
setCourseInfo(course)
console.error(`Course with code ${props.courseCode} not found`)
return
}

course.courseCode = data.name

const parsedLectures = parseLectures(data.allMeetingTimes)
Expand Down