@@ -26,6 +26,9 @@
26#include "overlapping-compat.h"
26#include "overlapping-compat.h"
27module Servant.ClientSpec where
27module Servant.ClientSpec where
29import Control.Arrow (left)
32import Control.Arrow (left)
30import Control.Concurrent (ThreadId, forkIO,
33import Control.Concurrent (ThreadId, forkIO,
31 killThread)
34 killThread)
@@ -34,9 +37,12 @@ import Control.Monad.Error.Class (throwError)
34import Data.Aeson
37import Data.Aeson
35import qualified Data.ByteString.Lazy as BS
38import qualified Data.ByteString.Lazy as BS
36import Data.Char (chr, isPrint)
39import Data.Char (chr, isPrint)
40+import Data.Foldable (toList)
37import Data.Foldable (forM_)
41import Data.Foldable (forM_)
42+import Data.Maybe (isJust)
38import Data.Monoid hiding (getLast)
43import Data.Monoid hiding (getLast)
39import Data.Proxy
44import Data.Proxy
45+import Data.Sequence (findIndexL)
40import qualified Generics.SOP as SOP
46import qualified Generics.SOP as SOP
41import GHC.Generics (Generic)
47import GHC.Generics (Generic)
42import qualified Network.HTTP.Client as C
48import qualified Network.HTTP.Client as C
@@ -45,8 +51,6 @@ import qualified Network.HTTP.Types as HTTP
45import Network.Socket
51import Network.Socket
46import qualified Network.Wai as Wai
52import qualified Network.Wai as Wai
47import Network.Wai.Handler.Warp
53import Network.Wai.Handler.Warp
50import System.IO.Unsafe (unsafePerformIO)
54import System.IO.Unsafe (unsafePerformIO)
51import Test.Hspec
55import Test.Hspec
52import Test.Hspec.QuickCheck
56import Test.Hspec.QuickCheck
@@ -61,19 +65,19 @@ import Servant.API ((:<|>) ((:<|>)),
62 CaptureAll, Delete,
66 CaptureAll, Delete,
63 DeleteNoContent,
67 DeleteNoContent,
64− EmptyAPI,
68+ EmptyAPI, addHeader,
65 FormUrlEncoded,
69 FormUrlEncoded,
66 Get, Header,
70 Get, Header,
67 Headers, JSON,
71 Headers, JSON,
68− NoContent, Post,
72+ NoContent (NoContent),
69− Put, QueryFlag,
74+ QueryFlag,
70 QueryParam,
75 QueryParam,
71 QueryParams,
76 QueryParams,
73import Servant.API.Internal.Test.ComprehensiveAPI
79import Servant.API.Internal.Test.ComprehensiveAPI
74import Servant.Client
80import Servant.Client
75−{-import qualified Servant.Common.Req as SCR-}
76−{-import qualified Servant.Client.HttpClient as SCR-}
77import Servant.Server
81import Servant.Server
78import Servant.Server.Experimental.Auth
82import Servant.Server.Experimental.Auth
@@ -139,10 +143,8 @@ getBody :: Person -> ClientM Person
139getQueryParam :: Maybe String -> ClientM Person
143getQueryParam :: Maybe String -> ClientM Person
140getQueryParams :: [String] -> ClientM [Person]
144getQueryParams :: [String] -> ClientM [Person]
141getQueryFlag :: Bool -> ClientM Bool
145getQueryFlag :: Bool -> ClientM Bool
142−getRawSuccess :: HTTP.Method
146+getRawSuccess :: HTTP.Method -> ClientM Response
144−getRawFailure :: HTTP.Method
147+getRawFailure :: HTTP.Method -> ClientM Response
145− -> ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
146getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
148getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
147 -> ClientM (String, Maybe Int, Bool, [(String, [Rational])])
149 -> ClientM (String, Maybe Int, Bool, [(String, [Rational])])
148getRespHeaders :: ClientM (Headers TestHeaders Bool)
150getRespHeaders :: ClientM (Headers TestHeaders Bool)
@@ -179,7 +181,7 @@ server = serve api (
179 :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess")
181 :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.ok200 [] "rawSuccess")
180 :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.badRequest400 [] "rawFailure")
182 :<|> (Tagged $ \ _request respond -> respond $ Wai.responseLBS HTTP.badRequest400 [] "rawFailure")
181 :<|> (\ a b c d -> return (a, b, c, d))
183 :<|> (\ a b c d -> return (a, b, c, d))
182− :<|> (return $ addHeader 1729 $ addHeader "eg2" True)
184+ :<|> (return $ Servant.API.addHeader 1729 $ Servant.API.addHeader "eg2" True)
183 :<|> return NoContent
185 :<|> return NoContent
184 :<|> emptyServer)
186 :<|> emptyServer)
@@ -233,7 +235,7 @@ type instance AuthClientData (AuthProtect "auth-tag") = ()
234genAuthHandler :: AuthHandler Request ()
236genAuthHandler :: AuthHandler Request ()
235genAuthHandler =
237genAuthHandler =
236− let handler req = case lookup "AuthHeader" (requestHeaders req) of
238+ let handler req = case lookup "AuthHeader" (toList $ requestHeaders req) of
237 Nothing -> throwError (err401 { errBody = "Missing auth header" })
239 Nothing -> throwError (err401 { errBody = "Missing auth header" })
238 Just _ -> return ()
240 Just _ -> return ()
239 in mkAuthHandler handler
241 in mkAuthHandler handler
@@ -295,57 +297,58 @@ genericClientServer = serve (Proxy :: Proxy GenericClientAPI) (
295manager' :: C.Manager
297manager' :: C.Manager
296manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
298manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
298−runClient x = runClientM x (ClientEnv manager' baseUrl)
300+runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl')
300sucessSpec :: Spec
302sucessSpec :: Spec
301sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
303sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
303 it "Servant.API.Get" $ \(_, baseUrl) -> do
305 it "Servant.API.Get" $ \(_, baseUrl) -> do
304− (left show <$> runClient getGet) `shouldReturn` Right alice
306+ left show <$> runClient getGet baseUrl `shouldReturn` Right alice
306 describe "Servant.API.Delete" $ do
308 describe "Servant.API.Delete" $ do
307 it "allows empty content type" $ \(_, baseUrl) -> do
309 it "allows empty content type" $ \(_, baseUrl) -> do
308− (left show <$> (runClient getDeleteEmpty)) `shouldReturn` Right NoContent
310+ left show <$> runClient getDeleteEmpty baseUrl `shouldReturn` Right NoContent
310 it "allows content type" $ \(_, baseUrl) -> do
312 it "allows content type" $ \(_, baseUrl) -> do
311− (left show <$> (runClient getDeleteContentType)) `shouldReturn` Right NoContent
313+ left show <$> runClient getDeleteContentType baseUrl `shouldReturn` Right NoContent
313 it "Servant.API.Capture" $ \(_, baseUrl) -> do
315 it "Servant.API.Capture" $ \(_, baseUrl) -> do
314− (left show <$> (runClient (getCapture "Paula"))) `shouldReturn` Right (Person "Paula" 0)
316+ left show <$> runClient (getCapture "Paula") baseUrl `shouldReturn` Right (Person "Paula" 0)
316 it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do
318 it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do
317 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
319 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
318− (left show <$> (runClient (getCaptureAll ["Paula", "Peta"]))) `shouldReturn` Right expected
320+ left show <$> runClient (getCaptureAll ["Paula", "Peta"]) baseUrl `shouldReturn` Right expected
320 it "Servant.API.ReqBody" $ \(_, baseUrl) -> do
322 it "Servant.API.ReqBody" $ \(_, baseUrl) -> do
321 let p = Person "Clara" 42
323 let p = Person "Clara" 42
322− (left show <$> runClient (getBody p)) `shouldReturn` Right p
324+ left show <$> runClient (getBody p) baseUrl `shouldReturn` Right p
324 it "Servant.API.QueryParam" $ \(_, baseUrl) -> do
326 it "Servant.API.QueryParam" $ \(_, baseUrl) -> do
325− left show <$> runClient (getQueryParam (Just "alice")) `shouldReturn` Right alice
327+ left show <$> runClient (getQueryParam (Just "alice")) baseUrl `shouldReturn` Right alice
326− Left (FailureResponse r) <- runClient (getQueryParam (Just "bob"))
328+ Left (FailureResponse r) <- runClient (getQueryParam (Just "bob")) baseUrl
327 responseStatusCode r `shouldBe` HTTP.Status 400 "bob not found"
329 responseStatusCode r `shouldBe` HTTP.Status 400 "bob not found"
329 it "Servant.API.QueryParam.QueryParams" $ \(_, baseUrl) -> do
331 it "Servant.API.QueryParam.QueryParams" $ \(_, baseUrl) -> do
330− (left show <$> runClient (getQueryParams [])) `shouldReturn` Right []
332+ left show <$> runClient (getQueryParams []) baseUrl `shouldReturn` Right []
331− (left show <$> runClient (getQueryParams ["alice", "bob"]))
333+ left show <$> runClient (getQueryParams ["alice", "bob"]) baseUrl
332 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
334 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
334 context "Servant.API.QueryParam.QueryFlag" $
336 context "Servant.API.QueryParam.QueryFlag" $
335 forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do
337 forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do
336− (left show <$> runClient (getQueryFlag flag)) `shouldReturn` Right flag
338+ left show <$> runClient (getQueryFlag flag) baseUrl `shouldReturn` Right flag
338 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
340 it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
339− res <- runClient (getRawSuccess HTTP.methodGet)
341+ res <- runClient (getRawSuccess HTTP.methodGet) baseUrl
340 case res of
342 case res of
341 Left e -> assertFailure $ show e
343 Left e -> assertFailure $ show e
342− Right (code, body, ct, _, response) -> do
344+ Right r -> do
343− (code, body, ct) `shouldBe` (200, "rawSuccess", "application"//"octet-stream")
345+ responseStatusCode r `shouldBe` HTTP.status200
344− C.responseBody response `shouldBe` body
346+ responseBody r `shouldBe` "rawSuccess"
345− C.responseStatus response `shouldBe` HTTP.ok200
347+ findIndexL (\x -> fst x == HTTP.hContentType) (responseHeaders r)
348+ `shouldSatisfy` isJust
347 it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
350 it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
348− res <- runClient (getRawFailure HTTP.methodGet)
351+ res <- runClient (getRawFailure HTTP.methodGet) baseUrl
349 case res of
352 case res of
350 Right _ -> assertFailure "expected Left, but got Right"
353 Right _ -> assertFailure "expected Left, but got Right"
351 Left (FailureResponse r) -> do
354 Left (FailureResponse r) -> do
@@ -354,7 +357,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
354 Left e -> assertFailure $ "expected FailureResponse, but got " ++ show e
357 Left e -> assertFailure $ "expected FailureResponse, but got " ++ show e
356 it "Returns headers appropriately" $ \(_, baseUrl) -> do
359 it "Returns headers appropriately" $ \(_, baseUrl) -> do
357− res <- runClient getRespHeaders
360+ res <- runClient getRespHeaders baseUrl
358 case res of
361 case res of
359 Left e -> assertFailure $ show e
362 Left e -> assertFailure $ show e
360 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
363 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
@@ -363,7 +366,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
363 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
366 it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
364 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
367 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
365 ioProperty $ do
368 ioProperty $ do
366− result <- left show <$> runClient (getMultiple cap num flag body)
369+ result <- left show <$> runClient (getMultiple cap num flag body) baseUrl
368 result === Right (cap, num, flag, body)
371 result === Right (cap, num, flag, body)
@@ -377,7 +380,7 @@ wrappedApiSpec = describe "error status codes" $ do
377 it desc $ bracket (startWaiApp $ serveW api) endWaiApp $ \(_, baseUrl) -> do
380 it desc $ bracket (startWaiApp $ serveW api) endWaiApp $ \(_, baseUrl) -> do
378 let getResponse :: ClientM ()
381 let getResponse :: ClientM ()
379 getResponse = client api
382 getResponse = client api
380− Left (FailureResponse r) <- runClient getResponse
383+ Left (FailureResponse r) <- runClient getResponse baseUrl
381 responseStatusCode r `shouldBe` (HTTP.Status 500 "error message")
384 responseStatusCode r `shouldBe` (HTTP.Status 500 "error message")
382 in mapM_ test $
385 in mapM_ test $
383 (WrappedApi (Proxy :: Proxy (Delete '[JSON] ())), "Delete") :
386 (WrappedApi (Proxy :: Proxy (Delete '[JSON] ())), "Delete") :
@@ -392,35 +395,35 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
392 context "client returns errors appropriately" $ do
395 context "client returns errors appropriately" $ do
393 it "reports FailureResponse" $ \(_, baseUrl) -> do
396 it "reports FailureResponse" $ \(_, baseUrl) -> do
394 let (_ :<|> getDeleteEmpty :<|> _) = client api
397 let (_ :<|> getDeleteEmpty :<|> _) = client api
395− Left res <- runClient getDeleteEmpty
398+ Left res <- runClient getDeleteEmpty baseUrl
396 case res of
399 case res of
397− FailureResponse r | responseStatusCode r == 404 -> return ()
400+ FailureResponse r | responseStatusCode r == HTTP.status404 -> return ()
398 _ -> fail $ "expected 404 response, but got " <> show res
401 _ -> fail $ "expected 404 response, but got " <> show res
400 it "reports DecodeFailure" $ \(_, baseUrl) -> do
403 it "reports DecodeFailure" $ \(_, baseUrl) -> do
401 let (_ :<|> _ :<|> getCapture :<|> _) = client api
404 let (_ :<|> _ :<|> getCapture :<|> _) = client api
402− Left res <- runClient (getCapture "foo") (ClientEnv manager baseUrl)
405+ Left res <- runClient (getCapture "foo") baseUrl
403 case res of
406 case res of
404 DecodeFailure _ _ -> return ()
407 DecodeFailure _ _ -> return ()
405 _ -> fail $ "expected DecodeFailure, but got " <> show res
408 _ -> fail $ "expected DecodeFailure, but got " <> show res
407 it "reports ConnectionError" $ \_ -> do
410 it "reports ConnectionError" $ \_ -> do
408 let (getGetWrongHost :<|> _) = client api
411 let (getGetWrongHost :<|> _) = client api
409− Left res <- runClientM getGetWrongHost (ClientEnv manager (BaseUrl Http "127.0.0.1" 19872 ""))
412+ Left res <- runClient getGetWrongHost (BaseUrl Http "127.0.0.1" 19872 "")
410 case res of
413 case res of
411 ConnectionError _ -> return ()
414 ConnectionError _ -> return ()
412 _ -> fail $ "expected ConnectionError, but got " <> show res
415 _ -> fail $ "expected ConnectionError, but got " <> show res
414 it "reports UnsupportedContentType" $ \(_, baseUrl) -> do
417 it "reports UnsupportedContentType" $ \(_, baseUrl) -> do
415 let (getGet :<|> _ ) = client api
418 let (getGet :<|> _ ) = client api
416− Left res <- runClient getGet
419+ Left res <- runClient getGet baseUrl
417 case res of
420 case res of
418 UnsupportedContentType ("application/octet-stream") _ -> return ()
421 UnsupportedContentType ("application/octet-stream") _ -> return ()
419 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
422 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
421 it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
424 it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
422 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
425 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
423− Left res <- runClient (getBody alice)
426+ Left res <- runClient (getBody alice) baseUrl
424 case res of
427 case res of
425 InvalidContentTypeHeader _ -> return ()
428 InvalidContentTypeHeader _ -> return ()
426 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
429 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
@@ -437,14 +440,14 @@ basicAuthSpec = beforeAll (startWaiApp basicAuthServer) $ afterAll endWaiApp $ d
437 it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
440 it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
438 let getBasic = client basicAuthAPI
441 let getBasic = client basicAuthAPI
439 let basicAuthData = BasicAuthData "servant" "server"
442 let basicAuthData = BasicAuthData "servant" "server"
440− (left show <$> runClient (getBasic basicAuthData)) `shouldReturn` Right alice
443+ left show <$> runClient (getBasic basicAuthData) baseUrl `shouldReturn` Right alice
442 context "Authentication is rejected when requests are not authenticated properly" $ do
445 context "Authentication is rejected when requests are not authenticated properly" $ do
444 it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
447 it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
445 let getBasic = client basicAuthAPI
448 let getBasic = client basicAuthAPI
446 let basicAuthData = BasicAuthData "not" "password"
449 let basicAuthData = BasicAuthData "not" "password"
447− Left (FailureResponse r) <- runClient (getBasic basicAuthData)
450+ Left (FailureResponse r) <- runClient (getBasic basicAuthData) baseUrl
448 responseStatusCode r `shouldBe` HTTP.Status 403 "Forbidden"
451 responseStatusCode r `shouldBe` HTTP.Status 403 "Forbidden"
450genAuthSpec :: Spec
453genAuthSpec :: Spec
@@ -453,15 +456,15 @@ genAuthSpec = beforeAll (startWaiApp genAuthServer) $ afterAll endWaiApp $ do
454 it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
457 it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
455 let getProtected = client genAuthAPI
458 let getProtected = client genAuthAPI
456− let authRequest = mkAuthenticateReq () (\_ req -> addHeader "AuthHeader" ("cool" :: String) req)
459+ let authRequest = mkAuthenticateReq () (\_ req -> Servant.Client.addHeader "AuthHeader" ("cool" :: String) req)
457− (left show <$> runClient (getProtected authRequest) ) `shouldReturn` Right alice
460+ left show <$> runClient (getProtected authRequest) baseUrl `shouldReturn` Right alice
459 context "Authentication is rejected when requests are not authenticated properly" $ do
462 context "Authentication is rejected when requests are not authenticated properly" $ do
461 it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
464 it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
462 let getProtected = client genAuthAPI
465 let getProtected = client genAuthAPI
463− let authRequest = mkAuthenticateReq () (\_ req -> addHeader "Wrong" ("header" :: String) req)
466+ let authRequest = mkAuthenticateReq () (\_ req -> Servant.Client.addHeader "Wrong" ("header" :: String) req)
464− Left (FailureResponse r) <- runClient (getProtected authRequest)
467+ Left (FailureResponse r) <- runClient (getProtected authRequest) baseUrl
465 responseStatusCode r `shouldBe` (HTTP.Status 401 "Unauthorized")
468 responseStatusCode r `shouldBe` (HTTP.Status 401 "Unauthorized")
467genericClientSpec :: Spec
470genericClientSpec :: Spec
@@ -473,12 +476,12 @@ genericClientSpec = beforeAll (startWaiApp genericClientServer) $ afterAll endWa
473 NestedClient2{..} = mkNestedClient2 (Just 42)
476 NestedClient2{..} = mkNestedClient2 (Just 42)
475 it "works for top-level client inClientM function" $ \(_, baseUrl) -> do
478 it "works for top-level client inClientM function" $ \(_, baseUrl) -> do
476− (left show <$> (runClient (getSqr (Just 5)))) `shouldReturn` Right 25
479+ left show <$> runClient (getSqr (Just 5)) baseUrl `shouldReturn` Right 25
478 it "works for nested clients" $ \(_, baseUrl) -> do
481 it "works for nested clients" $ \(_, baseUrl) -> do
479− (left show <$> (runClient (idChar (Just 'c')))) `shouldReturn` Right 'c'
482+ left show <$> runClient (idChar (Just 'c')) baseUrl `shouldReturn` Right 'c'
480− (left show <$> (runClient (getSum 3 4))) `shouldReturn` Right 7
483+ left show <$> runClient (getSum 3 4) baseUrl `shouldReturn` Right 7
481− (left show <$> (runClient doNothing )) `shouldReturn` Right ()
484+ left show <$> runClient doNothing baseUrl `shouldReturn` Right ()
483-- * utils
486-- * utils