{-# LANGUAGE MultiParamTypeClasses, TypeFamilies, OverloadedStrings, DataKinds, TypeOperators, TypeSynonymInstances, FlexibleInstances, DeriveGeneric #-} module WebApi.ResponseSpec (spec) where import GHC.Generics import WebApi import Test.Hspec import qualified Network.Wai as Wai import Test.Hspec.Wai (with, get, request, shouldRespondWith, matchStatus, (<:>), matchHeaders) import Network.HTTP.Media.MediaType import Network.HTTP.Types import Data.Text import Data.Aeson (ToJSON (..)) withApp :: SpecWith Wai.Application -> Spec withApp = with (return respSpecApp) respSpecApp :: Wai.Application respSpecApp = serverApp serverSettings RespSpecImpl data RespSpec data RespSpecImpl = RespSpecImpl data Out = Out { _out :: Text } deriving (Show, Eq, Generic) data HOut = HOut { _hOut :: Text } deriving (Show, Eq, Generic) data COut = COut { _cOut :: Text } deriving (Show, Eq, Generic) data Err = Err { _err :: Text } deriving (Show, Eq, Generic) instance ToJSON Err instance ToJSON Out instance ToHeader HOut instance ToParam COut 'Cookie instance ParamErrToApiErr Err where toApiErr = const (Err "fail") type ApiResp = Static "apiresp" type ApiWithHeaders = Static "apih" type ApiWithError = Static "apierror" type TextCType = Static "text" type LazyEncoding = Static "lazyencoding" instance WebApi RespSpec where type Apis RespSpec = '[ Route '[GET] ApiResp , Route '[GET] ApiWithHeaders , Route '[GET] ApiWithError , Route '[GET] TextCType , Route '[GET] LazyEncoding] instance WebApiImplementation RespSpecImpl where type ApiInterface RespSpecImpl = RespSpec type HandlerM RespSpecImpl = IO instance ApiContract RespSpec GET ApiResp where type ApiOut GET ApiResp = Out instance ApiContract RespSpec GET ApiWithHeaders where type ApiOut GET ApiWithHeaders = Out type HeaderOut GET ApiWithHeaders = HOut type CookieOut GET ApiWithHeaders = COut instance ApiContract RespSpec GET ApiWithError where type ApiOut GET ApiWithError = Out type ApiErr GET ApiWithError = Err instance ApiContract RespSpec GET TextCType where type ApiOut GET TextCType = Text type ApiErr GET TextCType = Text type ContentTypes GET TextCType = '[PlainText] instance ApiContract RespSpec GET LazyEncoding where type ApiOut GET LazyEncoding = Out type ContentTypes GET LazyEncoding = '[DummyCType, JSON] instance ApiHandler RespSpecImpl GET ApiResp where handler _ _ = respond (Out "Done") instance ApiHandler RespSpecImpl GET ApiWithHeaders where handler _ _ = respondWith status200 (Out "Done") (HOut "header") (COut "cookie") instance ApiHandler RespSpecImpl GET ApiWithError where handler _ _ = do -- raise should short circuit _ <- (raise status500 (Err "fail") :: IO (Response GET ApiWithError)) -- raiseWith' _ -- (ApiError status500 (Err "fail") Nothing Nothing) -- :: ApiError GET ApiWithError) -- which means respond will never get called respond (Out "Done") instance ApiHandler RespSpecImpl GET TextCType where handler _ _ = respond "plaintext" instance ApiHandler RespSpecImpl GET LazyEncoding where handler _ _ = respond (Out "Done") data DummyCType instance Accept DummyCType where contentType _ = "application" // "dummy" instance Encode DummyCType a where encode _ = error "Dummy content type not implemented" spec :: Spec spec = withApp $ describe "WebApi response" $ do context "Simple Response" $ do it "should be 200 ok" $ do get "apiresp" `shouldRespondWith` 200 context "Response with response header and cookies" $ do it "should be 200 ok" $ do get "apih" `shouldRespondWith` "{\"out\":\"Done\"}" { matchHeaders = [ "hOut" <:> "header" , "Set-Cookie" <:> "cOut=cookie" , "Content-Type" <:> "application/json"] , matchStatus = 200 } context "Response with api error" $ do it "should be 500 ok" $ do get "apierror" `shouldRespondWith` 500 context "Response with text as content type" $ do it "should be 200 ok" $ do get "text" `shouldRespondWith` "plaintext" { matchHeaders = ["Content-Type" <:> "text/plain;charset=utf-8"] , matchStatus = 200 } context "Response should get encoded lazily" $ do it "should be 200 ok" $ do let h = [(hAccept, "application/json")] request methodGet "lazyencoding" h "" `shouldRespondWith` "{\"out\":\"Done\"}" { matchHeaders = ["Content-Type" <:> "application/json"] , matchStatus = 200 }