I want to write little integration tests for my Snap web handlers but I am stuck. Here is the scenario. I have a Snap web handler that (run-of-the-mill style) CRUDs up a type and it looks something like this:
create :: AppHandler ()
create = method POST $ do
lastName <- decodeUtf8 . fromJust <$> getParam "lastName"
firstName <- decodeUtf8 . fromJust <$> getParam "firstName"
createPerson $ Person firstName lastName
modifyResponse (setResponseCode 204)
The Snap.Test module has some things to help build up a request and I use it to make a request for my handler:
createOwnerReq :: RequestBuilder IO ()
createOwnerReq = postUrlEncoded "host/person/create" $
fromList [ ("firstName", ["Greg-Shaw"])
, ("lastName", ["Snoy'Sullivan"])
]
Here's the problem, I want to make a TestUnit TestCase for this handler so I need the run the handler on the createOwnerReq request. The module Snap.Test provides:
runHandler :: MonadIO a => RequestBuilder m () -> Snap a -> m Response
so
... do
resp <- runHandler createOwnerReq ???
But wait!!! My request handler is of type AppHandler () but runHandler requires a Handler of type Snap a.
How do I lift my AppHandler type into the Snap monad? Help please, this is kind of trippin' me out.
Ibolla's return create trick probably doesn't do what you want. It compiles correctly because runHandler takes a Snap a which will work on a Snap action with any return value. return create :: Snap (AppHandler ()), which is very different from the Snap () that you were probably expecting.
We are working on a Snap.Snaplet.Test equivalent that will wrap the runHandler function provided by Snap.Test to allow you to test Handlers. This will probably be included in the 0.10 release of the snap package.
In the interim, you can solve the problem manually by using runSnaplet to convert your SnapletInit into a Snap () action that can be passed to Snap.Test.runHandler. This won't let you test an individual Handler, but it will let you test any of the routes defined in your application's initializer.
EDIT: In snap-0.10, we added test support for snaplets.
Related
Visualize a bytestring body on a webserver run on Spock (localhost for instance)
My goal : create website and view a bytestring (converted to text)
Framework: Http Simple for performing request to restAPI
Spock for my server
I don't want for instance to create a JSON as I need to manipulate/inspect my response before creating a JSON structure. General idea is that I want to use the response body to construct a JSON query structure (the user will be able to compose his question) that will be sent to the restAPI website.
I manage to build a request like this:
connect = do
request' <- (parseRequest "http://localhost")
let request = setRequestMethod "POST"
$ setRequestHost (S8.pack ("xx.xxx.xxx.xxx"))
$ setRequestPath "/api/Integration/Login"
$ setRequestBodyJSON me
$ setRequestPort 1000
$ request'
response <- httpJSON request
return (getResponseBody response :: Auth)
then I used it to query the API page
getRequest :: RequestPath -> HtmlT IO L.ByteString
getRequest rpath = do
atoken <- liftIO connect
request' <- liftIO (parseRequest "http://localhost")
let request = setRequestMethod "POST"
$ setRequestHost (S8.pack ("xx.xxx.xxx.xxx"))
$ setRequestPort 1000
$ setRequestPath (S8.pack ("/api/Integration/" ++ rpath))
$ addRequestHeader hAuthorization (S8.pack (unpack (token_type (atoken)) ++ " " ++ unpack (access_token (atoken))))
$ setRequestBodyJSON r1
$ request'
response <- httpLBS request
return (getResponseBody (response))
then I follow with a short SpockM monad:
app1 = do get root $ text "root"
fct
with fct equal to
fct = do get "/further" $ lucidIO ( fmap TL.decodeUtf8 (getRequest "GetProperties"))
Everything compile fine I am even able to see the result in GHCI with invocation like : connect >>= (\ x -> print x) (same with getRequest "GetProperties" )
What I don't understand is that lucidIO should give me a ActionCtxtT ctx m b type, which perfectly fit the type of a handler (for example like the text function in the do get ... $ text -> ActionCtxT ctx m a) and should be processed by the spock function in main() ie runSpock 8080 (spock spockCfg app1)
I tried to get rid of the ByteString 'ending' type replacing it with a () in order to mimic as close as possible the Html () type which shows up and work in lot of examples I studied.
All parsing and request building is done with the HTTP.Simple (it's not very elegant I know for instance it just have to work) which pulls me from start in a monad (due to the first function 'parseRequest' -> m Request) from which I cannot escape until lucidIO - may be I am choosing the wrong Monad (ie IO : but with IO I am able to check everything in ghci). Could you give me some hints on how to get this ByteString printed in my browser?
So finally I achieve what I was looking for - woua I am really proud of me ...
Okay for those who will look for the same thing, what I've manage to do, to recap my main problem was to escape the IO monad (my choice may be not clever but still) in which I was stuck due to the use of request parsers from HTTP.simple library.
My code change a little bit but the general idea stays the same:
building a Response query:
getResponseMethod :: RequestPath -> RequestBody -> IO (Maybe Value)
from which thanks to the decode function (aeson package) a Maybe Value is obtained (wrapped in IO but that's okay)
then my little spock server:
main :: IO ()
main = do
spockCfg <- defaultSpockCfg () PCNoDatabase ()
runSpock 8080 (spock spockCfg app)
I work a lot to have the right app -> SpockM () () () ()
I started with the simplest app we could imagine:
app = do get root $ text "Hello!"
noticing that the text function is producing a MonadIO m => ActionCtxT cxt m a monad so my thought was that if I 'sprinkle' some clever LiftIO thing it should do the job.
I create a helper function:
extrct :: MonadIO m => ActionCtxT ctx m Text
extrct = liftIO $ do
a <- getResponseMethod "GetProperties" r1
return (pack $ show a)
and with a twist of hand adjust my app
app :: SpockM () () () ()
app = do get root $ do
a <- extrct
text a
and finally I was able to see the string representation of the Maybe Value :: JSON on my spock local webserver. That's what I was looking for. Now I can work on cleaning my code. From what I understand using liftIO will place the IO monad in the rigth place in the Monad Stack that's because IO is always at the bottom?
Here's a (simplified) test I have
it "asserts route access for valid arguments" $ do
-- ...
token <- getFlagTokenFromCsrf "1234"
get $ FlagMentorR flaggedId Flag token
statusIs 200
where I have getFlagTokenFromCsrf :: Text -> Handler Text
The error I get when I try to stack test is:
Couldn't match type ‘Yesod.Core.Types.HandlerT App IO Text’
with ‘Control.Monad.Trans.State.Lazy.StateT
(YesodExampleData App) IO Text’
Expected type: Control.Monad.Trans.State.Lazy.StateT
(YesodExampleData App) IO Text
Actual type: Handler Text
(Here's the full example)
It seems I am trying to do something rather similar. That is: Try to run arbitrary Handler a functions in unit tests.
Some approaches I've tried and thought about:
The closest I've gotten that actually worked for me:
import Application (handler)
spec = withApp $ do
it "" $ do
liftIO $ handler $ someHandler
Although it seems this calls makeFoundation under the hood, and I'd prefer if my handler would run in the context of the main webapp and not a separate instance.
To execute a handler in the main app's context, I've also tried to:
import Foundation (unsafeHandler)
import qualified Control.Monad.Trans.State as ST
spec = withApp $ do
it "" $ do
(YesodExampleData app _ _ _) <- ST.get
liftIO $ unsafeHandler app $ someHandler
Which has gotten me a type error like this:
[34 of 35] Compiling Handler.FooSpec ( /path/to/test/Handler/FooSpec.hs, /path/to/.stack-work/odir/Handler/FooSpec.o )
/path/to/test/Handler/FooSpec.hs:42:31:
Couldn't match type ‘Network.Wai.Internal.Request
-> (Network.Wai.Internal.Response
-> IO Network.Wai.Internal.ResponseReceived)
-> IO Network.Wai.Internal.ResponseReceived’
with ‘App’
Expected type: App
Actual type: Network.Wai.Application
Probable cause: ‘app’ is applied to too few arguments
In the first argument of ‘unsafeHandler’, namely ‘app’
In the expression: unsafeHandler app
Failed, modules loaded: Import, Utils, Foundation, [...]
I'm not entirely clear on what might be the difference between Network.Wai.Application and App, but that might be the key to making this work.
I've also considered to specify a route to the handler, and then it would be accessible via get SomeRouteR. Although I'd rather not have to do this if I can help it.
I'm also considering refactoring some of my Handler a functions to IO a, and then I can use liftIO to execute them in not just handlers but tests as well.
That's as far as I've gotten. If I figure something more out I intend to update this answer. I've also starred this question so I get notified if someone else figures out something more or better.
Edit: I've also asked this question on GitHub.
Edit:
I've found something rather promissing while trying to dig further in the direction of option #2:
import Foundation (unsafeHandler)
runHandler :: Handler a -> ST.StateT (YesodExampleData App) IO a
runHandler handler = do
foundation <- getTestYesod
liftIO $ unsafeHandler foundation handler
On which I was able to run test like so:
it "runHandler" $ do
let
testHandler :: Handler Int
testHandler = do
return 2
runHandler testHandler >>= (==? 2)
let
testHandler2 :: Handler String
testHandler2 = do
fmap show $ (Import.runDB) $ insert (def :: User)
runHandler testHandler2 >>= (==? ("UserKey {unUserKey = SqlBackendKey {unSqlBackendKey = 1}}"))
I am not a Yesod expert but what you are trying to do probably does not make sense: getFlagTokenFromCsrf is a server-side function that extracts some information from the request, hence it lives in Handler a monad because this is the monad for server-side code.
By contrast, Yesod tests are meant to be integration tests, asserting behaviour from the client-side against your Application (see https://hackage.haskell.org/package/yesod-test-1.5.3/docs/Yesod-Test.html). The functions there live in a different monad, namely YesodExample site a which manages requests on your behalf. This monad allows you to reuse part of your server code (routes, data types) and manage state of your DB but is otherwise totally uncorrelated with server-side operations.
I am not sure what you are trying to do but if you need some information generated on the server-side, you have to find another way to make it available on the client side.
I am using the LevelDB library and Snap framework together. I have:
main :: IO ()
main = runResourceT $ do
db <- open "thedb" defaultOptions { createIfMissing = True }
liftIO $ serveSnaplet defaultConfig $ initWeb db
Now in my handler, I'm unsure how to get back to MonadResource IO in order to query the database:
handleWords :: Handler App App ()
handleWords = do
words <- uses thedb $ \db -> $ get db def "words"
writeBS $ pack $ show words
Which gives me a: No instance for (MonadResource IO) arising from a use of 'get'
Any ideas? I feel like I'm missing something about how to properly create a monad "stack". Thanks
MonadResource/ResourceT is one way of acquiring scarce resources in a way that guarantees resources will be freed in the case of an exception. Another approach is the bracket pattern, which is supported by Snap via the bracketSnap function. You can use this to create the ResourceT context needed by LevelDB:
import qualified Control.Monad.Trans.Resource as Res
bracketSnap Res.createInternalState Res.closeInternalState $ \resState -> do
let openAction = open "thedb" defaultOptions { createIfMissing = True }
db <- Res.runInternalState openAction resState
This could be made simpler with some changes in Snap and leveldb:
Instead of only providing the open function, which presumes a MonadResource context, there could be a function which returns a Resource value. I'm making this tweak in persistent for the 2.0 release.
Snap could provide support for either MonadResource or the Resource monad (two separate concepts with unfortunately similar names).
Snap doesn't need to support MonadResource or Resource for you to do this. You're doing the monad transformer composition in the wrong direction. A look at the types will help.
serveSnaplet :: Config Snap AppConfig -> SnapletInit b b -> IO ()
runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a
So you're trying to put an IO in the place that a ResourceT is expected. You should approach this the other way around. Put your open "thedb" ... call inside your application's Initializer with a liftIO. But open is a MonadResource, so you need to use the ResourceT instance to get it into an IO. It will look something like this:
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
...
db <- liftIO $ runResourceT $ open "thedb" defaultOptions
Then store the db handle in your App state and you can retrieve it later using Handler's MonadReader or MonadState instances.
this is probably a simple question and I've seen a similar one on SO, but I'm still stuck.
I'm trying to do an HTTP call to pull in the contents of another blog and display it on my page. This is more of a learning exercise than anything.
Here's my handler
blog :: App1Handler ()
blog = do
contents <- Requester.getUrl "http://someblog.com/"
heistLocal (bindString "contents" contents) . render $ "blog"
Requester.getUrl has the signature getUrl :: String -> IO T.Text
And the error I get back is
src/Main.hs:50:15:
Couldn't match expected type Handler App1 App1 t0' with actual typeIO T.Text'
In the return type of a call of `getUrl'
In a stmt of a 'do' block:
contents <- getUrl "http://someblog.com/"
In the expression:
do { contents <- getUrl "http://someblog.com/";
heistLocal (bindString "contents" contents) . render $ "blog" }
From what I gather, I'm stuck inside of the IO monad and it wants the type Handler App1 App1 t0. I've experimented with sticking liftIO in places, but I'm pretty confused on this one.
Can anyone point me in the right direction?
Thanks!
You just have to liftIO the IO action returned by getUrl, like this:
contents <- liftIO $ Requester.getUrl "http://someblog.com/"
The reasoning here is simple. You have a do-block of type App1Handler (), which means that the
right hand side of any <- statement within this do-block must have type App1Handler a.
However, getUrl returns IO Text, so you need to a function to convert from
IO a to App1Handler a which is exactly what liftIO does.
liftIO :: MonadIO m => IO a -> m a
Could someone explain what is going on in the default snap project template?
--------------------------------------------------------------------------
-- | Handle login submit
handleLoginSubmit :: Handler App (AuthManager App) ()
handleLoginSubmit =
loginUser "login" "password" Nothing
(\_ -> handleLogin err) (redirect "/")
where
err = Just "Unknown user or password"
-- | Render login form
handleLogin :: Maybe T.Text -> Handler App (AuthManager App) ()
handleLogin authError = heistLocal (bindSplices errs) $ render "login"
where
errs = [("loginError", textSplice c) | c <- maybeToList authError]
| The application's routes.
routes :: [(ByteString, Handler App App ())]
routes = [ ("/login", with auth handleLoginSubmit)
, ("/logout", with auth handleLogout)
, ("/new_user", with auth handleNewUser)
, ("", serveDirectory "static")
]
Why is it returning ()?
What is the standard signature of the "controller" (as you would call it in MVC speak) function?
How does it know to use index.tpl as a template for / route?
How do I get rid of authentication layer, say if I only want to make a simple personal web-app?
The documentation and a tutorial mainly covers the snapplets (or the templates), but it does not go over any of the Haskell. It would be nice to see an example of how to write a book-store like app, or a blog (the official snap website stores their blog entries in markdown - so I am not sure what's going on there).
I'm in the process of learning Haskell myself, and I know nothing about Snap, but I can do my best to answer what I can see:
1)
The Snap.Snaplet module defines a type: Handler b v a.
So, any Handler has three type parameters: b, v, and a.
Also, (Handler b v) is declared in the same module to be a Monad. That can probably tell you something about what the last a parameter is for.
IO is another example of a Monad.
IO () does "something" that has to do with IO, and then returns (), an empty value.
IO a does "something" that has to do with IO, and then returns something else of type a.
For example, it doesn't really make sense to do x <- putStrLn "text" because putStrLn has return type IO (). Technically, you can, but it's usually not useful. putStrLn does IO and that's all, declining to pass anything onward to future functions. Specifically, it prints something to the console, but it doesn't tell the rest of the program that it did so.
str <- getLine makes sense because getLine has type IO String. It does IO, and then tells str about a String. It produces a result that the rest of the function can use directly.
For Handler, you could forget what a handler does and look at it like this:
let M = Handler b v
M is declared to be a monad.
So, M a does "something" and then returns an a.
M () does "something" and doesn't return anything.
From that you can probably tell something about what functions like handleLogin are doing. It takes its arguments and probably does something to/with the Handler based on those. Afterwards, assuming there were no errors, the program moves to the next line without telling the function on the next line what happened.
There are some functions in Snap that return a Handler with something other than () for the last parameter. If you see a function like that, it means it carries a meaningful return value.
4)
I suspect the templates are more of examples than things you are meant to build from, but it's all manually written, so I'd think you could remove the authentication steps if you like. The type App is defined in "Application.hs" where you could remove its auth parameter. Then you could remove references to it in the rest of the project.