Run db action in yesod test - haskell

In my yesod test I want to be able to modify a record in the db in the middle of the test.
Here is the code I came up with
yit "post is created by authorized user" $ do
request $ do
addPostParam "ident" "dummy"
setMethod "POST"
setUrl ("http://localhost:3000/auth/page/dummy" :: Text)
update 0 [UserAuthorized =. True]
postBody PostR (encode $ object [
"body" .= ("test post" :: Text),
"title" .= ("test post" :: Text),
"coverImage" .= ("test post" :: Text),
"author" .= (0 :: Int)
])
statusIs 200
This fails with the error
• Couldn't match expected type ‘IO a0’
with actual type ‘ReaderT backend0 m0 ()’
• In the second argument of ‘($)’, namely
‘update 0 [UserAuthorized =. True]’
In a stmt of a 'do' block:
runIO $ update 0 [UserAuthorized =. True]
In the expression:
do { settings <- runIO
$ loadYamlSettings
["config/test-settings.yml", "config/settings.yml"] [] useEnv;
foundation <- runIO $ makeFoundation settings;
yesodSpec foundation $ do { ydescribe "Auth" $ do { ... } };
runIO $ update 0 [UserAuthorized =. True];
.... }
I can tell this is because update returns m () instead of YesodExample site () like request, postBody and statusIs do.
How would I be able to do a db update inside of this test?

You need to use runDB function. i.e:
runDB $ update 0 [UserAuthorized =. True]

There were 2 problems in this, the first one as Sibi pointed out is I needed runDB the second one is you can't just look up a record with an integer.
To get this working I used the following code
runDB $ do
(first :: Maybe (Entity User)) <- selectFirst [] []
case first of
Nothing -> pure () -- handle the case when the table is empty
Just (Entity k _) -> update k [UserAuthorized =. True]
This find's the record in the DB and then updates it. Modify (first :: Maybe (Entity User)) <- selectFirst [] [] to select the record you want to update.

Related

Couldn't match expected type ‘Post’ with actual type ‘Route App’

I'm trying to use the function postBody
postBody :: (Yesod site, RedirectUrl site url) => url -> ByteString -> YesodExample site ()
from the Yesod.Test package. The documentation says it can be used like this
import Data.Aeson
postBody HomeR (encode $ object ["age" .= (1 :: Integer)])
However when I tried to use it in my app
describe "Posts" $ do
it "posts post and returns post" $ do
postBody PostR (encode $ object [
"body" .= ("test post" :: Text),
"title" .= ("test post" :: Text),
"coverImage" .= ("test post" :: Text),
"author" .= (0 :: Int)
])
statusIs 200
I got the error
• Couldn't match expected type ‘Post’ with actual type ‘Route App’
• In the first argument of ‘postBody’, namely ‘PostR’
In a stmt of a 'do' block:
postBody
PostR
(encode
$ object
["body" .= ("test post" :: Text), "title" .= ("test post" :: Text),
"coverImage" .= ("test post" :: Text), "author" .= (0 :: Int)])
In the second argument of ‘($)’, namely
‘do { postBody
PostR
(encode
$ object
["body" .= ("test post" :: Text), "title" .= ("test post" :: Text),
....]);
statusIs 200 }’
My usage seems to be the same as the example so I can't see why it would fail.
PostR is in the routes file here
/posts PostR POST
and the handler for it
postPostR :: Handler Value
postPostR = do
post <- requireJsonBody :: Handler Post
maybeUserID <- maybeAuthId
case maybeUserID of
Just userID -> do
let post' = post {postAuthor = userID}
inserted <- runDB $ insertEntity post'
returnJson inserted
Nothing -> sendResponseStatus status403 ("Unauthorized" :: Text)
The problem ended up being that in the posts table there was a column body so Yesod created postBody for this which conflicted with the postBody from Yesod.Test.
The solution is using a qualified import on yesod test
import qualified Yesod.Test as T

Couldn't match type `BaseBackend(YesodPersistBackend PersonalPage)' with `SqlBackend'

I have a problem using persistent and Yesod with postgresql. This is the function that brings problems:
postBlogR :: MyHandler Html
postBlogR = do
((res, widgetForm), enctype) <- runFormPost blogForm
case res of
FormSuccess blog -> do
blogId <- runDB $ insert blog
redirect $ HomeR
_ -> adminLayout [whamlet|<h1>Post Created!|]
The trace is:
* Couldn't match type `BaseBackend(YesodPersistBackend PersonalPage)'
with `SqlBackend'
arising from a use of `insert'
* In the second argument of `($)', namely `insert blog'
In a stmt of a 'do' block: blogId <- runDB $ insert blog
In the expression:
do { blogId <- runDB $ insert blog;
redirect $ HomeR }
You are missing the relevant YesodPerist instance. It will be something like this:
instance YesodPersist App where
type YesodPersistBackend App = SqlBackend
runDB action = do
master <- getYesod
runSqlPool action $ appConnPool master

In Yesod.Test, is it possible to get into `SpecM (TestApp site)` from `IO`, similarly to how `liftIO` allows the opposite?

