diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a7dcf3a..5cada3765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Updated backend tests to use `tasty-discover` - Added documentation for running a subset of the backend tests - Deleted `app/Response/Image` file and refactored `app/Util/Helpers` to include `returnImageData` +- Added test cases for the retrieveProgram function in `Controllers/Program` ## [0.7.1] - 2025-06-16 diff --git a/backend-test/Controllers/ProgramControllerTests.hs b/backend-test/Controllers/ProgramControllerTests.hs index eb940fe15..79f6d35ea 100644 --- a/backend-test/Controllers/ProgramControllerTests.hs +++ b/backend-test/Controllers/ProgramControllerTests.hs @@ -1,10 +1,17 @@ +{-| +Description: Program Controller module tests. + +Module that contains the tests for the functions in the Program Controller module. +-} + module Controllers.ProgramControllerTests ( test_programController ) where import Config (runDb) +import Controllers.Program (index, retrieveProgram) import Control.Monad.IO.Class (liftIO) -import Controllers.Program (index) +import Data.Aeson (FromJSON (parseJSON), decode, withObject, (.:)) import qualified Data.ByteString.Lazy.Char8 as BL import qualified Data.Text as T import Data.Time (getCurrentTime) @@ -13,8 +20,74 @@ import Database.Persist.Sqlite (SqlPersistM, insert_) import Database.Tables (Post (..)) import Happstack.Server (rsBody) import Test.Tasty (TestTree) -import Test.Tasty.HUnit (assertEqual, testCase) -import TestHelpers (clearDatabase, runServerPart, withDatabase) +import Test.Tasty.HUnit (assertBool, assertEqual, testCase) +import TestHelpers (clearDatabase, runServerPart, runServerPartWithProgramQuery, withDatabase) + +-- | A Post response without timestamps (for comparison purposes) +data PostResponseNoTime = PostResponseNoTime + { postCode :: T.Text + , postDepartment :: T.Text + , postDescription :: T.Text + , postName :: T.Text + , postRequirements :: T.Text + } deriving (Show, Eq) + +instance FromJSON PostResponseNoTime where + parseJSON = withObject "Expected Object for Post" $ \o -> do + code <- o .: "postCode" + dept <- o .: "postDepartment" + desc <- o .: "postDescription" + name <- o .: "postName" + reqs <- o .: "postRequirements" + return $ PostResponseNoTime code dept desc name reqs + +-- | List of test cases as (label, programs to insert, query params, expected output) +retrieveprogramTestCases :: [(String, [T.Text], T.Text, String)] +retrieveprogramTestCases = + [ ("Valid program code returns JSON" + , ["ASMAJ1689"] + , "ASMAJ1689" + , "{\"postCode\":\"ASMAJ1689\",\"postDepartment\":\"test\",\"postDescription\":\"test\",\"postName\":\"Other\",\"postRequirements\":\"test\"}" + ) + , ("Invalid program code returns null JSON" + , [] + , "INVALID123" + , "null" + ) + , ("Empty code parameter returns null" + , [] + , "" + , "null" + ) + ] + +-- | Parse response and extract non-timestamp fields +parsePostResponse :: String -> Maybe PostResponseNoTime +parsePostResponse jsonStr = decode (BL.pack jsonStr) + +-- | Run a test case (case, input, expected output) on the retrieveProgram function. +runRetrieveProgramTest :: String -> [T.Text] -> T.Text -> String -> TestTree +runRetrieveProgramTest label posts queryParam expected = + testCase label $ do + runDb $ do + clearDatabase + insertPrograms posts + + response <- runServerPartWithProgramQuery Controllers.Program.retrieveProgram (T.unpack queryParam) + let actual = BL.unpack $ rsBody response + let parsedActual = parsePostResponse actual + let parsedExpected = parsePostResponse expected + + assertBool + ("JSON mismatch for " ++ label ++ + "\nExpected: " ++ show parsedExpected ++ + "\nActual: " ++ show parsedActual ++ + "\nRaw JSON: " ++ actual) + (parsedActual == parsedExpected) + +-- | Run all the retrieveProgram test cases +runRetrieveProgramTests :: [TestTree] +runRetrieveProgramTests = map (\(label, programs, params, expected) -> runRetrieveProgramTest label programs params expected) retrieveprogramTestCases -- | List of test cases as (label, input programs, expected output) indexTestCases :: [(String, [T.Text], String)] @@ -51,4 +124,4 @@ runIndexTests = map (\(label, programs, expected) -> runIndexTest label programs -- | Test suite for Program Controller Module test_programController :: TestTree -test_programController = withDatabase "Program Controller tests" runIndexTests +test_programController = withDatabase "Program Controller tests" (runIndexTests ++ runRetrieveProgramTests) diff --git a/backend-test/TestHelpers.hs b/backend-test/TestHelpers.hs index 20272f84b..74c3d425a 100644 --- a/backend-test/TestHelpers.hs +++ b/backend-test/TestHelpers.hs @@ -13,6 +13,7 @@ module TestHelpers releaseDatabase, runServerPartWithQuery, runServerPartWithCourseInfoQuery, + runServerPartWithProgramQuery, runServerPartWithGraphGenerate, withDatabase) where @@ -107,6 +108,30 @@ mockRequestWithCourseInfoQuery dept = do , rqPeer = ("127.0.0.1", 0) } +-- | A mock request for running ServerPartWithProgramQuery, specifically for retrieveProgram +mockRequestWithProgramQuery :: String -> IO Request +mockRequestWithProgramQuery programCode = do + inputsBody <- newMVar [] + requestBody <- newEmptyMVar + return Request + { rqSecure = False + , rqMethod = GET + , rqPaths = ["program"] + , rqUri = "/program" + , rqQuery = "" + , rqInputsQuery = [("code", Input { + inputValue = Right (BSL8.pack programCode), + inputFilename = Nothing, + inputContentType = defaultContentType + })] + , rqInputsBody = inputsBody + , rqCookies = [] + , rqVersion = HttpVersion 1 1 + , rqHeaders = Map.empty + , rqBody = requestBody + , 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 @@ -149,6 +174,12 @@ runServerPartWithCourseInfoQuery sp dept = do request <- mockRequestWithCourseInfoQuery dept simpleHTTP'' sp request +-- | Helper function to run ServerPartWithQuery Response for retrieveProgram +runServerPartWithProgramQuery :: ServerPart Response -> String -> IO Response +runServerPartWithProgramQuery sp programCode = do + request <- mockRequestWithProgramQuery programCode + simpleHTTP'' sp request + -- | Helper function to run ServerPartWithGraphGenerate for findAndSavePrereqsResponse runServerPartWithGraphGenerate :: ServerPart Response -> BSL.ByteString -> IO Response runServerPartWithGraphGenerate sp payload = do