Skip to content

Commit 8afab66

Browse files
Refactored TestHelpers.hs to remove duplicate code (#1617)
1 parent 894d9e4 commit 8afab66

File tree

6 files changed

+51
-143
lines changed

6 files changed

+51
-143
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
- Added documentation for running a subset of the backend tests
3838
- Deleted `app/Response/Image` file and refactored `app/Util/Helpers` to include `returnImageData`
3939
- Added test cases for the retrieveProgram function in `Controllers/Program`
40+
- Removed duplicate code from `mockRequest` and `runServerPartWith` in `backend-test/TestHelpers.hs`
4041
- Initialized a SchemaVersion table for the purposes of running robust database migrations
4142

4243
## [0.7.1] - 2025-06-16

backend-test/Controllers/CourseControllerTests.hs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ import Database.Tables (Courses (..))
2121
import Happstack.Server (rsBody)
2222
import Test.Tasty (TestTree)
2323
import Test.Tasty.HUnit (assertEqual, testCase)
24-
import TestHelpers (clearDatabase, runServerPart, runServerPartWithCourseInfoQuery,
25-
runServerPartWithQuery, withDatabase)
24+
import TestHelpers (mockGetRequest, clearDatabase, runServerPart, runServerPartWith, withDatabase)
2625

2726
-- | List of test cases as (input course name, course data, expected JSON output)
2827
retrieveCourseTestCases :: [(String, T.Text, Map.Map T.Text T.Text, String)]
@@ -86,7 +85,7 @@ runRetrieveCourseTest label courseName courseData expected =
8685
unless (T.null currCourseName) $
8786
insert_ courseToInsert
8887

89-
response <- runServerPartWithQuery Controllers.Course.retrieveCourse (T.unpack courseName)
88+
response <- runServerPartWith Controllers.Course.retrieveCourse $ mockGetRequest "/course" [("name", T.unpack courseName)] ""
9089
let actual = BL.unpack $ rsBody response
9190
assertEqual ("Unexpected response body for " ++ label) expected actual
9291

@@ -193,7 +192,7 @@ runCourseInfoTest label state dept expected =
193192
runDb $ do
194193
clearDatabase
195194
mapM_ insert_ state
196-
response <- runServerPartWithCourseInfoQuery Controllers.Course.courseInfo (T.unpack dept)
195+
response <- runServerPartWith Controllers.Course.courseInfo $ mockGetRequest "/course-info" [("dept", T.unpack dept)] ""
197196
let actual = BL.unpack $ rsBody response
198197
assertEqual ("Unexpected response body for " ++ label) expected actual
199198

backend-test/Controllers/GenerateControllerTests.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import Database.Tables (Courses (..))
2222
import Happstack.Server (rsBody)
2323
import Test.Tasty (TestTree)
2424
import Test.Tasty.HUnit (assertEqual, testCase)
25-
import TestHelpers (clearDatabase, runServerPartWithGraphGenerate, withDatabase)
25+
import TestHelpers (mockPutRequest, clearDatabase, runServerPartWith, withDatabase)
2626

2727
-- | Helper function to insert courses into the database
2828
insertCoursesWithPrerequisites :: [(T.Text, Maybe T.Text)] -> SqlPersistM ()
@@ -54,7 +54,7 @@ runfindAndSavePrereqsResponseTest course graphStructure payload expectedNodes ex
5454
runDb $ do
5555
clearDatabase
5656
insertCoursesWithPrerequisites graphStructure
57-
response <- runServerPartWithGraphGenerate Controllers.Generate.findAndSavePrereqsResponse payload
57+
response <- runServerPartWith Controllers.Generate.findAndSavePrereqsResponse $ mockPutRequest "/graph-generate" [] payload
5858
-- Take the response and extract the number of nodes (courses) within the generated graph, then assert that it is equal to the expected value.
5959
let body = rsBody response
6060
Just (Object object) = decode body

backend-test/Controllers/ProgramControllerTests.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import Database.Tables (Post (..))
2121
import Happstack.Server (rsBody)
2222
import Test.Tasty (TestTree)
2323
import Test.Tasty.HUnit (assertEqual, testCase)
24-
import TestHelpers (clearDatabase, runServerPart, runServerPartWithProgramQuery, withDatabase)
24+
import TestHelpers (mockGetRequest, clearDatabase, runServerPart, runServerPartWith, withDatabase)
2525

2626
-- | A Post response without timestamps (for comparison purposes)
2727
data PostResponseNoTime = PostResponseNoTime
@@ -72,7 +72,7 @@ runRetrieveProgramTest label posts queryParam expected =
7272
runDb $ do
7373
clearDatabase
7474
insertPrograms posts
75-
response <- runServerPartWithProgramQuery Controllers.Program.retrieveProgram (T.unpack queryParam)
75+
response <- runServerPartWith Controllers.Program.retrieveProgram $ mockGetRequest "/code" [("code" ,T.unpack queryParam)] ""
7676
let actual = BL.unpack $ rsBody response
7777
let parsedActual = parsePostResponse actual
7878
assertEqual ("Unexpected JSON response body for" ++ label) expected parsedActual

backend-test/TestHelpers.hs

Lines changed: 42 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@ Module that contains helper functions used in testing controller module function
77

88
module TestHelpers
99
(acquireDatabase,
10-
mockRequest,
10+
mockGetRequest,
11+
mockPutRequest,
12+
runServerPartWith,
1113
runServerPart,
1214
clearDatabase,
1315
releaseDatabase,
14-
runServerPartWithQuery,
15-
runServerPartWithCourseInfoQuery,
16-
runServerPartWithProgramQuery,
17-
runServerPartWithGraphGenerate,
1816
withDatabase)
1917
where
2018

2119
import Config (databasePath)
2220
import Control.Concurrent.MVar (newEmptyMVar, newMVar, putMVar)
23-
import qualified Data.ByteString.Lazy as BSL
21+
import Control.Monad (when)
2422
import qualified Data.ByteString.Lazy.Char8 as BSL8
2523
import qualified Data.Map as Map
24+
import Data.List.Split (splitOn)
2625
import Data.Text (unpack)
2726
import Database.Database (setupDatabase)
2827
import Database.Persist.Sqlite (Filter, SqlPersistM, deleteWhere)
@@ -34,125 +33,49 @@ import System.Directory (removeFile)
3433
import System.Environment (setEnv, unsetEnv)
3534
import Test.Tasty (TestTree, testGroup, withResource)
3635

37-
-- | A minimal mock request for running a ServerPart
38-
mockRequest :: IO Request
39-
mockRequest = do
40-
inputsBody <- newMVar []
41-
requestBody <- newEmptyMVar
42-
return Request
43-
{ rqSecure = False
44-
, rqMethod = GET
45-
, rqPaths = []
46-
, rqUri = "/"
47-
, rqQuery = ""
48-
, rqInputsQuery = []
49-
, rqInputsBody = inputsBody
50-
, rqCookies = []
51-
, rqVersion = HttpVersion 1 1
52-
, rqHeaders = Map.empty
53-
, rqBody = requestBody
54-
, rqPeer = ("127.0.0.1", 0)
55-
}
36+
-- | Generalized function to create a mock request
37+
createMockRequest :: Method -> String -> [(String, String)] -> BSL8.ByteString -> IO Request
38+
createMockRequest reqMethod reqUri queryInputs body = do
39+
reqInputsBody <- newMVar []
40+
reqBody <- newEmptyMVar
5641

57-
-- | Helper function to run ServerPart Response
58-
runServerPart :: ServerPart Response -> IO Response
59-
runServerPart sp = do
60-
request <- mockRequest
61-
simpleHTTP'' sp request
62-
63-
-- | A mock request for running ServerPartWithQuery, specifically for retrieveCourse
64-
mockRequestWithQuery :: String -> IO Request
65-
mockRequestWithQuery courseName = do
66-
inputsBody <- newMVar []
67-
requestBody <- newEmptyMVar
42+
-- If a payload is provided, write it into the request body
43+
when (body /= "") $
44+
putMVar reqBody (Body body)
6845
return Request
6946
{ rqSecure = False
70-
, rqMethod = GET
71-
, rqPaths = ["course"]
72-
, rqUri = "/course"
47+
, rqMethod = reqMethod
48+
, rqPaths = splitPath reqUri
49+
, rqUri = reqUri
7350
, rqQuery = ""
74-
, rqInputsQuery = [("name", Input {
75-
inputValue = Right (BSL8.pack courseName),
76-
inputFilename = Nothing,
77-
inputContentType = defaultContentType
78-
})]
79-
, rqInputsBody = inputsBody
51+
, rqInputsQuery = map (fmap convertInput) queryInputs
52+
, rqInputsBody = reqInputsBody
8053
, rqCookies = []
8154
, rqVersion = HttpVersion 1 1
8255
, rqHeaders = Map.empty
83-
, rqBody = requestBody
56+
, rqBody = reqBody
8457
, rqPeer = ("127.0.0.1", 0)
8558
}
86-
87-
-- | A mock request for running ServerPartWithCourseInfoQuery, specifically for courseInfo
88-
mockRequestWithCourseInfoQuery :: String -> IO Request
89-
mockRequestWithCourseInfoQuery dept = do
90-
inputsBody <- newMVar []
91-
requestBody <- newEmptyMVar
92-
return Request
93-
{ rqSecure = False
94-
, rqMethod = GET
95-
, rqPaths = ["course-info"]
96-
, rqUri = "/course-info"
97-
, rqQuery = ""
98-
, rqInputsQuery = [("dept", Input {
99-
inputValue = Right (BSL8.pack dept),
100-
inputFilename = Nothing,
101-
inputContentType = defaultContentType
102-
})]
103-
, rqInputsBody = inputsBody
104-
, rqCookies = []
105-
, rqVersion = HttpVersion 1 1
106-
, rqHeaders = Map.empty
107-
, rqBody = requestBody
108-
, rqPeer = ("127.0.0.1", 0)
59+
where
60+
-- | Helper to convert a String to a query input parameter
61+
convertInput :: String -> Input
62+
convertInput paramValue = Input
63+
{ inputValue = Right (BSL8.pack paramValue)
64+
, inputFilename = Nothing
65+
, inputContentType = defaultContentType
10966
}
11067

111-
-- | A mock request for running ServerPartWithProgramQuery, specifically for retrieveProgram
112-
mockRequestWithProgramQuery :: String -> IO Request
113-
mockRequestWithProgramQuery programCode = do
114-
inputsBody <- newMVar []
115-
requestBody <- newEmptyMVar
116-
return Request
117-
{ rqSecure = False
118-
, rqMethod = GET
119-
, rqPaths = ["program"]
120-
, rqUri = "/program"
121-
, rqQuery = ""
122-
, rqInputsQuery = [("code", Input {
123-
inputValue = Right (BSL8.pack programCode),
124-
inputFilename = Nothing,
125-
inputContentType = defaultContentType
126-
})]
127-
, rqInputsBody = inputsBody
128-
, rqCookies = []
129-
, rqVersion = HttpVersion 1 1
130-
, rqHeaders = Map.empty
131-
, rqBody = requestBody
132-
, rqPeer = ("127.0.0.1", 0)
133-
}
68+
-- | Split a URI into path segments
69+
splitPath :: String -> [String]
70+
splitPath uri = splitOn "/" uri
13471

135-
-- | A mock request for the graph generate route, specifically for findAndSavePrereqsResponse
136-
mockRequestWithGraphGenerate :: BSL.ByteString -> IO Request
137-
mockRequestWithGraphGenerate payload = do
138-
inputsBody <- newMVar []
139-
requestBody <- newEmptyMVar
140-
putMVar requestBody (Body payload)
72+
-- | Curried version of createMockRequest for GET requests
73+
mockGetRequest :: String -> [(String, String)] -> BSL8.ByteString -> IO Request
74+
mockGetRequest = createMockRequest GET
14175

142-
return Request
143-
{ rqSecure = False
144-
, rqMethod = PUT
145-
, rqPaths = ["graph-generate"]
146-
, rqUri = "/graph-generate"
147-
, rqQuery = ""
148-
, rqInputsQuery = []
149-
, rqInputsBody = inputsBody
150-
, rqCookies = []
151-
, rqVersion = HttpVersion 1 1
152-
, rqHeaders = Map.empty
153-
, rqBody = requestBody
154-
, rqPeer = ("127.0.0.1", 0)
155-
}
76+
-- | Curried version of createMockRequest for PUT requests
77+
mockPutRequest :: String -> [(String, String)] -> BSL8.ByteString -> IO Request
78+
mockPutRequest = createMockRequest PUT
15679

15780
-- | Default content type for the MockRequestWithQuery, specifically for retrieveCourse
15881
defaultContentType :: ContentType
@@ -162,29 +85,13 @@ defaultContentType = ContentType
16285
, ctParameters = []
16386
}
16487

165-
-- | Helper function to run ServerPartWithQuery Response
166-
runServerPartWithQuery :: ServerPart Response -> String -> IO Response
167-
runServerPartWithQuery sp courseName = do
168-
request <- mockRequestWithQuery courseName
169-
simpleHTTP'' sp request
170-
171-
-- | Helper function to run ServerPartWithQuery Response for courseInfo
172-
runServerPartWithCourseInfoQuery :: ServerPart Response -> String -> IO Response
173-
runServerPartWithCourseInfoQuery sp dept = do
174-
request <- mockRequestWithCourseInfoQuery dept
175-
simpleHTTP'' sp request
176-
177-
-- | Helper function to run ServerPartWithQuery Response for retrieveProgram
178-
runServerPartWithProgramQuery :: ServerPart Response -> String -> IO Response
179-
runServerPartWithProgramQuery sp programCode = do
180-
request <- mockRequestWithProgramQuery programCode
181-
simpleHTTP'' sp request
182-
183-
-- | Helper function to run ServerPartWithGraphGenerate for findAndSavePrereqsResponse
184-
runServerPartWithGraphGenerate :: ServerPart Response -> BSL.ByteString -> IO Response
185-
runServerPartWithGraphGenerate sp payload = do
186-
request <- mockRequestWithGraphGenerate payload
187-
simpleHTTP'' sp request
88+
-- | Run a 'ServerPart' with a custom request.
89+
runServerPartWith :: ServerPart Response -> IO Request -> IO Response
90+
runServerPartWith sp reqIO = simpleHTTP'' sp =<< reqIO
91+
92+
-- Run a 'ServerPart' with GET request for testing wihtout query parameters.
93+
runServerPart :: ServerPart Response -> IO Response
94+
runServerPart sp = runServerPartWith sp (mockGetRequest "/" [] "")
18895

18996
-- | Clear all the entries in the database
19097
clearDatabase :: SqlPersistM ()

courseography.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ test-suite Tests
135135
parsec,
136136
persistent-sqlite,
137137
QuickCheck,
138+
split,
138139
tagsoup,
139140
tasty >=1.5.3,
140141
tasty-discover,

0 commit comments

Comments
 (0)