@@ -9,6 +9,7 @@
9{-# LANGUAGE TypeOperators #-}
9{-# LANGUAGE TypeOperators #-}
10{-# OPTIONS_GHC -fcontext-stack=100 #-}
10{-# OPTIONS_GHC -fcontext-stack=100 #-}
11{-# OPTIONS_GHC -fno-warn-orphans #-}
11{-# OPTIONS_GHC -fno-warn-orphans #-}
12module Servant.ClientSpec where
13module Servant.ClientSpec where
14#if !MIN_VERSION_base(4,8,0)
15#if !MIN_VERSION_base(4,8,0)
@@ -129,44 +130,9 @@ server = serve api (
129withServer :: (BaseUrl -> IO a) -> IO a
130withServer :: (BaseUrl -> IO a) -> IO a
130withServer action = withWaiDaemon (return server) action
131withServer action = withWaiDaemon (return server) action
132−getGet :: BaseUrl -> EitherT ServantError IO Person
133−getDelete :: BaseUrl -> EitherT ServantError IO ()
134−getDeleteString :: BaseUrl -> EitherT ServantError IO String
135−getCapture :: String -> BaseUrl -> EitherT ServantError IO Person
136−getBody :: Person -> BaseUrl -> EitherT ServantError IO Person
137−getQueryParam :: Maybe String -> BaseUrl -> EitherT ServantError IO Person
138−getQueryParams :: [String] -> BaseUrl -> EitherT ServantError IO [Person]
139−getQueryFlag :: Bool -> BaseUrl -> EitherT ServantError IO Bool
140−getMatrixParam :: Maybe String -> BaseUrl -> EitherT ServantError IO Person
141−getMatrixParams :: [String] -> BaseUrl -> EitherT ServantError IO [Person]
142−getMatrixFlag :: Bool -> BaseUrl -> EitherT ServantError IO Bool
143−getRawSuccess :: Method -> BaseUrl -> EitherT ServantError IO (Int, ByteString,
144− MediaType, [HTTP.Header], C.Response ByteString)
145−getRawFailure :: Method -> BaseUrl -> EitherT ServantError IO (Int, ByteString,
146− MediaType, [HTTP.Header], C.Response ByteString)
147−getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
149− -> EitherT ServantError IO (String, Maybe Int, Bool, [(String, [Rational])])
150−getRespHeaders :: BaseUrl -> EitherT ServantError IO (Headers TestHeaders Bool)
153− :<|> getDeleteString
160− :<|> getMatrixParams
165− :<|> getRespHeaders)
168type FailApi =
133type FailApi =
169 "get" :> Raw
134 "get" :> Raw
135+ :<|> "delete" :> Raw
170 :<|> "capture" :> Capture "name" String :> Raw
136 :<|> "capture" :> Capture "name" String :> Raw
171 :<|> "body" :> Raw
137 :<|> "body" :> Raw
172failApi :: Proxy FailApi
138failApi :: Proxy FailApi
@@ -175,6 +141,7 @@ failApi = Proxy
175failServer :: Application
141failServer :: Application
176failServer = serve failApi (
142failServer = serve failApi (
177 (\ _request respond -> respond $ responseLBS ok200 [] "")
143 (\ _request respond -> respond $ responseLBS ok200 [] "")
144+ :<|> (\ _request respond -> respond $ responseLBS ok200 [] "")
178 :<|> (\ _capture _request respond -> respond $ responseLBS ok200 [("content-type", "application/json")] "")
145 :<|> (\ _capture _request respond -> respond $ responseLBS ok200 [("content-type", "application/json")] "")
179 :<|> (\_request respond -> respond $ responseLBS ok200 [("content-type", "fooooo")] "")
146 :<|> (\_request respond -> respond $ responseLBS ok200 [("content-type", "fooooo")] "")
@@ -182,141 +149,180 @@ failServer = serve failApi (
182withFailServer :: (BaseUrl -> IO a) -> IO a
149withFailServer :: (BaseUrl -> IO a) -> IO a
183withFailServer action = withWaiDaemon (return failServer) action
150withFailServer action = withWaiDaemon (return failServer) action
185−spec :: Spec
152+spec :: IO ()
186−spec = do
153+spec = withServer $ \ baseUrl -> do
187− it "Servant.API.Get" $ withServer $ \ host -> do
154+ let getGet :: EitherT ServantError IO Person
188− (Arrow.left show <$> runEitherT (getGet host)) `shouldReturn` Right alice
155+ getDelete :: EitherT ServantError IO ()
189−
156+ getCapture :: String -> EitherT ServantError IO Person
190− context "Servant.API.Delete" $ do
157+ getBody :: Person -> EitherT ServantError IO Person
191− it "return no body" $ withServer $ \ host -> do
158+ getQueryParam :: Maybe String -> EitherT ServantError IO Person
159+ getQueryParams :: [String] -> EitherT ServantError IO [Person]
160+ getQueryFlag :: Bool -> EitherT ServantError IO Bool
161+ getMatrixParam :: Maybe String -> EitherT ServantError IO Person
162+ getMatrixParams :: [String] -> EitherT ServantError IO [Person]
163+ getMatrixFlag :: Bool -> EitherT ServantError IO Bool
164+ getRawSuccess :: Method -> EitherT ServantError IO (Int, ByteString, MediaType, [HTTP.Header], C.Response ByteString)
165+ getRawFailure :: Method -> EitherT ServantError IO (Int, ByteString, MediaType, [HTTP.Header], C.Response ByteString)
166+ getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])] -> EitherT ServantError IO (String, Maybe Int, Bool, [(String, [Rational])])
167+ getRespHeaders :: EitherT ServantError IO (Headers TestHeaders Bool)
176+ :<|> getMatrixParams
181+ :<|> getRespHeaders)
182+ = client api baseUrl
185+ it "Servant.API.Get" $ do
192− (Arrow.left show <$> runEitherT (getDelete host)) `shouldReturn` Right ()
186+ (Arrow.left show <$> runEitherT getGet) `shouldReturn` Right alice
194− it "return body" $ withServer $ \ host -> do
188+ it "Servant.API.Delete" $ do
195− (Arrow.left show <$> runEitherT (getDeleteString host)) `shouldReturn` Right "ok"
189+ (Arrow.left show <$> runEitherT getDelete) `shouldReturn` Right ()
197− it "Servant.API.Capture" $ withServer $ \ host -> do
191+ it "Servant.API.Capture" $ do
198− (Arrow.left show <$> runEitherT (getCapture "Paula" host)) `shouldReturn` Right (Person "Paula" 0)
192+ (Arrow.left show <$> runEitherT (getCapture "Paula")) `shouldReturn` Right (Person "Paula" 0)
200− it "Servant.API.ReqBody" $ withServer $ \ host -> do
194+ it "Servant.API.ReqBody" $ do
201− let p = Person "Clara" 42
195+ let p = Person "Clara" 42
202− (Arrow.left show <$> runEitherT (getBody p host)) `shouldReturn` Right p
196+ (Arrow.left show <$> runEitherT (getBody p)) `shouldReturn` Right p
204− it "Servant.API.QueryParam" $ withServer $ \ host -> do
198+ it "Servant.API.QueryParam" $ do
205− Arrow.left show <$> runEitherT (getQueryParam (Just "alice") host) `shouldReturn` Right alice
199+ Arrow.left show <$> runEitherT (getQueryParam (Just "alice")) `shouldReturn` Right alice
206− Left FailureResponse{..} <- runEitherT (getQueryParam (Just "bob") host)
200+ Left FailureResponse{..} <- runEitherT (getQueryParam (Just "bob"))
207− responseStatus `shouldBe` Status 400 "bob not found"
201+ responseStatus `shouldBe` Status 400 "bob not found"
209− it "Servant.API.QueryParam.QueryParams" $ withServer $ \ host -> do
203+ it "Servant.API.QueryParam.QueryParams" $ do
210− (Arrow.left show <$> runEitherT (getQueryParams [] host)) `shouldReturn` Right []
204+ (Arrow.left show <$> runEitherT (getQueryParams [])) `shouldReturn` Right []
211− (Arrow.left show <$> runEitherT (getQueryParams ["alice", "bob"] host))
205+ (Arrow.left show <$> runEitherT (getQueryParams ["alice", "bob"]))
212− `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
206+ `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
214− context "Servant.API.QueryParam.QueryFlag" $
208+ context "Servant.API.QueryParam.QueryFlag" $
215− forM_ [False, True] $ \ flag ->
209+ forM_ [False, True] $ \ flag ->
216− it (show flag) $ withServer $ \ host -> do
210+ it (show flag) $ do
217− (Arrow.left show <$> runEitherT (getQueryFlag flag host)) `shouldReturn` Right flag
211+ (Arrow.left show <$> runEitherT (getQueryFlag flag)) `shouldReturn` Right flag
219− it "Servant.API.MatrixParam" $ withServer $ \ host -> do
213+ it "Servant.API.MatrixParam" $ do
220− Arrow.left show <$> runEitherT (getMatrixParam (Just "alice") host) `shouldReturn` Right alice
214+ Arrow.left show <$> runEitherT (getMatrixParam (Just "alice")) `shouldReturn` Right alice
221− Left FailureResponse{..} <- runEitherT (getMatrixParam (Just "bob") host)
215+ Left FailureResponse{..} <- runEitherT (getMatrixParam (Just "bob"))
222− responseStatus `shouldBe` Status 400 "bob not found"
216+ responseStatus `shouldBe` Status 400 "bob not found"
224− it "Servant.API.MatrixParam.MatrixParams" $ withServer $ \ host -> do
218+ it "Servant.API.MatrixParam.MatrixParams" $ do
225− Arrow.left show <$> runEitherT (getMatrixParams [] host) `shouldReturn` Right []
219+ Arrow.left show <$> runEitherT (getMatrixParams []) `shouldReturn` Right []
226− Arrow.left show <$> runEitherT (getMatrixParams ["alice", "bob"] host)
220+ Arrow.left show <$> runEitherT (getMatrixParams ["alice", "bob"])
227− `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
221+ `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
229− context "Servant.API.MatrixParam.MatrixFlag" $
223+ context "Servant.API.MatrixParam.MatrixFlag" $
230− forM_ [False, True] $ \ flag ->
224+ forM_ [False, True] $ \ flag ->
231− it (show flag) $ withServer $ \ host -> do
225+ it (show flag) $ do
232− Arrow.left show <$> runEitherT (getMatrixFlag flag host) `shouldReturn` Right flag
226+ Arrow.left show <$> runEitherT (getMatrixFlag flag) `shouldReturn` Right flag
234− it "Servant.API.Raw on success" $ withServer $ \ host -> do
228+ it "Servant.API.Raw on success" $ do
235− res <- runEitherT (getRawSuccess methodGet host)
229+ res <- runEitherT (getRawSuccess methodGet)
237− Left e -> assertFailure $ show e
238− Right (code, body, ct, _, response) -> do
239− (code, body, ct) `shouldBe` (200, "rawSuccess", "application"//"octet-stream")
240− C.responseBody response `shouldBe` body
241− C.responseStatus response `shouldBe` ok200
243− it "Servant.API.Raw on failure" $ withServer $ \ host -> do
244− res <- runEitherT (getRawFailure methodGet host)
246− Left e -> assertFailure $ show e
247− Right (code, body, ct, _, response) -> do
248− (code, body, ct) `shouldBe` (400, "rawFailure", "application"//"octet-stream")
249− C.responseBody response `shouldBe` body
250− C.responseStatus response `shouldBe` badRequest400
252− it "Returns headers appropriately" $ withServer $ \ host -> do
253− res <- runEitherT (getRespHeaders host)
255− Left e -> assertFailure $ show e
256− Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
258− modifyMaxSuccess (const 20) $ do
259− it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $
260− property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
262− withServer $ \ host -> do
263− result <- Arrow.left show <$> runEitherT (getMultiple cap num flag body host)
265− result === Right (cap, num, flag, body)
268− context "client correctly handles error status codes" $ do
269− let test :: (WrappedApi, String) -> Spec
270− test (WrappedApi api, desc) =
272− withWaiDaemon (return (serve api (left $ ServantErr 500 "error message" "" []))) $
274− let getResponse :: BaseUrl -> EitherT ServantError IO ()
275− getResponse = client api
276− Left FailureResponse{..} <- runEitherT (getResponse host)
277− responseStatus `shouldBe` (Status 500 "error message")
279− (WrappedApi (Proxy :: Proxy (Delete '[JSON] ())), "Delete") :
280− (WrappedApi (Proxy :: Proxy (Get '[JSON] ())), "Get") :
281− (WrappedApi (Proxy :: Proxy (Post '[JSON] ())), "Post") :
282− (WrappedApi (Proxy :: Proxy (Put '[JSON] ())), "Put") :
285− context "client returns errors appropriately" $ do
286− it "reports FailureResponse" $ withFailServer $ \ host -> do
287− Left res <- runEitherT (getDelete host)
288 case res of
230 case res of
289− FailureResponse (Status 404 "Not Found") _ _ -> return ()
231+ Left e -> assertFailure $ show e
290− _ -> fail $ "expected 404 response, but got " <> show res
232+ Right (code, body, ct, _, response) -> do
291−
233+ (code, body, ct) `shouldBe` (200, "rawSuccess", "application"//"octet-stream")
292− it "reports DecodeFailure" $ withFailServer $ \ host -> do
234+ C.responseBody response `shouldBe` body
293− Left res <- runEitherT (getCapture "foo" host)
235+ C.responseStatus response `shouldBe` ok200
237+ it "Servant.API.Raw on failure" $ do
238+ res <- runEitherT (getRawFailure methodGet)
294 case res of
239 case res of
295− DecodeFailure _ ("application/json") _ -> return ()
240+ Left e -> assertFailure $ show e
296− _ -> fail $ "expected DecodeFailure, but got " <> show res
241+ Right (code, body, ct, _, response) -> do
297−
242+ (code, body, ct) `shouldBe` (400, "rawFailure", "application"//"octet-stream")
298− it "reports ConnectionError" $ do
243+ C.responseBody response `shouldBe` body
299− Right host <- return $ parseBaseUrl "127.0.0.1:987654"
244+ C.responseStatus response `shouldBe` badRequest400
246+ it "Returns headers appropriately" $ withServer $ \ _ -> do
300− Left res <- runEitherT (getGet host)
247+ res <- runEitherT getRespHeaders
301 case res of
248 case res of
302− ConnectionError (C.FailedConnectionException2 "127.0.0.1" 987654 False _) -> return ()
249+ Left e -> assertFailure $ show e
303− _ -> fail $ "expected ConnectionError, but got " <> show res
250+ Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
252+ modifyMaxSuccess (const 20) $ do
253+ it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $
254+ property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
256+ result <- Arrow.left show <$> runEitherT (getMultiple cap num flag body)
258+ result === Right (cap, num, flag, body)
305− it "reports UnsupportedContentType" $ withFailServer $ \ host -> do
306− Left res <- runEitherT (getGet host)
308− UnsupportedContentType ("application/octet-stream") _ -> return ()
309− _ -> fail $ "expected UnsupportedContentType, but got " <> show res
311− it "reports InvalidContentTypeHeader" $ withFailServer $ \ host -> do
261+ context "client correctly handles error status codes" $ do
262+ let test :: (WrappedApi, String) -> Spec
263+ test (WrappedApi api, desc) =
265+ withWaiDaemon (return (serve api (left $ ServantErr 500 "error message" "" []))) $
267+ let getResponse :: EitherT ServantError IO ()
268+ getResponse = client api host
269+ Left FailureResponse{..} <- runEitherT getResponse
270+ responseStatus `shouldBe` (Status 500 "error message")
272+ (WrappedApi (Proxy :: Proxy Delete), "Delete") :
273+ (WrappedApi (Proxy :: Proxy (Get '[JSON] ())), "Get") :
274+ (WrappedApi (Proxy :: Proxy (Post '[JSON] ())), "Post") :
275+ (WrappedApi (Proxy :: Proxy (Put '[JSON] ())), "Put") :
279+failSpec = withFailServer $ \ baseUrl -> do
280+ let getGet :: EitherT ServantError IO Person
281+ getDelete :: EitherT ServantError IO ()
282+ getCapture :: String -> EitherT ServantError IO Person
283+ getBody :: Person -> EitherT ServantError IO Person
288+ = client failApi baseUrl
291+ context "client returns errors appropriately" $ do
292+ it "reports FailureResponse" $ do
293+ Left res <- runEitherT getDelete
295+ FailureResponse (Status 404 "Not Found") _ _ -> return ()
296+ _ -> fail $ "expected 404 response, but got " <> show res
298+ it "reports DecodeFailure" $ do
299+ Left res <- runEitherT (getCapture "foo")
301+ DecodeFailure _ ("application/json") _ -> return ()
302+ _ -> fail $ "expected DecodeFailure, but got " <> show res
304+ it "reports ConnectionError" $ do
305+ Right _ <- return $ parseBaseUrl "127.0.0.1:987654"
306+ Left res <- runEitherT getGet
308+ ConnectionError (C.FailedConnectionException2 "127.0.0.1" 987654 False _) -> return ()
309+ _ -> fail $ "expected ConnectionError, but got " <> show res
311+ it "reports UnsupportedContentType" $ do
312+ Left res <- runEitherT getGet
314+ UnsupportedContentType ("application/octet-stream") _ -> return ()
315+ _ -> fail $ "expected UnsupportedContentType, but got " <> show res
317+ it "reports InvalidContentTypeHeader" $ do
312− Left res <- runEitherT (getBody alice host)
318+ Left res <- runEitherT (getBody alice)
313− case res of
319+ case res of
314− InvalidContentTypeHeader "fooooo" _ -> return ()
320+ InvalidContentTypeHeader "fooooo" _ -> return ()
315− _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
321+ _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
317data WrappedApi where
323data WrappedApi where
318 WrappedApi :: (HasServer api, Server api ~ EitherT ServantErr IO a,
324 WrappedApi :: (HasServer api, Server api ~ EitherT ServantErr IO a,
319− HasClient api, Client api ~ (BaseUrl -> EitherT ServantError IO ())) =>
325+ HasClient api, Client' api ~ EitherT ServantError IO ()) =>
320 Proxy api -> WrappedApi
326 Proxy api -> WrappedApi