Skip to content
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ jobs:
name: Install Package Dependencies
command: |
sudo apt-get update
sudo apt-get install -y graphviz
sudo apt-get install -y texlive-latex-base
sudo wget https://imagemagick.org/archive/binaries/magick
sudo cp magick /usr/local/bin
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
### 🐛 Bug fixes

- Fixed a bug that was causing the focus info popup to appear blank
- Fixed a bug on the generate page causing extraneous ellipses to appear when hovering over a course to highlight its prerequisites

### 🔧 Internal changes

Expand Down
6 changes: 4 additions & 2 deletions app/Svg/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,12 @@ parseGraph key tags =
small shape = shapeWidth shape < 300
removeRedundant shapes =
filter (not . \s -> shapePos s `elem` map shapePos shapes &&
(T.null (shapeFill s) || shapeFill s == "#000000") &&
isEdge s &&
elem (shapeType_ s) [Node, Hybrid]) shapes


-- | Determine if the input shape is an edge.
isEdge :: Shape -> Bool
isEdge shape = T.null (shapeFill shape) || shapeFill shape == "black" || shapeFill shape == "#000000"

-- | Create text values from g tags.
-- This searches for nested tspan tags inside text tags using a recursive
Expand Down
70 changes: 70 additions & 0 deletions backend-test/Controllers/GenerateControllerTests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{-|
Description: Generate Controller module tests.

Module that contains the tests for the functions in the Generate Controller module.

-}

module Controllers.GenerateControllerTests
( test_generateController
) where

import Config (runDb)
import Controllers.Generate (findAndSavePrereqsResponse)
import Data.Aeson (Value (..), decode)
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Lazy as BSL
import Data.Foldable (toList)
import qualified Data.Text as T
import Database.Persist.Sqlite (SqlPersistM, insert_)
import Database.Tables (Courses (..))
import Happstack.Server (rsBody)
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (assertEqual, testCase)
import TestHelpers (clearDatabase, runServerPartWithGraphGenerate, withDatabase)

-- | Helper function to insert courses into the database
insertCoursesWithPrerequisites :: [(T.Text, Maybe T.Text)] -> SqlPersistM ()
insertCoursesWithPrerequisites = mapM_ insertCourse
where
insertCourse (code, prereqString) = insert_ (Courses code Nothing Nothing Nothing prereqString Nothing Nothing Nothing Nothing [])

-- | List of test cases as (input course, course/prereq structure, JSON payload, expected # of nodes in prereq graph)
findAndSavePrereqsResponseTestCases :: [(String, [(T.Text, Maybe T.Text)], BSL.ByteString, Integer)]
findAndSavePrereqsResponseTestCases =
[("CSC148H1",
[("CSC108H1", Nothing), ("CSC148H1", Just "CSC108H1")],
"{\"courses\":[\"CSC148H1\"],\"programs\":[],\"graphOptions\":{\"taken\":[],\"departments\":[\"CSC\",\"MAT\",\"STA\"]}}",
2
)]

-- | Run a test case (input course, course/prereq structure, JSON payload, expected # of nodes) on the findAndSavePrereqsResponse function.
runfindAndSavePrereqsResponseTest :: String -> [(T.Text, Maybe T.Text)] -> BSL.ByteString -> Integer -> TestTree
runfindAndSavePrereqsResponseTest course graphStructure payload expected =
testCase course $ do
runDb $ do
clearDatabase
insertCoursesWithPrerequisites graphStructure
response <- runServerPartWithGraphGenerate Controllers.Generate.findAndSavePrereqsResponse payload
-- Take the response and extract the number of nodes (courses) within the generated graph, then assert that it is equal to the expected value.
let body = rsBody response
Just (Object object) = decode body
Just (Array shapes) = KM.lookup (K.fromString "shapes") object
actual =
fromIntegral . length $ filter isNode (toList shapes)

assertEqual ("Unexpected response for " ++ course) expected actual

where
isNode (Object object) =
KM.lookup (K.fromString "type_") object == Just (String "Node")
isNode _ = False

-- | Run all the findAndSavePrereqsResponse test cases
runfindAndSavePrereqsResponseTests :: [TestTree]
runfindAndSavePrereqsResponseTests = map (\(course, courseStructure, payload, expectedNodes) -> runfindAndSavePrereqsResponseTest course courseStructure payload expectedNodes) findAndSavePrereqsResponseTestCases

-- | Test suite for Generate Controller Module
test_generateController :: TestTree
test_generateController = withDatabase "Generate Controller tests" runfindAndSavePrereqsResponseTests
44 changes: 37 additions & 7 deletions backend-test/TestHelpers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ module TestHelpers
releaseDatabase,
runServerPartWithQuery,
runServerPartWithCourseInfoQuery,
runServerPartWithGraphGenerate,
withDatabase)
where

import Config (databasePath)
import Control.Concurrent.MVar (newEmptyMVar, newMVar)
import qualified Data.ByteString.Lazy.Char8 as BSL
import Control.Concurrent.MVar (newEmptyMVar, newMVar, putMVar)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.Char8 as BSL8
import qualified Data.Map as Map
import Data.Text (unpack)
import Database.Database (setupDatabase)
import Database.Persist.Sqlite (Filter, SqlPersistM, deleteWhere)
import Database.Tables
import Happstack.Server (ContentType (..), HttpVersion (..), Input (..), Method (GET), Request (..),
Response, ServerPart, inputContentType, inputFilename, inputValue,
simpleHTTP'')
import Happstack.Server (ContentType (..), HttpVersion (..), Input (..), Method (GET, PUT),
Request (..), Response, RqBody (..), ServerPart, inputContentType,
inputFilename, inputValue, simpleHTTP'')
import System.Directory (removeFile)
import System.Environment (setEnv, unsetEnv)
import Test.Tasty (TestTree, testGroup, withResource)
Expand Down Expand Up @@ -69,7 +71,7 @@ mockRequestWithQuery courseName = do
, rqUri = "/course"
, rqQuery = ""
, rqInputsQuery = [("name", Input {
inputValue = Right (BSL.pack courseName),
inputValue = Right (BSL8.pack courseName),
inputFilename = Nothing,
inputContentType = defaultContentType
})]
Expand All @@ -93,7 +95,7 @@ mockRequestWithCourseInfoQuery dept = do
, rqUri = "/course-info"
, rqQuery = ""
, rqInputsQuery = [("dept", Input {
inputValue = Right (BSL.pack dept),
inputValue = Right (BSL8.pack dept),
inputFilename = Nothing,
inputContentType = defaultContentType
})]
Expand All @@ -105,6 +107,28 @@ mockRequestWithCourseInfoQuery dept = do
, rqPeer = ("127.0.0.1", 0)
}

