@@ -1,36 +1,40 @@
1−{-# LANGUAGE DataKinds #-}
1+{-# LANGUAGE DataKinds #-}
2−{-# LANGUAGE DeriveGeneric #-}
2+{-# LANGUAGE DeriveGeneric #-}
3−{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE OverloadedStrings #-}
4−{-# LANGUAGE QuasiQuotes #-}
5{-# LANGUAGE ScopedTypeVariables #-}
4{-# LANGUAGE ScopedTypeVariables #-}
6−{-# LANGUAGE TypeOperators #-}
5+{-# LANGUAGE TypeOperators #-}
8module Servant.ServerSpec where
7module Servant.ServerSpec where
11−import Control.Monad (when)
10+import Control.Monad (when)
12−import Control.Monad.Trans.Either (EitherT, left)
11+import Control.Monad.Trans.Either (EitherT, left)
13−import Data.Aeson (ToJSON, FromJSON, encode, decode')
12+import Data.Aeson (FromJSON, ToJSON, decode', encode)
14−import Data.Char (toUpper)
13+import Data.Char (toUpper)
15−import Data.Monoid ((<>))
14+import Data.Monoid ((<>))
16−import Data.Proxy (Proxy(Proxy))
15+import Data.Proxy (Proxy (Proxy))
17−import Data.String (fromString)
16+import Data.String (fromString)
18−import Data.String.Conversions (cs)
17+import Data.String.Conversions (cs)
19−import GHC.Generics (Generic)
18+import GHC.Generics (Generic)
19+import Network.HTTP.Types (hContentType, methodDelete,
20+ methodPatch, methodPost, methodPut,
20−import Network.HTTP.Types ( parseQuery, ok200, status409, methodPost
21+ ok200, parseQuery, status409)
21− , methodDelete, hContentType)
22−import Network.Wai ( Application, Request, responseLBS, pathInfo
22+import Network.Wai (Application, Request, pathInfo,
23− , queryString, rawQueryString )
23+ queryString, rawQueryString,
24−import Network.Wai.Test (runSession, defaultRequest, simpleBody, request)
25+import Network.Wai.Test (defaultRequest, request,
26+ runSession, simpleBody)
25−import Test.Hspec (Spec, describe, it, shouldBe)
27+import Test.Hspec (Spec, describe, it, shouldBe)
26−import Test.Hspec.Wai ( liftIO, with, get, post, shouldRespondWith
28+import Test.Hspec.Wai (get, liftIO, matchStatus, post,
27− , matchStatus, request )
29+ request, shouldRespondWith, with)
29−import Servant.API (JSON, Capture, Get, ReqBody, Post, QueryParam
31+import Servant.API ((:<|>) (..), (:>), Capture, Delete,
30− , QueryParams, QueryFlag, MatrixParam, MatrixParams
32+ Get, Header, JSON, MatrixFlag,
31− , MatrixFlag, Raw, (:>), (:<|>)(..), Header, Delete )
33+ MatrixParam, MatrixParams, Patch,
34+ Post, Put, QueryFlag, QueryParam,
35+ QueryParams, Raw, ReqBody)
32−import Servant.Server (Server, serve)
36+import Servant.Server (Server, serve)
33−import Servant.Server.Internal (RouteMismatch(..))
37+import Servant.Server.Internal (RouteMismatch (..))
36-- * test data types
40-- * test data types
@@ -69,9 +73,11 @@ spec :: Spec
70 captureSpec
74 captureSpec
72 queryParamSpec
79 queryParamSpec
73 matrixParamSpec
80 matrixParamSpec
75 headerSpec
81 headerSpec
@@ -105,13 +111,15 @@ captureSpec = do
107type GetApi = Get '[JSON] Person
113type GetApi = Get '[JSON] Person
114+ :<|> "empty" :> Get '[] ()
108getApi :: Proxy GetApi
115getApi :: Proxy GetApi
109getApi = Proxy
116getApi = Proxy
111getSpec :: Spec
118getSpec :: Spec
112getSpec = do
119getSpec = do
113 describe "Servant.API.Get" $ do
120 describe "Servant.API.Get" $ do
121+ let server = return alice :<|> return ()
114− with (return (serve getApi (return alice))) $ do
122+ with (return $ serve getApi server) $ do
115 it "allows to GET a Person" $ do
123 it "allows to GET a Person" $ do
116 response <- get "/"
124 response <- get "/"
117 return response `shouldRespondWith` 200
125 return response `shouldRespondWith` 200
@@ -121,6 +129,10 @@ getSpec = do
121 it "throws 405 (wrong method) on POSTs" $ do
129 it "throws 405 (wrong method) on POSTs" $ do
122 post "/" "" `shouldRespondWith` 405
130 post "/" "" `shouldRespondWith` 405
132+ it "returns 204 if the type is '()'" $ do
133+ get "empty" `shouldRespondWith` ""{ matchStatus = 204 }
125type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
137type QueryParamApi = QueryParam "name" String :> Get '[JSON] Person
126 :<|> "a" :> QueryParams "names" String :> Get '[JSON] Person
138 :<|> "a" :> QueryParams "names" String :> Get '[JSON] Person
@@ -291,13 +303,16 @@ matrixParamSpec = do
291type PostApi =
303type PostApi =
292 ReqBody '[JSON] Person :> Post '[JSON] Integer
304 ReqBody '[JSON] Person :> Post '[JSON] Integer
293 :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
305 :<|> "bla" :> ReqBody '[JSON] Person :> Post '[JSON] Integer
306+ :<|> "empty" :> Post '[] ()
294postApi :: Proxy PostApi
308postApi :: Proxy PostApi
295postApi = Proxy
309postApi = Proxy
297postSpec :: Spec
311postSpec :: Spec
298postSpec = do
312postSpec = do
299 describe "Servant.API.Post and .ReqBody" $ do
313 describe "Servant.API.Post and .ReqBody" $ do
314+ let server = return . age :<|> return . age :<|> return ()
300− with (return (serve postApi (return . age :<|> return . age))) $ do
315+ with (return $ serve postApi server) $ do
301 let post' x = Test.Hspec.Wai.request methodPost x [(hContentType
316 let post' x = Test.Hspec.Wai.request methodPost x [(hContentType
302 , "application/json;charset=utf-8")]
317 , "application/json;charset=utf-8")]
@@ -319,11 +334,98 @@ postSpec = do
319 it "correctly rejects invalid request bodies with status 400" $ do
334 it "correctly rejects invalid request bodies with status 400" $ do
320 post' "/" "some invalid body" `shouldRespondWith` 400
335 post' "/" "some invalid body" `shouldRespondWith` 400
337+ it "returns 204 if the type is '()'" $ do
338+ post' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
322 it "responds with 415 if the requested media type is unsupported" $ do
340 it "responds with 415 if the requested media type is unsupported" $ do
323 let post'' x = Test.Hspec.Wai.request methodPost x [(hContentType
341 let post'' x = Test.Hspec.Wai.request methodPost x [(hContentType
324 , "application/nonsense")]
342 , "application/nonsense")]
325 post'' "/" "anything at all" `shouldRespondWith` 415
343 post'' "/" "anything at all" `shouldRespondWith` 415
346+ ReqBody '[JSON] Person :> Put '[JSON] Integer
347+ :<|> "bla" :> ReqBody '[JSON] Person :> Put '[JSON] Integer
348+ :<|> "empty" :> Put '[] ()
350+putApi :: Proxy PutApi
355+ describe "Servant.API.Put and .ReqBody" $ do
356+ let server = return . age :<|> return . age :<|> return ()
357+ with (return $ serve putApi server) $ do
358+ let put' x = Test.Hspec.Wai.request methodPut x [(hContentType
359+ , "application/json;charset=utf-8")]
361+ it "allows to put a Person" $ do
362+ put' "/" (encode alice) `shouldRespondWith` "42"{
366+ it "allows alternative routes if all have request bodies" $ do
367+ put' "/bla" (encode alice) `shouldRespondWith` "42"{
371+ it "handles trailing '/' gracefully" $ do
372+ put' "/bla/" (encode alice) `shouldRespondWith` "42"{
376+ it "correctly rejects invalid request bodies with status 400" $ do
377+ put' "/" "some invalid body" `shouldRespondWith` 400
379+ it "returns 204 if the type is '()'" $ do
380+ put' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
382+ it "responds with 415 if the requested media type is unsupported" $ do
383+ let put'' x = Test.Hspec.Wai.request methodPut x [(hContentType
384+ , "application/nonsense")]
385+ put'' "/" "anything at all" `shouldRespondWith` 415
388+ ReqBody '[JSON] Person :> Patch '[JSON] Integer
389+ :<|> "bla" :> ReqBody '[JSON] Person :> Patch '[JSON] Integer
390+ :<|> "empty" :> Patch '[] ()
392+patchApi :: Proxy PatchApi
397+ describe "Servant.API.Patch and .ReqBody" $ do
398+ let server = return . age :<|> return . age :<|> return ()
399+ with (return $ serve patchApi server) $ do
400+ let patch' x = Test.Hspec.Wai.request methodPatch x [(hContentType
401+ , "application/json;charset=utf-8")]
403+ it "allows to patch a Person" $ do
404+ patch' "/" (encode alice) `shouldRespondWith` "42"{
408+ it "allows alternative routes if all have request bodies" $ do
409+ patch' "/bla" (encode alice) `shouldRespondWith` "42"{
413+ it "handles trailing '/' gracefully" $ do
414+ patch' "/bla/" (encode alice) `shouldRespondWith` "42"{
418+ it "correctly rejects invalid request bodies with status 400" $ do
419+ patch' "/" "some invalid body" `shouldRespondWith` 400
421+ it "returns 204 if the type is '()'" $ do
422+ patch' "empty" "" `shouldRespondWith` ""{ matchStatus = 204 }
424+ it "responds with 415 if the requested media type is unsupported" $ do
425+ let patch'' x = Test.Hspec.Wai.request methodPatch x [(hContentType
426+ , "application/nonsense")]
427+ patch'' "/" "anything at all" `shouldRespondWith` 415
327type HeaderApi a = Header "MyHeader" a :> Delete
429type HeaderApi a = Header "MyHeader" a :> Delete
328headerApi :: Proxy (HeaderApi a)
430headerApi :: Proxy (HeaderApi a)
329headerApi = Proxy
431headerApi = Proxy