@@ -7,6 +7,7 @@ Defines 'MockSqlQueryT', which one can use in tests in order to mock out
7
7
8
8
{-# LANGUAGE GADTs #-}
9
9
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
10
+ {-# LANGUAGE LambdaCase #-}
10
11
{-# LANGUAGE RankNTypes #-}
11
12
{-# LANGUAGE ScopedTypeVariables #-}
12
13
{-# LANGUAGE TypeApplications #-}
@@ -17,16 +18,29 @@ module Database.Persist.Monad.TestUtils
17
18
, withRecord
18
19
, mockQuery
19
20
, MockQuery
21
+ -- * Specialized helpers
22
+ , mockSelectSource
23
+ , mockSelectKeys
24
+ , mockWithRawQuery
25
+ , mockRawQuery
26
+ , mockRawSql
20
27
) where
21
28
29
+ import Conduit ((.|) )
30
+ import qualified Conduit
22
31
import Control.Monad (msum )
23
32
import Control.Monad.IO.Class (MonadIO (.. ))
24
33
import Control.Monad.Reader (ReaderT , ask , runReaderT )
25
34
import Control.Monad.Trans.Resource (MonadResource )
35
+ import qualified Data.Acquire as Acquire
36
+ import Data.Text (Text )
37
+ import qualified Data.Text as Text
26
38
import Data.Typeable (Typeable , eqT , (:~:) (.. ))
39
+ import Database.Persist.Sql
40
+ (Entity , Filter , Key , PersistValue , SelectOpt , rawSqlProcessRow )
27
41
28
42
import Database.Persist.Monad.Class (MonadSqlQuery (.. ))
29
- import Database.Persist.Monad.SqlQueryRep (SqlQueryRep )
43
+ import Database.Persist.Monad.SqlQueryRep (SqlQueryRep ( .. ) )
30
44
31
45
-- | A monad transformer for testing functions that use 'MonadSqlQuery'.
32
46
newtype MockSqlQueryT m a = MockSqlQueryT
@@ -43,11 +57,10 @@ newtype MockSqlQueryT m a = MockSqlQueryT
43
57
--
44
58
-- When a database query is executed, the first mock that returns a 'Just' is
45
59
-- returned. If no mocks match the query, an error is thrown. See 'SqlQueryRep'
46
- -- for the constructors available to match against. Use `withRecord` to only
47
- -- match queries against a specific @record@ type
48
- -- (e.g. 'Database.Persist.Monad.Shim.selectList'), or `mockQuery` to match
49
- -- queries that don't reference a specific @record@ type
50
- -- (e.g. 'Database.Persist.Monad.Shim.rawSql').
60
+ -- for the constructors available to match against. Most of the time, you'll
61
+ -- want to use 'withRecord' to only match queries against a specific @record@
62
+ -- type (e.g. only match 'Database.Persist.Monad.Shim.selectList' calls for
63
+ -- the @Person@ entity).
51
64
--
52
65
-- Usage:
53
66
--
@@ -71,10 +84,10 @@ newtype MockSqlQueryT m a = MockSqlQueryT
71
84
runMockSqlQueryT :: MockSqlQueryT m a -> [MockQuery ] -> m a
72
85
runMockSqlQueryT action mockQueries = (`runReaderT` mockQueries) . unMockSqlQueryT $ action
73
86
74
- instance Monad m => MonadSqlQuery (MockSqlQueryT m ) where
87
+ instance MonadIO m => MonadSqlQuery (MockSqlQueryT m ) where
75
88
runQueryRep rep = do
76
89
mockQueries <- MockSqlQueryT ask
77
- maybe (error $ " Could not find mock for query: " ++ show rep) return
90
+ maybe (error $ " Could not find mock for query: " ++ show rep) liftIO
78
91
$ msum $ map tryMockQuery mockQueries
79
92
where
80
93
tryMockQuery (MockQuery f) = f rep
@@ -83,8 +96,8 @@ instance Monad m => MonadSqlQuery (MockSqlQueryT m) where
83
96
84
97
-- | A mocked query to use in 'runMockSqlQueryT'.
85
98
--
86
- -- Use 'withRecord' or 'mockQuery' to create a 'MockQuery'.
87
- data MockQuery = MockQuery (forall record a . Typeable record => SqlQueryRep record a -> Maybe a )
99
+ -- Use 'withRecord' or another helper to create a 'MockQuery'.
100
+ data MockQuery = MockQuery (forall record a . Typeable record => SqlQueryRep record a -> Maybe ( IO a ) )
88
101
89
102
-- | A helper for defining a mocked database query against a specific @record@
90
103
-- type. Designed to be used with TypeApplications.
@@ -110,13 +123,113 @@ data MockQuery = MockQuery (forall record a. Typeable record => SqlQueryRep reco
110
123
withRecord :: forall record . Typeable record => (forall a . SqlQueryRep record a -> Maybe a ) -> MockQuery
111
124
withRecord f = MockQuery $ \ (rep :: SqlQueryRep someRecord result ) ->
112
125
case eqT @ record @ someRecord of
113
- Just Refl -> f rep
126
+ Just Refl -> pure <$> f rep
114
127
Nothing -> Nothing
115
128
116
129
-- | A helper for defining a mocked database query.
117
130
--
118
131
-- This does not do any matching on the @record@ type, so it is mostly useful
119
132
-- for queries that don't use the @record@ type, like
120
- -- 'Database.Persist.Monad.Shim.rawSql '.
133
+ -- 'Database.Persist.Monad.Shim.rawExecute '.
121
134
mockQuery :: (forall record a . Typeable record => SqlQueryRep record a -> Maybe a ) -> MockQuery
122
- mockQuery = MockQuery
135
+ mockQuery f = MockQuery (fmap pure . f)
136
+
137
+ -- | A helper for mocking a 'Database.Persist.Monad.Shim.selectSource' or
138
+ -- 'Database.Persist.Monad.Shim.selectSourceRes' call.
139
+ --
140
+ -- Usage:
141
+ --
142
+ -- @
143
+ -- mockSelectSource $ \\filters opts ->
144
+ -- if null filters && null opts
145
+ -- then
146
+ -- let person1 = [Entity (toSqlKey 1) $ Person \"Alice\"]
147
+ -- person2 = [Entity (toSqlKey 2) $ Person \"Bob\"]
148
+ -- in Just [person1, person2]
149
+ -- else Nothing
150
+ -- @
151
+ mockSelectSource :: forall record . Typeable record => ([Filter record ] -> [SelectOpt record ] -> Maybe [Entity record ]) -> MockQuery
152
+ mockSelectSource f = withRecord @ record $ \ case
153
+ SelectSourceRes filters opts ->
154
+ let toAcquire entities = Acquire. mkAcquire (pure $ Conduit. yieldMany entities) (\ _ -> pure () )
155
+ in toAcquire <$> f filters opts
156
+ _ -> Nothing
157
+
158
+ -- | A helper for mocking a 'Database.Persist.Monad.Shim.selectKeys' or
159
+ -- 'Database.Persist.Monad.Shim.selectKeysRes' call.
160
+ --
161
+ -- Usage:
162
+ --
163
+ -- @
164
+ -- mockSelectKeys $ \\filters opts ->
165
+ -- if null filters && null opts
166
+ -- then Just $ map toSqlKey [1, 2]
167
+ -- else Nothing
168
+ -- @
169
+ mockSelectKeys :: forall record . Typeable record => ([Filter record ] -> [SelectOpt record ] -> Maybe [Key record ]) -> MockQuery
170
+ mockSelectKeys f = withRecord @ record $ \ case
171
+ SelectKeysRes filters opts ->
172
+ let toAcquire keys = Acquire. mkAcquire (pure $ Conduit. yieldMany keys) (\ _ -> pure () )
173
+ in toAcquire <$> f filters opts
174
+ _ -> Nothing
175
+
176
+ -- | A helper for mocking a 'Database.Persist.Monad.Shim.withRawQuery' call.
177
+ --
178
+ -- Usage:
179
+ --
180
+ -- @
181
+ -- mockWithRawQuery $ \\sql vals ->
182
+ -- if sql == "SELECT id, name FROM person"
183
+ -- then
184
+ -- let row1 = [toPersistValue 1, toPersistValue \"Alice\"]
185
+ -- row2 = [toPersistValue 2, toPersistValue \"Bob\"]
186
+ -- in Just [row1, row2]
187
+ -- else Nothing
188
+ -- @
189
+ mockWithRawQuery :: (Text -> [PersistValue ] -> Maybe [[PersistValue ]]) -> MockQuery
190
+ mockWithRawQuery f = MockQuery $ \ case
191
+ WithRawQuery sql vals conduit ->
192
+ let outputRows rows = Conduit. runConduit $ Conduit. yieldMany rows .| conduit
193
+ in outputRows <$> f sql vals
194
+ _ -> Nothing
195
+
196
+ -- | A helper for mocking a 'Database.Persist.Monad.Shim.rawQuery' or
197
+ -- 'Database.Persist.Monad.Shim.rawQueryRes' call.
198
+ --
199
+ -- Usage:
200
+ --
201
+ -- @
202
+ -- mockRawQuery $ \\sql vals ->
203
+ -- if sql == "SELECT id, name FROM person"
204
+ -- then
205
+ -- let row1 = [toPersistValue 1, toPersistValue \"Alice\"]
206
+ -- row2 = [toPersistValue 2, toPersistValue \"Bob\"]
207
+ -- in Just [row1, row2]
208
+ -- else Nothing
209
+ -- @
210
+ mockRawQuery :: (Text -> [PersistValue ] -> Maybe [[PersistValue ]]) -> MockQuery
211
+ mockRawQuery f = MockQuery $ \ case
212
+ RawQueryRes sql vals ->
213
+ let toAcquire rows = Acquire. mkAcquire (pure $ Conduit. yieldMany rows) (\ _ -> pure () )
214
+ in pure . toAcquire <$> f sql vals
215
+ _ -> Nothing
216
+
217
+ -- | A helper for mocking a 'Database.Persist.Monad.Shim.rawSql' call.
218
+ --
219
+ -- Usage:
220
+ --
221
+ -- @
222
+ -- mockRawSql $ \\sql vals ->
223
+ -- if sql == "SELECT id, name FROM person"
224
+ -- then
225
+ -- let row1 = [toPersistValue 1, toPersistValue \"Alice\"]
226
+ -- row2 = [toPersistValue 2, toPersistValue \"Bob\"]
227
+ -- in Just [row1, row2]
228
+ -- else Nothing
229
+ -- @
230
+ mockRawSql :: (Text -> [PersistValue ] -> Maybe [[PersistValue ]]) -> MockQuery
231
+ mockRawSql f = MockQuery $ \ case
232
+ RawSql sql vals ->
233
+ let fromRow = either (error . Text. unpack) id . rawSqlProcessRow
234
+ in pure . map fromRow <$> f sql vals
235
+ _ -> Nothing
0 commit comments