Skip to content
Open
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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
81 changes: 77 additions & 4 deletions backend-test/Controllers/ProgramControllerTests.hs
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)]
Expand Down Expand Up @@ -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)
31 changes: 31 additions & 0 deletions backend-test/TestHelpers.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module TestHelpers
releaseDatabase,
runServerPartWithQuery,
runServerPartWithCourseInfoQuery,
runServerPartWithProgramQuery,
runServerPartWithGraphGenerate,
withDatabase)
where
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down