@@ -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,75 @@ 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 qualified Servant.Common.Req as SCR
59import Servant.Server
54import Servant.Server
60import Servant.Server.Experimental.Auth
55import 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
57spec :: Spec
59+ runIO buildTestServer
67−spec = describe "Servant.Client" $ do
60+ describe "Servant.Client" $ do
68 sucessSpec
61 sucessSpec
71 basicAuthSpec
63 basicAuthSpec
72 genAuthSpec
64 genAuthSpec
67+-- | Run a test-server (identified by name) while performing the given action.
68+-- The provided 'BaseUrl' points to the running server.
70+-- Running the test-servers is done differently depending on the compiler
73+-- With ghc it's somewhat straight-forward: a wai 'Application' is being started
74+-- on a free port inside the same process using 'warp'.
76+-- When running the test-suite with ghcjs all the test-servers are compiled into
77+-- a single external executable (with ghc and warp). This is done through
78+-- 'buildTestServer' once at the start of the test-suite. This built executable
79+-- will provide all the test-servers on a free port under a path that
80+-- corresponds to the test-servers name, for example under
81+-- 'http://localhost:82923/failServer'. 'withTestServer' will then
82+-- start this executable as an external process while the given action is being
83+-- executed and provide it with the correct BaseUrl.
84+-- This rather cumbersome approach is taken because it's not easy to run a wai
85+-- Application as a http server when using ghcjs.
86+withTestServer :: String -> (BaseUrl -> IO a) -> IO a
87+withTestServer name action = do
88+ server <- lookupTestServer name
89+ withServer server action
91+lookupTestServer :: String -> IO TestServer
92+lookupTestServer name = case lookup name mapping of
93+ Nothing -> die ("test server not found: " ++ name)
94+ Just testServer -> return testServer
96+ mapping :: [(String, TestServer)]
97+ mapping = map (\ server -> (testServerName server, server)) allTestServers
99+-- | All test-servers must be registered here.
100+allTestServers :: [TestServer]
74-- * test data types
109-- * test data types
@@ -120,9 +155,9 @@ getBody :: Person -> SCR.ClientM Person
120getQueryParam :: Maybe String -> SCR.ClientM Person
155getQueryParam :: Maybe String -> SCR.ClientM Person
121getQueryParams :: [String] -> SCR.ClientM [Person]
156getQueryParams :: [String] -> SCR.ClientM [Person]
122getQueryFlag :: Bool -> SCR.ClientM Bool
157getQueryFlag :: Bool -> SCR.ClientM Bool
123−getRawSuccess :: HTTP.Method
158+getRawSuccess :: HTTP.Method
124 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
159 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
125−getRawFailure :: HTTP.Method
160+getRawFailure :: HTTP.Method
126 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
161 -> SCR.ClientM (Int, BS.ByteString, MediaType, [HTTP.Header], C.Response BS.ByteString)
127getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
162getMultiple :: String -> Maybe Int -> Bool -> [(String, [Rational])]
128 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
163 -> SCR.ClientM (String, Maybe Int, Bool, [(String, [Rational])])
@@ -142,8 +177,8 @@ getGet
142 :<|> getRespHeaders
177 :<|> getRespHeaders
143 :<|> getDeleteContentType = client api
178 :<|> getDeleteContentType = client api
145−server :: Application
180+server :: TestServer
146−server = serve api (
181+server = TestServer "server" $ serve api (
147 return alice
182 return alice
148 :<|> return NoContent
183 :<|> return NoContent
149 :<|> (\ name -> return $ Person name 0)
184 :<|> (\ name -> return $ Person name 0)
@@ -162,19 +197,19 @@ server = serve api (
162 :<|> return NoContent
197 :<|> return NoContent
166type FailApi =
200type FailApi =
167 "get" :> Raw
201 "get" :> Raw
168 :<|> "capture" :> Capture "name" String :> Raw
202 :<|> "capture" :> Capture "name" String :> Raw
169 :<|> "body" :> Raw
203 :<|> "body" :> Raw
170failApi :: Proxy FailApi
205failApi :: Proxy FailApi
171failApi = Proxy
206failApi = Proxy
173−failServer :: Application
208+failServer :: TestServer
174−failServer = serve failApi (
209+failServer = TestServer "failServer" $ serve failApi (
175 (\ _request respond -> respond $ responseLBS HTTP.ok200 [] "")
210 (\ _request respond -> respond $ responseLBS HTTP.ok200 [] "")
176 :<|> (\ _capture _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "application/json")] "")
211 :<|> (\ _capture _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "application/json")] "")
177− :<|> (\_request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
212+ :<|> (\ _request respond -> respond $ responseLBS HTTP.ok200 [("content-type", "fooooo")] "")
180-- * basic auth stuff
215-- * basic auth stuff
@@ -196,8 +231,9 @@ basicAuthHandler =
196basicServerContext :: Context '[ BasicAuthCheck () ]
231basicServerContext :: Context '[ BasicAuthCheck () ]
197basicServerContext = basicAuthHandler :. EmptyContext
232basicServerContext = basicAuthHandler :. EmptyContext
199−basicAuthServer :: Application
234+basicAuthServer :: TestServer
235+basicAuthServer = TestServer "basicAuthServer" $
200−basicAuthServer = serveWithContext basicAuthAPI basicServerContext (const (return alice))
236+ serveWithContext basicAuthAPI basicServerContext (const (return alice))
202-- * general auth stuff
238-- * general auth stuff
@@ -210,62 +246,63 @@ genAuthAPI = Proxy
210type instance AuthServerData (AuthProtect "auth-tag") = ()
246type instance AuthServerData (AuthProtect "auth-tag") = ()
211type instance AuthClientData (AuthProtect "auth-tag") = ()
247type instance AuthClientData (AuthProtect "auth-tag") = ()
213−genAuthHandler :: AuthHandler Request ()
249+genAuthHandler :: AuthHandler Wai.Request ()
214genAuthHandler =
250genAuthHandler =
215− let handler req = case lookup "AuthHeader" (requestHeaders req) of
251+ let handler req = case lookup "AuthHeader" (Wai.requestHeaders req) of
216 Nothing -> throwE (err401 { errBody = "Missing auth header" })
252 Nothing -> throwE (err401 { errBody = "Missing auth header" })
217 Just _ -> return ()
253 Just _ -> return ()
218 in mkAuthHandler handler
254 in mkAuthHandler handler
220−genAuthServerContext :: Context '[ AuthHandler Request () ]
256+genAuthServerContext :: Context '[ AuthHandler Wai.Request () ]
221genAuthServerContext = genAuthHandler :. EmptyContext
257genAuthServerContext = genAuthHandler :. EmptyContext
223−genAuthServer :: Application
259+genAuthServer :: TestServer
260+genAuthServer = TestServer "genAuthServer" $
224−genAuthServer = serveWithContext genAuthAPI genAuthServerContext (const (return alice))
261+ serveWithContext genAuthAPI genAuthServerContext (const (return alice))
226{-# NOINLINE manager #-}
263{-# NOINLINE manager #-}
227manager :: C.Manager
264manager :: C.Manager
228manager = unsafePerformIO $ C.newManager C.defaultManagerSettings
265manager = unsafePerformIO $ C.newManager C.defaultManagerSettings
230sucessSpec :: Spec
267sucessSpec :: Spec
231−sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
268+sucessSpec = around (withTestServer "server") $ do
233− it "Servant.API.Get" $ \(_, baseUrl) -> do
270+ it "Servant.API.Get" $ \baseUrl -> do
234 (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice
271 (left show <$> (runClientM getGet (ClientEnv manager baseUrl))) `shouldReturn` Right alice
236 describe "Servant.API.Delete" $ do
273 describe "Servant.API.Delete" $ do
237− it "allows empty content type" $ \(_, baseUrl) -> do
274+ it "allows empty content type" $ \baseUrl -> do
238 (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
275 (left show <$> (runClientM getDeleteEmpty (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
240− it "allows content type" $ \(_, baseUrl) -> do
277+ it "allows content type" $ \baseUrl -> do
241 (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
278 (left show <$> (runClientM getDeleteContentType (ClientEnv manager baseUrl))) `shouldReturn` Right NoContent
243− it "Servant.API.Capture" $ \(_, baseUrl) -> do
280+ it "Servant.API.Capture" $ \baseUrl -> do
244 (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0)
281 (left show <$> (runClientM (getCapture "Paula") (ClientEnv manager baseUrl))) `shouldReturn` Right (Person "Paula" 0)
246− it "Servant.API.CaptureAll" $ \(_, baseUrl) -> do
283+ it "Servant.API.CaptureAll" $ \baseUrl -> do
247 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
284 let expected = [(Person "Paula" 0), (Person "Peta" 1)]
248 (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected
285 (left show <$> (runClientM (getCaptureAll ["Paula", "Peta"]) (ClientEnv manager baseUrl))) `shouldReturn` Right expected
250− it "Servant.API.ReqBody" $ \(_, baseUrl) -> do
287+ it "Servant.API.ReqBody" $ \baseUrl -> do
251 let p = Person "Clara" 42
288 let p = Person "Clara" 42
252 (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p
289 (left show <$> runClientM (getBody p) (ClientEnv manager baseUrl)) `shouldReturn` Right p
254− it "Servant.API.QueryParam" $ \(_, baseUrl) -> do
291+ it "Servant.API.QueryParam" $ \baseUrl -> do
255 left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice
292 left show <$> runClientM (getQueryParam (Just "alice")) (ClientEnv manager baseUrl) `shouldReturn` Right alice
256 Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl)
293 Left FailureResponse{..} <- runClientM (getQueryParam (Just "bob")) (ClientEnv manager baseUrl)
257 responseStatus `shouldBe` HTTP.Status 400 "bob not found"
294 responseStatus `shouldBe` HTTP.Status 400 "bob not found"
259− it "Servant.API.QueryParam.QueryParams" $ \(_, baseUrl) -> do
296+ it "Servant.API.QueryParam.QueryParams" $ \baseUrl -> do
260 (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right []
297 (left show <$> runClientM (getQueryParams []) (ClientEnv manager baseUrl)) `shouldReturn` Right []
261 (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl))
298 (left show <$> runClientM (getQueryParams ["alice", "bob"]) (ClientEnv manager baseUrl))
262 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
299 `shouldReturn` Right [Person "alice" 0, Person "bob" 1]
264 context "Servant.API.QueryParam.QueryFlag" $
301 context "Servant.API.QueryParam.QueryFlag" $
265− forM_ [False, True] $ \ flag -> it (show flag) $ \(_, baseUrl) -> do
302+ forM_ [False, True] $ \ flag -> it (show flag) $ \baseUrl -> do
266 (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag
303 (left show <$> runClientM (getQueryFlag flag) (ClientEnv manager baseUrl)) `shouldReturn` Right flag
268− it "Servant.API.Raw on success" $ \(_, baseUrl) -> do
305+ it "Servant.API.Raw on success" $ \baseUrl -> do
269 res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl)
306 res <- runClientM (getRawSuccess HTTP.methodGet) (ClientEnv manager baseUrl)
270 case res of
307 case res of
271 Left e -> assertFailure $ show e
308 Left e -> assertFailure $ show e
@@ -274,7 +311,7 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
274 C.responseBody response `shouldBe` body
311 C.responseBody response `shouldBe` body
275 C.responseStatus response `shouldBe` HTTP.ok200
312 C.responseStatus response `shouldBe` HTTP.ok200
277− it "Servant.API.Raw should return a Left in case of failure" $ \(_, baseUrl) -> do
314+ it "Servant.API.Raw should return a Left in case of failure" $ \baseUrl -> do
278 res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl)
315 res <- runClientM (getRawFailure HTTP.methodGet) (ClientEnv manager baseUrl)
279 case res of
316 case res of
280 Right _ -> assertFailure "expected Left, but got Right"
317 Right _ -> assertFailure "expected Left, but got Right"
@@ -282,51 +319,93 @@ sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do
282 Servant.Client.responseStatus e `shouldBe` HTTP.status400
319 Servant.Client.responseStatus e `shouldBe` HTTP.status400
283 Servant.Client.responseBody e `shouldBe` "rawFailure"
320 Servant.Client.responseBody e `shouldBe` "rawFailure"
285− it "Returns headers appropriately" $ \(_, baseUrl) -> do
322+ it "Returns headers appropriately" $ \baseUrl -> do
286 res <- runClientM getRespHeaders (ClientEnv manager baseUrl)
323 res <- runClientM getRespHeaders (ClientEnv manager baseUrl)
287 case res of
324 case res of
288 Left e -> assertFailure $ show e
325 Left e -> assertFailure $ show e
289 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
326 Right val -> getHeaders val `shouldBe` [("X-Example1", "1729"), ("X-Example2", "eg2")]
291 modifyMaxSuccess (const 20) $ do
328 modifyMaxSuccess (const 20) $ do
292− it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \(_, baseUrl) ->
329+ it "works for a combination of Capture, QueryParam, QueryFlag and ReqBody" $ \baseUrl ->
293 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
330 property $ forAllShrink pathGen shrink $ \(NonEmpty cap) num flag body ->
294 ioProperty $ do
331 ioProperty $ do
295 result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl)
332 result <- left show <$> runClientM (getMultiple cap num flag body) (ClientEnv manager baseUrl)
297 result === Right (cap, num, flag, body)
334 result === Right (cap, num, flag, body)
337+ Delete '[JSON] () :<|>
339+ Post '[JSON] () :<|>
342+errorApi :: Proxy ErrorApi
345+errorServer :: TestServer
346+errorServer = TestServer "errorServer" $ serve errorApi $
347+ err :<|> err :<|> err :<|> err
349+ err = throwE $ ServantErr 500 "error message" "" []
353+ around (withTestServer "errorServer") $ do
354+ describe "error status codes" $
355+ it "reports error statuses correctly" $ \baseUrl -> do
356+ let delete :<|> get :<|> post :<|> put =
358+ actions = [delete, get, post, put]
359+ forM_ actions $ \ clientAction -> do
360+ Left FailureResponse{..} <- runClientM clientAction (ClientEnv manager baseUrl)
361+ responseStatus `shouldBe` HTTP.Status 500 "error message"
363+basicAuthSpec :: Spec
364+basicAuthSpec = around (withTestServer "basicAuthServer") $ do
365+ context "Authentication works when requests are properly authenticated" $ do
367+ it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
368+ let getBasic = client basicAuthAPI
369+ let basicAuthData = BasicAuthData "servant" "server"
370+ (left show <$> runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
372+ context "Authentication is rejected when requests are not authenticated properly" $ do
374+ it "Authenticates a BasicAuth protected server appropriately" $ \baseUrl -> do
375+ let getBasic = client basicAuthAPI
376+ let basicAuthData = BasicAuthData "not" "password"
377+ Left FailureResponse{..} <- runClientM (getBasic basicAuthData) (ClientEnv manager baseUrl)
378+ responseStatus `shouldBe` HTTP.Status 403 "Forbidden"
381+genAuthSpec = around (withTestServer "genAuthServer") $ do
382+ context "Authentication works when requests are properly authenticated" $ do
384+ it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
385+ let getProtected = client genAuthAPI
386+ let authRequest = mkAuthenticateReq () (\_ req -> SCR.addHeader "AuthHeader" ("cool" :: String) req)
387+ (left show <$> runClientM (getProtected authRequest) (ClientEnv manager baseUrl)) `shouldReturn` Right alice
300−wrappedApiSpec :: Spec
389+ context "Authentication is rejected when requests are not authenticated properly" $ do
301−wrappedApiSpec = describe "error status codes" $ do
390+
302− let serveW api = serve api $ throwE $ ServantErr 500 "error message" "" []
391+ it "Authenticates a AuthProtect protected server appropriately" $ \baseUrl -> do
303− context "are correctly handled by the client" $
392+ let getProtected = client genAuthAPI
304− let test :: (WrappedApi, String) -> Spec
393+ 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)
394+ Left FailureResponse{..} <- runClientM (getProtected authRequest) (ClientEnv manager baseUrl)
310− responseStatus `shouldBe` (HTTP.Status 500 "error message")
395+ 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
397failSpec :: Spec
319−failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
398+failSpec = around (withTestServer "failServer") $ do
321 context "client returns errors appropriately" $ do
400 context "client returns errors appropriately" $ do
322− it "reports FailureResponse" $ \(_, baseUrl) -> do
401+ it "reports FailureResponse" $ \baseUrl -> do
323 let (_ :<|> getDeleteEmpty :<|> _) = client api
402 let (_ :<|> getDeleteEmpty :<|> _) = client api
324 Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl)
403 Left res <- runClientM getDeleteEmpty (ClientEnv manager baseUrl)
325 case res of
404 case res of
326 FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return ()
405 FailureResponse (HTTP.Status 404 "Not Found") _ _ -> return ()
327 _ -> fail $ "expected 404 response, but got " <> show res
406 _ -> fail $ "expected 404 response, but got " <> show res
329− it "reports DecodeFailure" $ \(_, baseUrl) -> do
408+ it "reports DecodeFailure" $ \baseUrl -> do
330 let (_ :<|> _ :<|> getCapture :<|> _) = client api
409 let (_ :<|> _ :<|> getCapture :<|> _) = client api
331 Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl)
410 Left res <- runClientM (getCapture "foo") (ClientEnv manager baseUrl)
332 case res of
411 case res of
@@ -340,81 +419,23 @@ failSpec = beforeAll (startWaiApp failServer) $ afterAll endWaiApp $ do
340 ConnectionError _ -> return ()
419 ConnectionError _ -> return ()
341 _ -> fail $ "expected ConnectionError, but got " <> show res
420 _ -> fail $ "expected ConnectionError, but got " <> show res
343− it "reports UnsupportedContentType" $ \(_, baseUrl) -> do
422+ it "reports UnsupportedContentType" $ \baseUrl -> do
344 let (getGet :<|> _ ) = client api
423 let (getGet :<|> _ ) = client api
345 Left res <- runClientM getGet (ClientEnv manager baseUrl)
424 Left res <- runClientM getGet (ClientEnv manager baseUrl)
346 case res of
425 case res of
347 UnsupportedContentType ("application/octet-stream") _ -> return ()
426 UnsupportedContentType ("application/octet-stream") _ -> return ()
348 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
427 _ -> fail $ "expected UnsupportedContentType, but got " <> show res
350− it "reports InvalidContentTypeHeader" $ \(_, baseUrl) -> do
429+ it "reports InvalidContentTypeHeader" $ \baseUrl -> do
351 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
430 let (_ :<|> _ :<|> _ :<|> _ :<|> getBody :<|> _) = client api
352 Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl)
431 Left res <- runClientM (getBody alice) (ClientEnv manager baseUrl)
353 case res of
433 case res of
354 InvalidContentTypeHeader "fooooo" _ -> return ()
434 InvalidContentTypeHeader "fooooo" _ -> return ()
355 _ -> fail $ "expected InvalidContentTypeHeader, but got " <> show res
435 _ -> 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
437-- * 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)
439pathGen :: Gen (NonEmptyList Char)
419pathGen = fmap NonEmpty path
440pathGen = fmap NonEmpty path