@@ -3,8 +3,10 @@
3{-# LANGUAGE DeriveGeneric #-}
3{-# LANGUAGE DeriveGeneric #-}
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE OverloadedStrings #-}
5{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE PolyKinds #-}
6{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE ScopedTypeVariables #-}
7{-# LANGUAGE TypeOperators #-}
8{-# LANGUAGE TypeOperators #-}
9+{-# LANGUAGE TypeFamilies #-}
8{-# LANGUAGE TypeSynonymInstances #-}
10{-# LANGUAGE TypeSynonymInstances #-}
9{-# LANGUAGE FlexibleInstances #-}
11{-# LANGUAGE FlexibleInstances #-}
@@ -13,7 +15,7 @@ module Servant.ServerSpec where
13#if !MIN_VERSION_base(4,8,0)
15#if !MIN_VERSION_base(4,8,0)
14import Control.Applicative ((<$>))
16import Control.Applicative ((<$>))
16−import Control.Monad (forM_, when)
18+import Control.Monad (forM_, when, unless)
17import Control.Monad.Trans.Except (ExceptT, throwE)
19import Control.Monad.Trans.Except (ExceptT, throwE)
18import Data.Aeson (FromJSON, ToJSON, decode', encode)
20import Data.Aeson (FromJSON, ToJSON, decode', encode)
19import Data.ByteString.Conversion ()
21import Data.ByteString.Conversion ()
@@ -23,82 +25,144 @@ import Data.String (fromString)
23import Data.String.Conversions (cs)
25import Data.String.Conversions (cs)
24import qualified Data.Text as T
26import qualified Data.Text as T
25import GHC.Generics (Generic)
27import GHC.Generics (Generic)
26−import Network.HTTP.Types (hAccept, hContentType,
28+import Network.HTTP.Types (Status (..), hAccept, hContentType,
27− methodDelete, methodGet, methodHead,
29+ methodDelete, methodGet,
30+ methodHead, methodPatch,
28− methodPatch, methodPost, methodPut,
31+ methodPost, methodPut, ok200,
29− ok200, parseQuery, Status(..))
32+ parseQuery)
30import Network.Wai (Application, Request, pathInfo,
33import Network.Wai (Application, Request, pathInfo,
31 queryString, rawQueryString,
34 queryString, rawQueryString,
32− responseLBS, responseBuilder)
35+ responseBuilder, responseLBS)
33−import Network.Wai.Internal (Response(ResponseBuilder))
36+import Network.Wai.Internal (Response (ResponseBuilder))
34import Network.Wai.Test (defaultRequest, request,
37import Network.Wai.Test (defaultRequest, request,
35− runSession, simpleBody)
38+ runSession, simpleBody,
39+ simpleHeaders, simpleStatus)
36import Servant.API ((:<|>) (..), (:>), Capture, Delete,
40import Servant.API ((:<|>) (..), (:>), Capture, Delete,
37− Get, Header (..), Headers,
41+ Get, Header (..),
42+ Headers, HttpVersion,
38− HttpVersion, IsSecure (..), JSON,
43+ IsSecure (..), JSON,
39− Patch, PlainText, Post, Put,
44+ NoContent (..), Patch, PlainText,
40 QueryFlag, QueryParam, QueryParams,
46 QueryFlag, QueryParam, QueryParams,
41 Raw, RemoteHost, ReqBody,
47 Raw, RemoteHost, ReqBody,
42− addHeader)
48+ StdMethod (..), Verb, addHeader)
43−import Servant.Server (Server, serve, ServantErr(..), err404)
49+import Servant.Server (ServantErr (..), Server, err404,
44−import Test.Hspec (Spec, describe, it, shouldBe)
51+import Test.Hspec (Spec, context, describe, it,
52+ shouldBe, shouldContain)
45import Test.Hspec.Wai (get, liftIO, matchHeaders,
53import Test.Hspec.Wai (get, liftIO, matchHeaders,
46− matchStatus, post, request,
54+ matchStatus, request,
47 shouldRespondWith, with, (<:>))
55 shouldRespondWith, with, (<:>))
48−import Servant.Server.Internal.RoutingApplication (toApplication, RouteResult(..))
57+import Servant.Server.Internal.RoutingApplication
58+ (toApplication, RouteResult(..))
49import Servant.Server.Internal.Router
59import Servant.Server.Internal.Router
50 (tweakResponse, runRouter,
60 (tweakResponse, runRouter,
51 Router, Router'(LeafRouter))
61 Router, Router'(LeafRouter))
54−-- * test data types
64+-- * Specs
56−data Person = Person {
60− deriving (Eq, Show, Generic)
62−instance ToJSON Person
63−instance FromJSON Person
66−alice = Person "Alice" 42
68−data Animal = Animal {
70− numberOfLegs :: Integer
72− deriving (Eq, Show, Generic)
74−instance ToJSON Animal
75−instance FromJSON Animal
78−jerry = Animal "Mouse" 4
81−tweety = Animal "Bird" 2
86spec :: Spec
66spec :: Spec
88 captureSpec
69 captureSpec
94 queryParamSpec
70 queryParamSpec
95 headerSpec
72 headerSpec
97− unionSpec
74+ alternativeSpec
99 responseHeadersSpec
75 responseHeadersSpec
100− miscReqCombinatorsSpec
76+ routerSpec
101−
77+ miscCombinatorSpec
79+------------------------------------------------------------------------------
81+------------------------------------------------------------------------------
83+type VerbApi method status
84+ = Verb method status '[JSON] Person
85+ :<|> "noContent" :> Verb method status '[JSON] NoContent
86+ :<|> "header" :> Verb method status '[JSON] (Headers '[Header "H" Int] Person)
87+ :<|> "headerNC" :> Verb method status '[JSON] (Headers '[Header "H" Int] NoContent)
90+verbSpec = describe "Servant.API.Verb" $ do
91+ let server :: Server (VerbApi method status)
92+ server = return alice
93+ :<|> return NoContent
94+ :<|> return (addHeader 5 alice)
95+ :<|> return (addHeader 10 NoContent)
96+ get200 = Proxy :: Proxy (VerbApi 'GET 200)
97+ post210 = Proxy :: Proxy (VerbApi 'POST 210)
98+ put203 = Proxy :: Proxy (VerbApi 'PUT 203)
99+ delete280 = Proxy :: Proxy (VerbApi 'DELETE 280)
100+ patch214 = Proxy :: Proxy (VerbApi 'PATCH 214)
101+ wrongMethod m = if m == methodPatch then methodPost else methodPatch
102+ test desc api method (status :: Int) = context desc $
104+ with (return $ serve api server) $ do
106+ -- HEAD and 214/215 need not return bodies
107+ unless (status `elem` [214, 215] || method == methodHead) $
108+ it "returns the person" $ do
109+ response <- Test.Hspec.Wai.request method "/" [] ""
110+ liftIO $ statusCode (simpleStatus response) `shouldBe` status
111+ liftIO $ decode' (simpleBody response) `shouldBe` Just alice
113+ it "returns no content on NoContent" $ do
114+ response <- Test.Hspec.Wai.request method "/noContent" [] ""
115+ liftIO $ statusCode (simpleStatus response) `shouldBe` status
116+ liftIO $ simpleBody response `shouldBe` ""
118+ -- HEAD should not return body
119+ when (method == methodHead) $
120+ it "HEAD returns no content body" $ do
121+ response <- Test.Hspec.Wai.request method "/" [] ""
122+ liftIO $ simpleBody response `shouldBe` ""
124+ it "throws 405 on wrong method " $ do
125+ Test.Hspec.Wai.request (wrongMethod method) "/" [] ""
126+ `shouldRespondWith` 405
128+ it "returns headers" $ do
129+ response1 <- Test.Hspec.Wai.request method "/header" [] ""
130+ liftIO $ statusCode (simpleStatus response1) `shouldBe` status
131+ liftIO $ simpleHeaders response1 `shouldContain` [("H", "5")]
133+ response2 <- Test.Hspec.Wai.request method "/header" [] ""
134+ liftIO $ statusCode (simpleStatus response2) `shouldBe` status
135+ liftIO $ simpleHeaders response2 `shouldContain` [("H", "5")]
137+ it "handles trailing '/' gracefully" $ do
138+ response <- Test.Hspec.Wai.request method "/headerNC/" [] ""
139+ liftIO $ statusCode (simpleStatus response) `shouldBe` status
141+ it "returns 406 if the Accept header is not supported" $ do
142+ Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] ""
143+ `shouldRespondWith` 406
145+ it "responds if the Accept header is supported" $ do
146+ response <- Test.Hspec.Wai.request method ""
147+ [(hAccept, "application/json")] ""
148+ liftIO $ statusCode (simpleStatus response) `shouldBe` status
150+ it "sets the Content-Type header" $ do
151+ response <- Test.Hspec.Wai.request method "" [] ""
152+ liftIO $ simpleHeaders response `shouldContain`
153+ [("Content-Type", "application/json")]
155+ test "GET 200" get200 methodGet 200
156+ test "POST 210" post210 methodPost 210
157+ test "PUT 203" put203 methodPut 203
158+ test "DELETE 280" delete280 methodDelete 280
159+ test "PATCH 214" patch214 methodPatch 214
160+ test "GET 200 with HEAD" get200 methodHead 200
163+------------------------------------------------------------------------------
165+------------------------------------------------------------------------------
103type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
167type CaptureApi = Capture "legs" Integer :> Get '[JSON] Animal
104captureApi :: Proxy CaptureApi
168captureApi :: Proxy CaptureApi
@@ -128,63 +192,10 @@ captureSpec = do
128 it "strips the captured path snippet from pathInfo" $ do
192 it "strips the captured path snippet from pathInfo" $ do
129 get "/captured/foo" `shouldRespondWith` (fromString (show ["foo" :: String]))
193 get "/captured/foo" `shouldRespondWith` (fromString (show ["foo" :: String]))
132−type GetApi = Get '[JSON] Person
196+------------------------------------------------------------------------------
133− :<|> "empty" :> Get '[] ()
197+-- * queryParamSpec {{{
134− :<|> "post" :> Post '[] ()
198+------------------------------------------------------------------------------
135−getApi :: Proxy GetApi
140− describe "Servant.API.Get" $ do
141− let server = return alice :<|> return () :<|> return ()
142− with (return $ serve getApi server) $ do
144− it "allows to GET a Person" $ do
146− return response `shouldRespondWith` 200
147− liftIO $ decode' (simpleBody response) `shouldBe` Just alice
149− it "throws 405 (wrong method) on POSTs" $ do
150− post "/" "" `shouldRespondWith` 405
151− post "/empty" "" `shouldRespondWith` 405
153− it "returns 204 if the type is '()'" $ do
154− get "/empty" `shouldRespondWith` ""{ matchStatus = 204 }
156− it "returns 406 if the Accept header is not supported" $ do
157− Test.Hspec.Wai.request methodGet "" [(hAccept, "crazy/mime")] ""
158− `shouldRespondWith` 406
163− describe "Servant.API.Head" $ do
164− let server = return alice :<|> return () :<|> return ()
165− with (return $ serve getApi server) $ do
167− it "allows to GET a Person" $ do
168− response <- Test.Hspec.Wai.request methodHead "/" [] ""
169− return response `shouldRespondWith` 200
170− liftIO $ decode' (simpleBody response) `shouldBe` (Nothing :: Maybe Person)
172− it "does not allow HEAD to POST route" $ do
173− response <- Test.Hspec.Wai.request methodHead "/post" [] ""
174− return response `shouldRespondWith` 405
176− it "throws 405 (wrong method) on POSTs" $ do
177− post "/" "" `shouldRespondWith` 405
178− post "/empty" "" `shouldRespondWith` 405
180− it "returns 204 if the type is '()'" $ do
181− response <- Test.Hspec.Wai.request methodHead "/empty" [] ""
182− return response `shouldRespondWith` ""{ matchStatus = 204 }
184− it "returns 406 if the Accept header is not supported" $ do
185− Test.Hspec.Wai.request methodHead "" [(hAccept, "crazy/mime")] ""
186− `shouldRespondWith` 406
189type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
200type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
190 :<|> "a" :> QueryParams "names" String :> Get '[JSON] Person
201 :<|> "a" :> QueryParams "names" String :> Get '[JSON] Person
@@ -269,131 +280,41 @@ queryParamSpec = do
269 name = "Alice"
280 name = "Alice"
272−type PostApi =
283+-- }}}
273− ReqBody '[JSON] Person :> Post '[JSON] Integer
284+------------------------------------------------------------------------------
274− :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
285+-- * reqBodySpec {{{
275− :<|> "empty" :> Post '[] ()
286+------------------------------------------------------------------------------
277−postApi :: Proxy PostApi
282− describe "Servant.API.Post and .ReqBody" $ do
283− let server = return . age :<|> return . age :<|> return ()
284− with (return $ serve postApi server) $ do
285− let post' x = Test.Hspec.Wai.request methodPost x [(hContentType
286− , "application/json;charset=utf-8")]
288− it "allows to POST a Person" $ do
289− post' "/" (encode alice) `shouldRespondWith` "42"{
293− it "allows alternative routes if all have request bodies" $ do
294− post' "/bla" (encode alice) `shouldRespondWith` "42"{
298− it "handles trailing '/' gracefully" $ do
299− post' "/bla/" (encode alice) `shouldRespondWith` "42"{
303− it "correctly rejects invalid request bodies with status 400" $ do
304− post' "/" "some invalid body" `shouldRespondWith` 400
306− it "returns 204 if the type is '()'" $ do
307− post' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
309− it "responds with 415 if the request body media type is unsupported" $ do
310− let post'' x = Test.Hspec.Wai.request methodPost x [(hContentType
311− , "application/nonsense")]
312− post'' "/" "anything at all" `shouldRespondWith` 415
315− ReqBody '[JSON] Person :> Put '[JSON] Integer
287+type ReqBodyApi = ReqBody '[JSON] Person :> Post '[JSON] Person
316− :<|> "bla" :> ReqBody '[JSON] Person :> Put '[JSON] Integer
288+ :<|> "blah" :> ReqBody '[JSON] Person :> Put '[JSON] Integer
317− :<|> "empty" :> Put '[] ()
319−putApi :: Proxy PutApi
324− describe "Servant.API.Put and .ReqBody" $ do
325− let server = return . age :<|> return . age :<|> return ()
326− with (return $ serve putApi server) $ do
327− let put' x = Test.Hspec.Wai.request methodPut x [(hContentType
328− , "application/json;charset=utf-8")]
330− it "allows to put a Person" $ do
290+reqBodyApi :: Proxy ReqBodyApi
331− put' "/" (encode alice) `shouldRespondWith` "42"{
291+reqBodyApi = Proxy
335− it "allows alternative routes if all have request bodies" $ do
336− put' "/bla" (encode alice) `shouldRespondWith` "42"{
340− it "handles trailing '/' gracefully" $ do
293+reqBodySpec :: Spec
341− put' "/bla/" (encode alice) `shouldRespondWith` "42"{
294+reqBodySpec = describe "Servant.API.ReqBody" $ do
345− it "correctly rejects invalid request bodies with status 400" $ do
296+ let server :: Server ReqBodyApi
346− put' "/" "some invalid body" `shouldRespondWith` 400
297+ server = return :<|> return . age
298+ mkReq method x = Test.Hspec.Wai.request method x
299+ [(hContentType, "application/json;charset=utf-8")]
348− it "returns 204 if the type is '()'" $ do
301+ with (return $ serve reqBodyApi server) $ do
349− put' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
351− it "responds with 415 if the request body media type is unsupported" $ do
303+ it "passes the argument to the handler" $ do
352− let put'' x = Test.Hspec.Wai.request methodPut x [(hContentType
304+ response <- mkReq methodPost "" (encode alice)
353− , "application/nonsense")]
305+ liftIO $ decode' (simpleBody response) `shouldBe` Just alice
354− put'' "/" "anything at all" `shouldRespondWith` 415
356−type PatchApi =
307+ it "rejects invalid request bodies with status 400" $ do
357− ReqBody '[JSON] Person :> Patch '[JSON] Integer
308+ mkReq methodPut "/blah" "some invalid body" `shouldRespondWith` 400
358− :<|> "bla" :> ReqBody '[JSON] Person :> Patch '[JSON] Integer
359− :<|> "empty" :> Patch '[] ()
361−patchApi :: Proxy PatchApi
310+ it "responds with 415 if the request body media type is unsupported" $ do
362−patchApi = Proxy
311+ Test.Hspec.Wai.request methodPost "/"
312+ [(hContentType, "application/nonsense")] "" `shouldRespondWith` 415
364−patchSpec :: Spec
314+-- }}}
365−patchSpec = do
315+------------------------------------------------------------------------------
366− describe "Servant.API.Patch and .ReqBody" $ do
316+-- * headerSpec {{{
367− let server = return . age :<|> return . age :<|> return ()
317+------------------------------------------------------------------------------
368− with (return $ serve patchApi server) $ do
369− let patch' x = Test.Hspec.Wai.request methodPatch x [(hContentType
370− , "application/json;charset=utf-8")]
372− it "allows to patch a Person" $ do
373− patch' "/" (encode alice) `shouldRespondWith` "42"{
377− it "allows alternative routes if all have request bodies" $ do
378− patch' "/bla" (encode alice) `shouldRespondWith` "42"{
382− it "handles trailing '/' gracefully" $ do
383− patch' "/bla/" (encode alice) `shouldRespondWith` "42"{
387− it "correctly rejects invalid request bodies with status 400" $ do
388− patch' "/" "some invalid body" `shouldRespondWith` 400
390− it "returns 204 if the type is '()'" $ do
391− patch' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
393− it "responds with 415 if the request body media type is unsupported" $ do
394− let patch'' x = Test.Hspec.Wai.request methodPatch x [(hContentType
395− , "application/nonsense")]
396− patch'' "/" "anything at all" `shouldRespondWith` 415
398type HeaderApi a = Header "MyHeader" a :> Delete '[JSON] ()
319type HeaderApi a = Header "MyHeader" a :> Delete '[JSON] ()
399headerApi :: Proxy (HeaderApi a)
320headerApi :: Proxy (HeaderApi a)
@@ -411,23 +332,30 @@ headerSpec = describe "Servant.API.Header" $ do
411 expectsString Nothing = error "Expected a string"
332 expectsString Nothing = error "Expected a string"
413 with (return (serve headerApi expectsInt)) $ do
334 with (return (serve headerApi expectsInt)) $ do
414− let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader" ,"5")]
335+ let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "5")]
416 it "passes the header to the handler (Int)" $
337 it "passes the header to the handler (Int)" $
417− delete' "/" "" `shouldRespondWith` 204
338+ delete' "/" "" `shouldRespondWith` 200
419 with (return (serve headerApi expectsString)) $ do
340 with (return (serve headerApi expectsString)) $ do
420− let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader" ,"more from you")]
341+ let delete' x = Test.Hspec.Wai.request methodDelete x [("MyHeader", "more from you")]
422 it "passes the header to the handler (String)" $
343 it "passes the header to the handler (String)" $
423− delete' "/" "" `shouldRespondWith` 204
344+ delete' "/" "" `shouldRespondWith` 200
347+------------------------------------------------------------------------------
349+------------------------------------------------------------------------------
426type RawApi = "foo" :> Raw
351type RawApi = "foo" :> Raw
427rawApi :: Proxy RawApi
353rawApi :: Proxy RawApi
428rawApi = Proxy
354rawApi = Proxy
429rawApplication :: Show a => (Request -> a) -> Application
356rawApplication :: Show a => (Request -> a) -> Application
430−rawApplication f request_ respond = respond $ responseLBS ok200 [] (cs $ show $ f request_)
357+rawApplication f request_ respond = respond $ responseLBS ok200 []
358+ (cs $ show $ f request_)
432rawSpec :: Spec
360rawSpec :: Spec
433rawSpec = do
361rawSpec = do
@@ -448,7 +376,10 @@ rawSpec = do
448 liftIO $ do
376 liftIO $ do
449 simpleBody response `shouldBe` cs (show ["bar" :: String])
377 simpleBody response `shouldBe` cs (show ["bar" :: String])
380+------------------------------------------------------------------------------
381+-- * alternativeSpec {{{
382+------------------------------------------------------------------------------
452type AlternativeApi =
383type AlternativeApi =
453 "foo" :> Get '[JSON] Person
384 "foo" :> Get '[JSON] Person
454 :<|> "bar" :> Get '[JSON] Animal
385 :<|> "bar" :> Get '[JSON] Animal
@@ -456,11 +387,12 @@ type AlternativeApi =
456 :<|> "bar" :> Post '[JSON] Animal
387 :<|> "bar" :> Post '[JSON] Animal
457 :<|> "bar" :> Put '[JSON] Animal
388 :<|> "bar" :> Put '[JSON] Animal
458 :<|> "bar" :> Delete '[JSON] ()
389 :<|> "bar" :> Delete '[JSON] ()
459−unionApi :: Proxy AlternativeApi
391+alternativeApi :: Proxy AlternativeApi
392+alternativeApi = Proxy
462−unionServer :: Server AlternativeApi
394+alternativeServer :: Server AlternativeApi
463−unionServer =
395+alternativeServer =
464 return alice
396 return alice
465 :<|> return jerry
397 :<|> return jerry
466 :<|> return "a string"
398 :<|> return "a string"
@@ -468,10 +400,10 @@ unionServer =
468 :<|> return jerry
400 :<|> return jerry
469 :<|> return ()
401 :<|> return ()
471−unionSpec :: Spec
403+alternativeSpec :: Spec
472−unionSpec = do
404+alternativeSpec = do
473 describe "Servant.API.Alternative" $ do
405 describe "Servant.API.Alternative" $ do
474− with (return $ serve unionApi unionServer) $ do
406+ with (return $ serve alternativeApi alternativeServer) $ do
476 it "unions endpoints" $ do
408 it "unions endpoints" $ do
477 response <- get "/foo"
409 response <- get "/foo"
@@ -488,7 +420,10 @@ unionSpec = do
489 it "returns 404 if the path does not exist" $ do
421 it "returns 404 if the path does not exist" $ do
490 get "/nonexistent" `shouldRespondWith` 404
422 get "/nonexistent" `shouldRespondWith` 404
424+------------------------------------------------------------------------------
425+-- * responseHeaderSpec {{{
426+------------------------------------------------------------------------------
492type ResponseHeadersApi =
427type ResponseHeadersApi =
493 Get '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
428 Get '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
494 :<|> Post '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
429 :<|> Post '[JSON] (Headers '[Header "H1" Int, Header "H2" String] String)
@@ -505,26 +440,29 @@ responseHeadersSpec :: Spec
505responseHeadersSpec = describe "ResponseHeaders" $ do
440responseHeadersSpec = describe "ResponseHeaders" $ do
506 with (return $ serve (Proxy :: Proxy ResponseHeadersApi) responseHeadersServer) $ do
441 with (return $ serve (Proxy :: Proxy ResponseHeadersApi) responseHeadersServer) $ do
508− let methods = [(methodGet, 200), (methodPost, 201), (methodPut, 200), (methodPatch, 200)]
443+ let methods = [methodGet, methodPost, methodPut, methodPatch]
510 it "includes the headers in the response" $
445 it "includes the headers in the response" $
511− forM_ methods $ \(method, expected) ->
446+ forM_ methods $ \method ->
512 Test.Hspec.Wai.request method "/" [] ""
447 Test.Hspec.Wai.request method "/" [] ""
513 `shouldRespondWith` "\"hi\""{ matchHeaders = ["H1" <:> "5", "H2" <:> "kilroy"]
448 `shouldRespondWith` "\"hi\""{ matchHeaders = ["H1" <:> "5", "H2" <:> "kilroy"]
514− , matchStatus = expected
449+ , matchStatus = 200
517 it "responds with not found for non-existent endpoints" $
452 it "responds with not found for non-existent endpoints" $
518− forM_ methods $ \(method,_) ->
453+ forM_ methods $ \method ->
519 Test.Hspec.Wai.request method "blahblah" [] ""
454 Test.Hspec.Wai.request method "blahblah" [] ""
520 `shouldRespondWith` 404
455 `shouldRespondWith` 404
522 it "returns 406 if the Accept header is not supported" $
457 it "returns 406 if the Accept header is not supported" $
523− forM_ methods $ \(method,_) ->
458+ forM_ methods $ \method ->
524 Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] ""
459 Test.Hspec.Wai.request method "" [(hAccept, "crazy/mime")] ""
525 `shouldRespondWith` 406
460 `shouldRespondWith` 406
463+------------------------------------------------------------------------------
465+------------------------------------------------------------------------------
528routerSpec :: Spec
466routerSpec :: Spec
529routerSpec = do
467routerSpec = do
530 describe "Servant.Server.Internal.Router" $ do
468 describe "Servant.Server.Internal.Router" $ do
@@ -543,6 +481,10 @@ routerSpec = do
543 it "calls f on route result" $ do
481 it "calls f on route result" $ do
544 get "" `shouldRespondWith` 202
482 get "" `shouldRespondWith` 202
485+------------------------------------------------------------------------------
486+-- * miscCombinatorSpec {{{
487+------------------------------------------------------------------------------
546type MiscCombinatorsAPI
488type MiscCombinatorsAPI
547 = "version" :> HttpVersion :> Get '[JSON] String
489 = "version" :> HttpVersion :> Get '[JSON] String
548 :<|> "secure" :> IsSecure :> Get '[JSON] String
490 :<|> "secure" :> IsSecure :> Get '[JSON] String
@@ -561,8 +503,8 @@ miscServ = versionHandler
561 secureHandler NotSecure = return "not secure"
503 secureHandler NotSecure = return "not secure"
562 hostHandler = return . show
504 hostHandler = return . show
564−miscReqCombinatorsSpec :: Spec
506+miscCombinatorSpec :: Spec
565−miscReqCombinatorsSpec = with (return $ serve miscApi miscServ) $
507+miscCombinatorSpec = with (return $ serve miscApi miscServ) $
566 describe "Misc. combinators for request inspection" $ do
508 describe "Misc. combinators for request inspection" $ do
567 it "Successfully gets the HTTP version specified in the request" $
509 it "Successfully gets the HTTP version specified in the request" $
568 go "/version" "\"HTTP/1.0\""
510 go "/version" "\"HTTP/1.0\""
@@ -574,3 +516,35 @@ miscReqCombinatorsSpec = with (return $ serve miscApi miscServ) $
574 go "/host" "\"0.0.0.0:0\""
516 go "/host" "\"0.0.0.0:0\""
576 where go path res = Test.Hspec.Wai.get path `shouldRespondWith` res
518 where go path res = Test.Hspec.Wai.get path `shouldRespondWith` res
520+------------------------------------------------------------------------------
521+-- * Test data types {{{
522+------------------------------------------------------------------------------
524+data Person = Person {
528+ deriving (Eq, Show, Generic)
530+instance ToJSON Person
531+instance FromJSON Person
534+alice = Person "Alice" 42
536+data Animal = Animal {
538+ numberOfLegs :: Integer
540+ deriving (Eq, Show, Generic)
542+instance ToJSON Animal
543+instance FromJSON Animal
546+jerry = Animal "Mouse" 4
549+tweety = Animal "Bird" 2