@@ -1,5 +1,4 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2−{-# LANGUAGE ConstraintKinds #-}
3{-# LANGUAGE DataKinds #-}
2{-# LANGUAGE DataKinds #-}
4{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
5{-# LANGUAGE FlexibleContexts #-}
4{-# LANGUAGE FlexibleContexts #-}
@@ -11,7 +10,6 @@
11{-# LANGUAGE PolyKinds #-}
10{-# LANGUAGE PolyKinds #-}
12{-# LANGUAGE RecordWildCards #-}
11{-# LANGUAGE RecordWildCards #-}
13{-# LANGUAGE StandaloneDeriving #-}
12{-# LANGUAGE StandaloneDeriving #-}
14−{-# LANGUAGE ScopedTypeVariables #-}
15{-# LANGUAGE TypeFamilies #-}
13{-# LANGUAGE TypeFamilies #-}
16{-# LANGUAGE TypeOperators #-}
14{-# LANGUAGE TypeOperators #-}
17{-# LANGUAGE UndecidableInstances #-}
15{-# LANGUAGE UndecidableInstances #-}
@@ -21,17 +19,13 @@
21{-# OPTIONS_GHC -fcontext-stack=100 #-}
19{-# OPTIONS_GHC -fcontext-stack=100 #-}
23{-# OPTIONS_GHC -fno-warn-orphans #-}
21{-# OPTIONS_GHC -fno-warn-orphans #-}
24−{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
26−#include "overlapping-compat.h"
27module Servant.ClientSpec where
23module Servant.ClientSpec where
29−#if !MIN_VERSION_base(4,8,0)
25+import Prelude ()
30−import Control.Applicative ((<$>))
26+import Prelude.Compat
32import Control.Arrow (left)
28import Control.Arrow (left)
33−import Control.Concurrent (forkIO, killThread, ThreadId)
34−import Control.Exception (bracket)
35import Control.Monad.Trans.Except (throwE )
29import Control.Monad.Trans.Except (throwE )
36import Data.Aeson
30import Data.Aeson
37import qualified Data.ByteString.Lazy as BS
31import qualified Data.ByteString.Lazy as BS
@@ -42,34 +36,76 @@ import Data.Proxy
42import GHC.Generics (Generic)
36import GHC.Generics (Generic)
43import qualified Network.HTTP.Client as C
37import qualified Network.HTTP.Client as C
44import Network.HTTP.Media
38import Network.HTTP.Media
45−import qualified Network.HTTP.Types as HTTP
39+import qualified Network.HTTP.Types as HTTP
47−import Network.Wai (Request, requestHeaders, responseLBS)
40+import Network.Wai (responseLBS)
48−import Network.Wai.Handler.Warp
41+import qualified Network.Wai as Wai
42+import System.Exit.Compat
49import System.IO.Unsafe (unsafePerformIO)
43import System.IO.Unsafe (unsafePerformIO)
50import Test.Hspec
45import Test.Hspec
51import Test.Hspec.QuickCheck
46import Test.Hspec.QuickCheck
53import Test.QuickCheck
47import Test.QuickCheck
54import Web.FormUrlEncoded (FromForm, ToForm)
48import Web.FormUrlEncoded (FromForm, ToForm)
56import Servant.API
50import Servant.API
57−import Servant.API.Internal.Test.ComprehensiveAPI
58import Servant.Client
51import Servant.Client
52+import Servant.Client.TestServer
53+import Servant.Client.TestServer.GHC
54+import qualified Servant.Common.Req as SCR
59import Servant.Server
55import Servant.Server
60import Servant.Server.Experimental.Auth
56import Servant.Server.Experimental.Auth
61−import qualified Servant.Common.Req as SCR
63−-- This declaration simply checks that all instances are in place.
64−_ = client comprehensiveAPI
66spec :: Spec
58spec :: Spec
60+ runIO buildTestServer
67−spec = describe "Servant.Client" $ do
61+ describe "Servant.Client" $ do
68 sucessSpec
62 sucessSpec
71 basicAuthSpec
64 basicAuthSpec
72 genAuthSpec
65 genAuthSpec
68+-- | Run a test-server (identified by name) while performing the given action.
69+-- The provided 'BaseUrl' points to the running server.
71+-- Running the test-servers is done differently depending on the compiler
74+-- With ghc it's somewhat straight-forward: a wai 'Application' is being started
75+-- on a free port inside the same process using 'warp'.
77+-- When running the test-suite with ghcjs all the test-servers are compiled into
78+-- a single external executable (with ghc and warp). This is done through
79+-- 'buildTestServer' once at the start of the test-suite. This built executable
80+-- will provide all the test-servers on a free port under a path that
81+-- corresponds to the test-servers name, for example under
82+-- 'http://localhost:82923/failServer'. 'withTestServer' will then
83+-- start this executable as an external process while the given action is being
84+-- executed and provide it with the correct BaseUrl.
85+-- This rather cumbersome approach is taken because it's not easy to run a wai
86+-- Application as a http server when using ghcjs.
87+withTestServer :: String -> (BaseUrl -> IO a) -> IO a
88+withTestServer name action = do
89+ server <- lookupTestServer name
90+ withServer server action
92+lookupTestServer :: String -> IO TestServer
93+lookupTestServer name = case lookup name mapping of
94+ Nothing -> die ("test server not found: " ++ name)
95+ Just testServer -> return testServer
97+ mapping :: [(String, TestServer)]
98+ mapping = map (\ server -> (testServerName server, server)) allTestServers
100+-- | All test-servers must be registered here.
101+allTestServers :: [TestServer]
74-- * test data types
110-- * test data types
@@ -120,9 +156,9 @@ getBody :: Person -> SCR.ClientM Person
120getQueryParam :: Maybe String -> SCR.ClientM Person
156getQueryParam :: Maybe String -> SCR.ClientM Person
121getQueryParams :: [String] -> SCR.ClientM [Person]
157getQueryParams :: [String] -> SCR.ClientM [Person]
122getQueryFlag :: Bool -> SCR.ClientM Bool
158getQueryFlag :: Bool -> SCR.ClientM Bool
123−getRawSuccess :: HTTP.Method
159+getRawSuccess :: HTTP.Method
124 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
160 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
125−getRawFailure :: HTTP.Method
161+getRawFailure :: HTTP.Method
126 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
162 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
127getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
163getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
128 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
164 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
@@ -142,8 +178,8 @@ getGet
142 :<|> getRespHeaders
178 :<|> getRespHeaders
143 :<|> getDeleteContentType = client api
179 :<|> getDeleteContentType = client api
145−server :: Application
181+server :: TestServer
146−server = serve api (
182+server = TestServer "server" $ serve api (
147 return alice
183 return alice
148 :<|> return NoContent
184 :<|> return NoContent
149 :<|> (\ name -> return $ Person name 0)
185 :<|> (\ name -> return $ Person name 0)
@@ -162,19 +198,19 @@ server = serve api (
162 :<|> return NoContent
198 :<|> return NoContent
166type FailApi =
201type FailApi =
167 "get" :> Raw
202 "get" :> Raw
168 :<|> "capture" :> Capture "name" String :> Raw
203 :<|> "capture" :> Capture "name" String :> Raw
169 :<|> "body" :> Raw
204 :<|> "body" :> Raw
170failApi :: Proxy FailApi
206failApi :: Proxy FailApi
171failApi = Proxy
207failApi = Proxy
173−failServer :: Application
209+failServer :: TestServer
174−failServer = serve failApi (
210+failServer = TestServer "failServer" $ serve failApi (
175 (\ _request respond -> respond $ responseLBS HTTP.ok200 [] "")
211 (\ _request respond -> respond $ responseLBS HTTP.ok200 [] "")
176 :<|> (\ _capture _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "application/json")] "")
212 :<|> (\ _capture _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "application/json")] "")
177− :<|> (\_request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
213+ :<|> (\ _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
180-- * basic auth stuff
216-- * basic auth stuff
@@ -196,8 +232,9 @@ basicAuthHandler =
196basicServerContext :: Context '[ BasicAuthCheck () ]
232basicServerContext :: Context '[ BasicAuthCheck () ]
197basicServerContext = basicAuthHandler :. EmptyContext
233basicServerContext = basicAuthHandler :. EmptyContext
199−basicAuthServer :: Application
235+basicAuthServer :: TestServer
236+basicAuthServer = TestServer "basicAuthServer" $
200−basicAuthServer = serveWithContext basicAuthAPI basicServerContext (const (return alice))
237+ serveWithContext basicAuthAPI basicServerContext (const (return alice))
202-- * general auth stuff
239-- * general auth stuff
@@ -210,62 +247,63 @@ genAuthAPI = Proxy
210type instance AuthServerData (AuthProtect "auth-tag") = ()
247type instance AuthServerData (AuthProtect "auth-tag") = ()
211type instance AuthClientData (AuthProtect "auth-tag") = ()
248type instance AuthClientData (AuthProtect "auth-tag") = ()
213−genAuthHandler :: AuthHandler Request ()
250+genAuthHandler :: AuthHandler Wai.Request ()
214genAuthHandler =
251genAuthHandler =
215− let handler req = case lookup "AuthHeader" (requestHeaders req) of
252+ let handler req = case lookup "AuthHeader" (Wai.requestHeaders req) of
216 Nothing -> throwE (err401 { errBody = "Missing auth header" })
253 Nothing -> throwE (err401 { errBody = "Missing auth header" })
217 Just _ -> return ()
254 Just _ -> return ()
218 in mkAuthHandler handler
255 in mkAuthHandler handler
220−genAuthServerContext :: Context '[ AuthHandler Request () ]
257+genAuthServerContext :: Context '[ AuthHandler Wai.Request () ]
221genAuthServerContext = genAuthHandler :. EmptyContext
258genAuthServerContext = genAuthHandler :. EmptyContext
223−genAuthServer :: Application
260+genAuthServer :: TestServer
261+genAuthServer = TestServer "genAuthServer" $
224−genAuthServer = serveWithContext genAuthAPI genAuthServerContext (const (return alice))
262+ serveWithContext genAuthAPI genAuthServerContext (const (return alice))
226{-# NOINLINE manager #-}
264{-# NOINLINE manager #-}
227manager :: C.Manager
265manager :: C.Manager
228manager = unsafePerformIO $ C.newManager C.defaultManagerSettings
266manager = unsafePerformIO $ C.newManager C.defaultManagerSettings
230sucessSpec :: Spec
268sucessSpec :: Spec
231−sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
269+sucessSpec = around (withTestServer "server") $ do
233− it "Servant.API.Get" $ \(_, baseUrl) -> do
271+ it "Servant.API.Get" $ \baseUrl -> do
234 (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice
272 (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice
236 describe "Servant.API.Delete" $ do
274 describe "Servant.API.Delete" $ do
237− it "allows empty content type" $ \(_, baseUrl) -> do
275+ it "allows empty content type" $ \baseUrl -> do
238 (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
276 (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
240− it "allows content type" $ \(_, baseUrl) -> do
278+ it "allows content type" $ \baseUrl -> do
241 (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
279 (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
243− it "Servant.API.Capture" $ \(_, baseUrl) -> do
281+ it "Servant.API.Capture" $ \baseUrl -> do
244 (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0)
282 (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0)
246− it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do
284+ it "Servant.API.CaptureAll" $ \baseUrl -> do
247 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
285 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
248 (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected
286 (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected
250− it "Servant.API.ReqBody" $ \(_, baseUrl) -> do
288+ it "Servant.API.ReqBody" $ \baseUrl -> do
251 let p = Person "Clara" 42
289 let p = Person "Clara" 42
252 (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p
290 (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p
254− it "Servant.API.QueryParam" $ \(_, baseUrl) -> do
292+ it "Servant.API.QueryParam" $ \baseUrl -> do
255 left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice
293 left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice
256 Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl)
294 Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl)
257 responseStatus `shouldBe` HTTP.Status 400 "bob not found"
295 responseStatus `shouldBe` HTTP.Status 400 "bob not found"
259− it "Servant.API.QueryParam.QueryParams" $ \(_, baseUrl) -> do
297+ it "Servant.API.QueryParam.QueryParams" $ \baseUrl -> do
260 (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right []
298 (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right []
261 (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl))
299 (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl))
262 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
300 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
264 context "Servant.API.QueryParam.QueryFlag" $
302 context "Servant.API.QueryParam.QueryFlag" $
265− forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do
303+ forM_ [False, True] $ \ flag -> it (show flag) $ \baseUrl -> do
266 (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag
304 (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag
268− it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
306+ it "Servant.API.Raw on success" $ \baseUrl -> do
269 res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl)
307 res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl)
270 case res of
308 case res of
271 Left e -> assertFailure $ show e
309 Left e -> assertFailure $ show e
@@ -274,7 +312,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
274 C.responseBody response `shouldBe` body
312 C.responseBody response `shouldBe` body
275 C.responseStatus response `shouldBe` HTTP.ok200
313 C.responseStatus response `shouldBe` HTTP.ok200
277− it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
315+ it "Servant.API.Raw should return a Left in case of failure" $ \baseUrl -> do
278 res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl)
316 res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl)
279 case res of
317 case res of
280 Right _ -> assertFailure "expected Left, but got Right"
318 Right _ -> assertFailure "expected Left, but got Right"
@@ -282,51 +320,93 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
282 Servant.Client.responseStatus e `shouldBe` HTTP.status400
320 Servant.Client.responseStatus e `shouldBe` HTTP.status400
283 Servant.Client.responseBody e `shouldBe` "rawFailure"
321 Servant.Client.responseBody e `shouldBe` "rawFailure"
285− it "Returns headers appropriately" $ \(_, baseUrl) -> do
323+ it "Returns headers appropriately" $ \baseUrl -> do
286 res <- runClientM getRespHeaders (ClientEnv manager baseUrl)
324 res <- runClientM getRespHeaders (ClientEnv manager baseUrl)
287 case res of
325 case res of
288 Left e -> assertFailure $ show e
326 Left e -> assertFailure $ show e
289 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
327 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
291 modifyMaxSuccess (const 20) $ do
329 modifyMaxSuccess (const 20) $ do
292− it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
330+ it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \baseUrl ->
293 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
331 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
294 ioProperty $ do
332 ioProperty $ do
295 result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl)
333 result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl)
297 result === Right (cap, num, flag, body)
335 result === Right (cap, num, flag, body)
338+ Delete '[JSON] () :<|>
340+ Post '[JSON] () :<|>
343+errorApi :: Proxy ErrorApi
346+errorServer :: TestServer
347+errorServer = TestServer "errorServer" $ serve errorApi $
348+ err :<|> err :<|> err :<|> err
350+ err = throwE $ ServantErr 500 "error message" "" []
354+ around (withTestServer "errorServer") $ do
355+ describe "error status codes" $
356+ it "reports error statuses correctly" $ \baseUrl -> do
357+ let delete :<|> get :<|> post :<|> put =
359+ actions = [delete, get, post, put]
360+ forM_ actions $ \ clientAction -> do
361+ Left FailureResponse{..} <- runClientM clientAction (ClientEnv manager baseUrl)
362+ responseStatus `shouldBe` HTTP.Status 500 "error message"
364+basicAuthSpec :: Spec
365+basicAuthSpec = around (withTestServer "basicAuthServer") $ do
366+ context "Authentication works when requests are properly authenticated" $ do
368+ it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
369+ let getBasic = client basicAuthAPI
370+ let basicAuthData = BasicAuthData "servant" "server"
371+ (left show <$> runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
373+ context "Authentication is rejected when requests are not authenticated properly" $ do
375+ it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
376+ let getBasic = client basicAuthAPI
377+ let basicAuthData = BasicAuthData "not" "password"
378+ Left FailureResponse{..} <- runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)
379+ responseStatus `shouldBe` HTTP.Status 403 "Forbidden"
382+genAuthSpec = around (withTestServer "genAuthServer") $ do
383+ context "Authentication works when requests are properly authenticated" $ do
385+ it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
386+ let getProtected = client genAuthAPI
387+ let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "AuthHeader" ("cool" :: String) req)
388+ (left show <$> runClientM (getProtected authRequest) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
300−wrappedApiSpec :: Spec
390+ context "Authentication is rejected when requests are not authenticated properly" $ do
301−wrappedApiSpec = describe "error status codes" $ do
391+
302− let serveW api = serve api $ throwE $ ServantErr 500 "error message" "" []
392+ it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
303− context "are correctly handled by the client" $
393+ let getProtected = client genAuthAPI
304− let test :: (WrappedApi, String) -> Spec
394+ let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "Wrong" ("header" :: String) req)
305− test (WrappedApi api, desc) =
306− it desc $ bracket (startWaiApp $ serveW api) endWaiApp $ \(_, baseUrl) -> do
307− let getResponse :: SCR.ClientM ()
308− getResponse = client api
309− Left FailureResponse{..} <- runClientM getResponse (ClientEnv manager baseUrl)
395+ Left FailureResponse{..} <- runClientM (getProtected authRequest) (ClientEnv manager baseUrl)
310− responseStatus `shouldBe` (HTTP.Status 500 "error message")
396+ responseStatus `shouldBe` (HTTP.Status 401 "Unauthorized")
312− (WrappedApi (Proxy :: Proxy (Delete '[JSON] ())), "Delete") :
313− (WrappedApi (Proxy :: Proxy (Get '[JSON] ())), "Get") :
314− (WrappedApi (Proxy :: Proxy (Post '[JSON] ())), "Post") :
315− (WrappedApi (Proxy :: Proxy (Put '[JSON] ())), "Put") :
318failSpec :: Spec
398failSpec :: Spec
319−failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
399+failSpec = around (withTestServer "failServer") $ do
321 context "client returns errors appropriately" $ do
401 context "client returns errors appropriately" $ do
322− it "reports FailureResponse" $ \(_, baseUrl) -> do
402+ it "reports FailureResponse" $ \baseUrl -> do
323 let (_ :<|> getDeleteEmpty :<|> _) = client api
403 let (_ :<|> getDeleteEmpty :<|> _) = client api
324 Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl)
404 Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl)
325 case res of
405 case res of
326 FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return ()
406 FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return ()
327 _ -> fail $ "expected 404 response, but got " <> show res
407 _ -> fail $ "expected 404 response, but got " <> show res
329− it "reports DecodeFailure" $ \(_, baseUrl) -> do
409+ it "reports DecodeFailure" $ \baseUrl -> do
330 let (_ :<|> _ :<|> getCapture :<|> _) = client api
410 let (_ :<|> _ :<|> getCapture :<|> _) = client api
331 Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl)
411 Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl)
332 case res of
412 case res of
@@ -340,81 +420,23 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
340 ConnectionError _ -> return ()
420 ConnectionError _ -> return ()
341 _ -> fail $ "expected ConnectionError, but got " <> show res
421 _ -> fail $ "expected ConnectionError, but got " <> show res
343− it "reports UnsupportedContentType" $ \(_, baseUrl) -> do
423+ it "reports UnsupportedContentType" $ \baseUrl -> do
344 let (getGet :<|> _ ) = client api
424 let (getGet :<|> _ ) = client api
345 Left res <- runClientM getGet (ClientEnv manager baseUrl)
425 Left res <- runClientM getGet (ClientEnv manager baseUrl)
346 case res of
426 case res of
347 UnsupportedContentType ("application/octet-stream") _ -> return ()
427 UnsupportedContentType ("application/octet-stream") _ -> return ()
348 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
428 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
350− it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
430+ it "reports InvalidContentTypeHeader" $ \baseUrl -> do
351 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
431 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
352 Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl)
432 Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl)
353 case res of
434 case res of
354 InvalidContentTypeHeader "fooooo" _ -> return ()
435 InvalidContentTypeHeader "fooooo" _ -> return ()
355 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
436 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
357−data WrappedApi where
358− WrappedApi :: (HasServer (api :: *) '[], Server api ~ Handler a,
359− HasClient api, Client api ~ SCR.ClientM ()) =>
360− Proxy api -> WrappedApi
362−basicAuthSpec :: Spec
363−basicAuthSpec = beforeAll (startWaiApp basicAuthServer) $ afterAll endWaiApp $ do
364− context "Authentication works when requests are properly authenticated" $ do
366− it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
367− let getBasic = client basicAuthAPI
368− let basicAuthData = BasicAuthData "servant" "server"
369− (left show <$> runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
371− context "Authentication is rejected when requests are not authenticated properly" $ do
373− it "Authenticates a BasicAuth protected server appropriately" $ \(_,baseUrl) -> do
374− let getBasic = client basicAuthAPI
375− let basicAuthData = BasicAuthData "not" "password"
376− Left FailureResponse{..} <- runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)
377− responseStatus `shouldBe` HTTP.Status 403 "Forbidden"
380−genAuthSpec = beforeAll (startWaiApp genAuthServer) $ afterAll endWaiApp $ do
381− context "Authentication works when requests are properly authenticated" $ do
383− it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
384− let getProtected = client genAuthAPI
385− let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "AuthHeader" ("cool" :: String) req)
386− (left show <$> runClientM (getProtected authRequest) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
388− context "Authentication is rejected when requests are not authenticated properly" $ do
390− it "Authenticates a AuthProtect protected server appropriately" $ \(_, baseUrl) -> do
391− let getProtected = client genAuthAPI
392− let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "Wrong" ("header" :: String) req)
393− Left FailureResponse{..} <- runClientM (getProtected authRequest) (ClientEnv manager baseUrl)
394− responseStatus `shouldBe` (HTTP.Status 401 "Unauthorized")
396-- * utils
438-- * utils
398−startWaiApp :: Application -> IO (ThreadId, BaseUrl)
400− (port, socket) <- openTestSocket
401− let settings = setPort port $ defaultSettings
402− thread <- forkIO $ runSettingsSocket settings socket app
403− return (thread, BaseUrl Http "localhost" port "")
406−endWaiApp :: (ThreadId, BaseUrl) -> IO ()
407−endWaiApp (thread, _) = killThread thread
409−openTestSocket :: IO (Port, Socket)
411− s <- socket AF_INET Stream defaultProtocol
412− localhost <- inet_addr "127.0.0.1"
413− bind s (SockAddrInet aNY_PORT localhost)
415− port <- socketPort s
416− return (fromIntegral port, s)
418pathGen :: Gen (NonEmptyList Char)
440pathGen :: Gen (NonEmptyList Char)
419pathGen = fmap NonEmpty path
441pathGen = fmap NonEmpty path