I have an in-memory repository that I can create by calling this function:
newEmptyRepository :: IO InMemoryGameRepository
where InMemoryGameRepository is defined like this:
type State = (HashMap GameId Game)
type IORefState = IORef State
newtype InMemoryGameRepository = InMemoryGameRepository IORefState
When writing tests for my Scotty application I've seen examples of using this approach:
spec =
before app $ do
describe "GET /" $ do
it "responds with 200" $ get "/" `shouldRespondWith` 200
it "responds with 'hello'" $ get "/" `shouldRespondWith` "hello"
...
This is all fine but I need to somehow also initialize the InMemoryGameRepository (by calling newEmptyRepository) and use the created instance in my tests. Thus I've changed app to:
app :: InMemoryGameRepository -> IO Application
app repo = scottyApp $ routes repo
And I'm trying to create a test that uses the repository AND the IO Application, for example like this (which doesn't work):
spec =
before (do repo <- newEmptyRepository
app repo) $
-- API Tests
describe "GET /api/games" $
it "responds with " $ do
liftIO $ startGame repo
get "/api/games" `shouldRespondWith` singleGameResponse
where startGame is defined like this:
startGame :: InMemoryGameRepository -> IO Game
Here the compiler says (obviously) that repo is not in scope. But how can I achieve this? I.e. I want to share a single instance of newEmptyRepository both for the app and in the test?
Ps: you can see the full application on github.
You should use beforeWith which has the type
beforeWith :: (b -> IO a) -> SpecWith a -> SpecWith b
Use it as e.g. before newEmptyRepository . beforeWith app whose type is SpecWith Application -> Spec.
If you want to access both the InMemoryGameRepository and the Application in your test cases, defined a helper function
withArg f a = (,) a <$> f a
withArg :: Functor f => (t -> f b) -> t -> f (t, b)
then use
before newEmptyRepository . beforeWith (withArg app)
:: SpecWith (InMemoryGameRepository, Application) -> Spec
Finally, you shouldn't use liftIO $ startGame repo in the definition of your tests - this runs startGame every time the test tree is built (although, this may actually be what you want, it doesn't seem to be the case). Instead, if you use the before family of functions, startGame will run once before the tests are actually run. You can even access the Game returned by startGame using the same technique as above:
before newEmptyRepository
. beforeWith (withArg startGame)
. beforeWith (withArg $ app . fst)
:: SpecWith ((InMemoryGameRepository, Game), Application) -> Spec
Related
I am trying to build an automated feature testing suite using webdriver and polysemy in Haskell. I've gotten as far as defining proper effects and interpreting them into a webdriver WD monad, but now I'm stuck.
I have a value of type Member BrowserMaster r => Sem r () where BrowserMaster is my custom capability.
And this is the interpreter:
runBrowserMaster :: Members [Embed WD.WD, Embed IO] r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
Now I'm wondering how to convert the Embed WD.WD effect into Embed IO, so that I end up with just one.
I tried to craft an interpreter:
runWebDriver :: Member (Embed IO) r => Sem (Embed WD.WD ': r) a -> Sem r a
runWebDriver = interpret $
\a -> embed $ runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
unEmbed a
(Here runSession chromeConfig . finallyClose is a WD a -> IO a)
It does work, but it seems to fire up a new browser session for each of the commands instead of starting it just once, doing everything within and closing.
I have an intuition that it has to do something with resource acquisition and release, but I just cannot get my head around this to be able to put it all together.
Keep in mind that each interpreter will be executed each time an action of the BrowserMaster effect is executed. So every time it runs the runWebDriver interpreter, which explains why it creates, runs and close the session.
I think what you want to do is instead to create/delete the session once, and execute your whole code in this session.
Also, since WD is already a wrapper around IO, I think it's unnecessary to embed both effects.
I am not familiar with your code nor the webdriver library, but I assume this would be something along the lines of:
main :: IO ()
main = runSession chromeConfig . finallyClose $ do
setImplicitWait 60000
setWindowSize (1024, 768)
runM . runBrowserMaster $ myBusinessCode
runBrowserMaster :: Member (Embed WD.WD) r => Sem (BrowserMaster ': r) a -> Sem r a
runBrowserMaster = interpret $ \case
ClickElement bmSelector ->
let action = (WD.findElem (bmSelectoToSelector bmSelector) >>= WD.click :: WD.WD ())
in embed action
{- ... -}
Note: If you need to run some IO code in the interpreter, use liftIO to make it an WD action instead, e.g. liftIO $ putStrLn "Hello world".
PS: I recommend renaming the runBrowserMaster interpreter to something like browserMasterToWD as it better represents what it does: interpret the BrowserMaster effect in terms of an WD action.
I have been searching for examples, however most examples do a RouteSomething -> PathSegment "firstpath" $ unitEncoder mempty and not a single nested route.
There are some examples that use Cat.id to pass the whole URI as a Text such Characher-Sheet:
backendRouteEncoder = mkFullRouteEncoder
(FullRoute_Backend BackendRoute_Missing :/ ())
(\case
BackendRoute_API -> PathSegment "api" $ Cat.id
BackendRoute_Missing -> PathSegment "missing" $ unitEncoder mempty
)
and then on the backend parse the whole route:
server sql (BackendRoute_API :=> Identity _) = dir "api" $
route [ ("spelllist/", runReaderT spellListHandler sql)
, ("featlist/", runReaderT featListHandler sql)
]
however, this feels odd since I would have thought all routing would have been defined in Common.Route with Obelisk.Route as per the skeleton of ob init and other examples defining routes exclusively there. I would appreciate the safety of exhaustiveness check of the LambdaCase over the datatype codifying routes and not having to add BackendRoute_Missing equivalent on all possible backend/frontend subroutes.
data FrontendRoute :: * -> * where
FrontendRoute_Sub :: FrontendRoute (R SubRoute)
data SubRoute :: * -> * where
SubRoute_Foo :: SubRoute ()
Then replace unitEncoder with pathComponentEncoder $ \case ...
See obelisk-oauth for another example.
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?
I have been enjoying learning Haskell and I think I am making some good progress with the help of folks here and at #haskell. My learning is mostly still at the point where I look at examples and try to abstract out the techniques applied there and apply them to my own code.
Currently, I have started looking at developing monad stacks for various applications and I am looking to incorporate the functionality of the persistent framework into my application.
Here is my monad stack:
newtype App a = App { unApp :: StateT AppState (SqlPersistT (ResourceT (LoggingT IO))) a }
deriving ( Applicative
, Functor
, Monad
, MonadIO
, MonadState AppState
)
AppState is just a record data type holding a single Int value in this example.
My main function looks like:
main = runApp "./test.sqlite" (AppState 69) runMigrate
where runApp is supposed to unwrap all the monads:
runApp :: Text -> AppState -> App a -> IO a
runApp t s a =
runStdoutLoggingT . runResourceT . withSqliteConn t . runSqlConn . flip evalStateT s . unApp
and runMigrate is the application to run in the App monad. In this case I was shooting for just getting it to run the migration:
runMigrate :: App ()
runMigrate = return $ liftPersist $ runMigration migrateAll
The compiler points out that I don't know what I am doing with the complaint:
Main.lhs:59:16:
Couldn't match type ‘m0 ()’ with ‘()’
Expected type: App ()
Actual type: App (m0 ())
In the expression: return $ liftPersist $ runMigration migrateAll
In an equation for ‘runMigrate’:
runMigrate = return $ liftPersist $ runMigration migrateAll
Questions:
What is the right way to do this?
What happens if in my monad stack I introduce a ReaderT? Given that SqlPersistT is really a ReaderT how can I make sure that ask is matched to the real ReaderT and not SqlPersistT?
For your first question: return isn't the right function --- the point of return is that return x does none of the work of your monad and just returns a value. I think you probably want:
runMigrate = App $ lift $ runMigration migrateAll
App lifts your newtype definition into your monad; lift lifts the PersistT into the wrapping StateT.
(Incidentally, I recommend naming App . lift . runMigration as something like runMigrationApp if you're going to be using it a lot.)
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.