More specifically, I'm looking for a function of type:
f :: site -> SpecM (TestApp site) b -> IO b
or similar.
In other words, I am looking for the inverse function of:
liftIO :: IO b -> SpecM (TestApp _site) b
I've looked in the Yesod.Test docs and source code and tried to construct it myself too, but I don't seem to find it anywhere and it doesn't seem to be simple to construct either. Have I overlooked something, and might it exist somewhere? Or could it be simple to construct in a way I have not considered?
Background
I've run into the need for such a function multiple times, but so far I've always been able to workaround or do without. But in my latest situation it doesn't seem possible to work around: I want to write a Monadic QuickCheck property that would set up an arbitrary db environment (runDB . insert) and run some handlers on it to test for certain invariants of my application.
This is the farthest I've gotten with my attempts:
propSpec = do
context "qt" $ do
it "monadic" $ property $ \a b -> monadicIO $ do
run $ withServer $ \site -> yesodSpec site $ runDB $ insert $ User{..}
assert $ a /= b
withServer cont = do
p <- runLogging $ withSqlitePool "database_test.db3" 10 $ \pool -> do
runSqlPool (runMigration migrateAll) pool
return pool
wipeDB (MyApp p)
cont (MyApp p, id)
Which, somewhat understandably, results in the following type error:
/path/to/project/spec/Spec.hs:301:35: error:
• Couldn't match type ‘hspec-core-2.2.4:Test.Hspec.Core.Spec.Monad.SpecM
() ()’
with ‘IO a0’
Expected type: IO a0
Actual type: Spec
• In the expression:
yesodSpec site $ runDB $ insert $ User{..}
In the second argument of ‘($)’, namely
‘\ site
-> yesodSpec site $ runDB $ insert $ User{..}’
In the second argument of ‘($)’, namely
‘withServer
$ \ site
-> yesodSpec site $ runDB $ insert $ User{..}’
/path/to/project/spec/Spec.hs:301:52: error:
• Couldn't match type ‘ST.StateT
(YesodExampleData MyApp) IO (Key User)’
with ‘transformers-0.5.2.0:Control.Monad.Trans.Writer.Lazy.WriterT
[YesodSpecTree (MyApp, a1 -> a1)]
Data.Functor.Identity.Identity
()’
Expected type: YesodSpec (MyApp, a1 -> a1)
Actual type: YesodExample MyApp (Key User)
• In the second argument of ‘($)’, namely
‘runDB $ insert $ User{..}’
In the expression:
yesodSpec site $ runDB $ insert $ User{..}
In the second argument of ‘($)’, namely
‘\ site
-> yesodSpec site $ runDB $ insert $ User{..}’
• Relevant bindings include
site :: (MyApp, a1 -> a1)
(bound at /path/to/project/spec/Spec.hs:301:27)
Any ideas?
Also, has anyone before used Test.QuickCheck.Monadic successfully in conjunction with Yesod.Test? Or if perhaps not successfully, at least attempted to do so?

How can I select record by column value?

I have a realy simple data in my config/model
Author
name Text
and I want to select author by name and not by id
I tried this ( the information comes from a form) :
FormSuccess book -> do
result <- runDB $ selectSource [ AuthorName ==. title book]
And book is a data
data FormBook = FormBook {
isbn :: Text
, author :: Text
, title :: Text
, description :: Maybe Text
}
But I got this error
Couldn't match type `[SelectOpt Author]' with `HandlerT App IO'
Expected type: YesodPersistBackend
App
(HandlerT App IO)
(Data.Conduit.Internal.Source m0 (Entity Author))
Actual type: [SelectOpt Author]
-> Data.Conduit.Internal.Source m0 (Entity Author)
In the return type of a call of `selectSource'
Probable cause: `selectSource' is applied to too few arguments
In the second argument of `($)', namely
`selectSource [AuthorName ==. (author book)]'
In a stmt of a 'do' block:
authorresult <- runDB $ selectSource [AuthorName ==. (author book)]
I tried th put before Author PersistenceEntity and PersistenceField but they are not in scope.
I do not have any other idea, except raw query.

how do I get yesod-persistent to recognize the type of my aeson parsed entity array?

I've got a yesod handler that can accept some json with an array of objects.
I'd like to insert all the objects into the database.
newtype NodeList = NodeList [Node]
instance FromJSON NodeList where
parseJSON (Object o) = NodeList <$> o .: "nodes"
parseJSON _ = mzero
postMoreNodesR :: Handler ()
postMoreNodesR = do
nodes::NodeList <- requireJsonBody
runDB $ mapM_ insert nodes
return ()
But some how, its not recognizing my entity type. (although other POST and GET handlers in the same module work great.) I can tell I'm pretty close, but I'm not sure what to do, since "a0" is not a type I've declared anywhere. Here's the error:
Handler/Node.hs:46:30:
Couldn't match expected type `[a0]' with actual type `NodeList'
In the second argument of `mapM_', namely `nodes'
In the second argument of `($)', namely `mapM_ insert nodes'
In a stmt of a 'do' block: runDB $ mapM_ insert nodes
You can also pattern match directly in the bind:
postMoreNodesR :: Handler ()
postMoreNodesR = do
NodeList nodes <- requireJsonBody
runDB $ mapM_ insert nodes
return ()
This obviates the need for the type annotation as well.
It works because the do expression is de-sugared to a lambda:
requireJsonBody >>= \NodeList nodes -> runDB -- ...
I figured it out! I followed the types and realized I needed a helper function to extract the Nodes from the newtyped NodeList:
getNodesFromList :: NodeList -> [Node]
getNodesFromList (NodeList l) = l
Then my handler function became:
postMoreNodesR :: Handler ()
postMoreNodesR = do
nodes::NodeList <- requireJsonBody
runDB $ mapM_ insert $ getNodesFromList nodes
return ()
This stuff is really starting to click!

Resources