-- | A mock request for the graph generate route, specifically for findAndSavePrereqsResponse
mockRequestWithGraphGenerate :: BSL.ByteString -> IO Request
mockRequestWithGraphGenerate payload = do
inputsBody <- newMVar []
requestBody <- newEmptyMVar
putMVar requestBody (Body payload)

return Request
{ rqSecure = False
, rqMethod = PUT
, rqPaths = ["graph-generate"]
, rqUri = "/graph-generate"
, rqQuery = ""
, rqInputsQuery = []
, rqInputsBody = inputsBody
, rqCookies = []
, rqVersion = HttpVersion 1 1
, rqHeaders = Map.empty
, rqBody = requestBody
, rqPeer = ("127.0.0.1", 0)
}

-- | Default content type for the MockRequestWithQuery, specifically for retrieveCourse
defaultContentType :: ContentType
defaultContentType = ContentType
Expand All @@ -125,6 +149,12 @@ runServerPartWithCourseInfoQuery sp dept = do
request <- mockRequestWithCourseInfoQuery dept
simpleHTTP'' sp request

-- | Helper function to run ServerPartWithGraphGenerate for findAndSavePrereqsResponse
runServerPartWithGraphGenerate :: ServerPart Response -> BSL.ByteString -> IO Response
runServerPartWithGraphGenerate sp payload = do
request <- mockRequestWithGraphGenerate payload
simpleHTTP'' sp request

-- | Clear all the entries in the database
clearDatabase :: SqlPersistM ()
clearDatabase = do
Expand Down
8 changes: 8 additions & 0 deletions courseography.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ library
exposed-modules:
Config,
Controllers.Course,
Controllers.Generate,
Controllers.Graph,
Css.Constants,
Database.CourseInsertion,
Expand All @@ -33,7 +34,11 @@ library
Database.DataType,
Database.Requirement,
Database.Tables,
DynamicGraphs.CourseFinder,
DynamicGraphs.GraphGenerator,
DynamicGraphs.GraphNodeUtils,
DynamicGraphs.GraphOptions,
DynamicGraphs.WriteRunDot,
Export.GetImages,
Export.ImageConversion,
Export.TimetableImageCreator,
Expand Down Expand Up @@ -68,6 +73,8 @@ library
directory,
diagrams-lib,
diagrams-svg,
filepath,
graphviz,
happstack-server,
hlint,
hslogger,
Expand Down Expand Up @@ -105,6 +112,7 @@ test-suite Tests
main-is: Main.hs
other-modules:
Controllers.CourseControllerTests,
Controllers.GenerateControllerTests,
Controllers.GraphControllerTests,
Database.CourseQueriesTests,
RequirementTests.ModifierTests,
Expand Down