1{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE TypeOperators #-}
4{-# LANGUAGE ViewPatterns #-}
6module Servant.ClientSpec where
8import Control.Concurrent
9import Control.Exception
10import Control.Monad.Trans.Either
12import Data.Foldable (forM_)
17import Network.Wai.Handler.Warp
19import Test.Hspec.QuickCheck
25import Servant.Common.BaseUrl
27import Servant.ServerSpec
31 :<|> "capture" :> Capture "name" String :> Get Person
32 :<|> "body" :> ReqBody Person :> Post Person
33 :<|> "param" :> QueryParam "name" String :> Get Person
34 :<|> "params" :> QueryParams "names" String :> Get [Person]
35 :<|> "flag" :> QueryFlag "flag" :> Get Bool
37 Capture "first" String :>
38 QueryParam "second" Int :>
40 ReqBody [(String, [Rational])] :>
41 Get (String, Maybe Int, Bool, [(String, [Rational])])
48 :<|> (\ name -> return $ Person name 0)
50 :<|> (\ name -> case name of
51 Just "alice" -> return alice
52 Just name -> left (400, name ++ " not found")
53 Nothing -> left (400, "missing parameter"))
54 :<|> (\ names -> return (zipWith Person names [0..]))
56 :<|> \ a b c d -> return (a, b, c, d)
59withServer :: (BaseUrl -> IO a) -> IO a
60withServer action = withWaiDaemon (return server) action
62getGet :: BaseUrl -> EitherT String IO Person
63getCapture :: String -> BaseUrl -> EitherT String IO Person
64getBody :: Person -> BaseUrl -> EitherT String IO Person
65getQueryParam :: Maybe String -> BaseUrl -> EitherT String IO Person
66getQueryParams :: [String] -> BaseUrl -> EitherT String IO [Person]
67getQueryFlag :: Bool -> BaseUrl -> EitherT String IO Bool
68getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
70 -> EitherT String IO (String, Maybe Int, Bool, [(String, [Rational])])
82 it "Servant.API.Get" $ withServer $ \ host -> do
83 runEitherT (getGet host) `shouldReturn` Right alice
85 it "Servant.API.Capture" $ withServer $ \ host -> do
86 runEitherT (getCapture "Paula" host) `shouldReturn` Right (Person "Paula" 0)
88 it "Servant.API.ReqBody" $ withServer $ \ host -> do
89 let p = Person "Clara" 42
90 runEitherT (getBody p host) `shouldReturn` Right p
92 it "Servant.API.QueryParam" $ withServer $ \ host -> do
93 runEitherT (getQueryParam (Just "alice") host) `shouldReturn` Right alice
94 Left result <- runEitherT (getQueryParam (Just "bob") host)
95 result `shouldContain` "bob not found"
97 it "Servant.API.QueryParam.QueryParams" $ withServer $ \ host -> do
98 runEitherT (getQueryParams [] host) `shouldReturn` Right []
99 runEitherT (getQueryParams ["alice", "bob"] host)
100 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
102 context "Servant.API.QueryParam.QueryFlag" $
103 forM_ [False, True] $ \ flag ->
104 it (show flag) $ withServer $ \ host -> do
105 runEitherT (getQueryFlag flag host) `shouldReturn` Right flag
107 modifyMaxSuccess (const 20) $ do
108 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $
109 property $ forAllShrink pathGen shrink $ \ a -> \ b c d ->
111 withServer $ \ host -> do
112 result <- runEitherT (getMultiple a b c d host)
114 result === Right (a, b, c, d)
117 context "client correctly handles error status codes" $ do
118 let test :: WrappedApi -> Spec
119 test (WrappedApi api) =
120 it (show (typeOf api)) $
121 withWaiDaemon (return (serve api (left (500, "error message")))) $
123 let getResponse :: BaseUrl -> EitherT String IO ()
124 getResponse = client api
125 Left result <- runEitherT (getResponse host)
126 result `shouldContain` "error message"
128 (WrappedApi (Proxy :: Proxy Delete)) :
129 (WrappedApi (Proxy :: Proxy (Get ()))) :
130 (WrappedApi (Proxy :: Proxy (Post ()))) :
131 (WrappedApi (Proxy :: Proxy (Put ()))) :
135 WrappedApi :: (HasServer api, Server api ~ EitherT (Int, String) IO a,
136 HasClient api, Client api ~ (BaseUrl -> EitherT String IO ()),
138 Proxy api -> WrappedApi
143withWaiDaemon :: IO Application -> (BaseUrl -> IO a) -> IO a
144withWaiDaemon mkApplication action = do
145 application <- mkApplication
146 bracket (acquire application) free (\ (_, _, baseUrl) -> action baseUrl)
148 acquire application = do
149 (notifyStart, waitForStart) <- lvar
150 (notifyKilled, waitForKilled) <- lvar
151 thread <- forkIO $ (do
152 (krakenPort, socket) <- openTestSocket
154 setPort krakenPort $ -- set here just for consistency, shouldn't be
155 -- used (it's set in the socket)
156 setBeforeMainLoop (notifyStart krakenPort)
158 runSettingsSocket settings socket application)
159 `finally` notifyKilled ()
160 krakenPort <- waitForStart
161 let baseUrl = (BaseUrl Http "localhost" 80){baseUrlPort = krakenPort}
162 return (thread, waitForKilled, baseUrl)
163 free (thread, waitForKilled, _) = do
167 lvar :: IO (a -> IO (), IO a)
170 let put = putMVar mvar
174openTestSocket :: IO (Port, Socket)
176 s <- socket AF_INET Stream defaultProtocol
177 localhost <- inet_addr "127.0.0.1"
178 bind s (SockAddrInet aNY_PORT localhost)
181 return (fromIntegral port, s)
184pathGen = listOf $ elements $
185 filter (not . (`elem` "?%[]/#")) $
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list