{-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE ViewPatterns #-} module Dormouse.ClientSpec ( spec ) where import Control.Concurrent.MVar import Control.Exception.Safe (MonadThrow, catch, Exception, SomeException (SomeException)) import Control.Monad.IO.Class import Control.Monad.Reader import Data.Aeson (encode, Value, ToJSON (toJSON)) import qualified Data.Map.Strict as Map import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as LB import Dormouse.Client import Dormouse.Client.Test.Class import Dormouse.Url.QQ import Dormouse.Client.Generators.Json import Test.Hspec import Test.Hspec.Hedgehog import qualified Hedgehog.Range as Range data TestEnv = TestEnv { sentJson :: MVar (Maybe (LB.ByteString)) , sentContentType :: MVar (Maybe B.ByteString) , sentAcceptHeader :: MVar (Maybe B.ByteString) , returnJson :: MVar (Maybe (LB.ByteString)) , triggered :: MVar Bool } newtype TestM a = TestM { unTestM :: ReaderT TestEnv IO a } deriving (Functor, Applicative, Monad, MonadReader TestEnv, MonadIO, MonadThrow) runTestM :: TestEnv -> TestM a -> IO a runTestM deps app = flip runReaderT deps $ unTestM app instance MonadDormouseTestClient TestM where expectLbs req@HttpRequest { requestUrl = u, requestMethod = method, requestBody = body, requestHeaders = headers } = do let reqUrl = asAnyUrl u case (reqUrl, method) of ([url|https://starfleet.com/captains|], GET) -> do testEnv <- ask _ <- liftIO . swapMVar (triggered testEnv) $ True pure HttpResponse { responseStatusCode = 200 , responseHeaders = Map.empty , responseBody = encode () } _ -> do testEnv <- ask _ <- liftIO . swapMVar (sentJson testEnv) . Just $ requestBody req _ <- liftIO . swapMVar (sentContentType testEnv) . getHeaderValue "Content-Type" $ req _ <- liftIO . swapMVar (sentAcceptHeader testEnv) . getHeaderValue "Accept" $ req maybeResponseJson <- liftIO . readMVar $ returnJson testEnv let respBs = maybe (encode ()) id $ maybeResponseJson pure HttpResponse { responseStatusCode = 200 , responseHeaders = Map.empty , responseBody = respBs } spec :: Spec spec = before setup $ do describe "expect" $ do it "submits the correct json body, content-type and accept header for a json request" $ \(sentJson', sentContentType', sentAcceptHeader', _, testEnv, jsonGenRanges) -> do hedgehog $ do arbJson <- forAll . genJsonValue $ jsonGenRanges let req = accept noPayload $ supplyBody json arbJson $ post [https|https://testing123.com|] _ :: HttpResponse Empty <- liftIO $ runTestM testEnv $ expect req lbs <- liftIO $ readMVar sentJson' actualContentType <- liftIO $ readMVar sentContentType' actualAcceptHeader <- liftIO $ readMVar sentAcceptHeader' let expected = encode arbJson lbs === (Just expected) actualContentType === Just "application/json" actualAcceptHeader === Nothing it "submits the correct accept header and gets the correct json body for a json response" $ \(_, _, sentAcceptHeader', returnJson', testEnv, jsonGenRanges) -> do hedgehog $ do arbJson <- forAll . genJsonValue $ jsonGenRanges _ <- liftIO $ swapMVar returnJson' $ Just (encode arbJson) let req = accept json $ supplyBody json () $ post [https|https://testing123.com|] r :: HttpResponse Value <- liftIO $ runTestM testEnv $ expect req actualAcceptHeader <- liftIO $ readMVar sentAcceptHeader' let actualJson = responseBody r actualJson === arbJson actualAcceptHeader === Just "application/json" describe "expectAs" $ do it "submits the correct json body, content-type and accept header for a json request" $ \(sentJson', sentContentType', sentAcceptHeader', _, testEnv, jsonGenRanges) -> do hedgehog $ do arbJson <- forAll . genJsonValue $ jsonGenRanges let req = supplyBody json arbJson $ post [https|https://testing123.com|] _ :: HttpResponse Empty <- liftIO $ runTestM testEnv $ expectAs noPayload req lbs <- liftIO $ readMVar sentJson' actualContentType <- liftIO $ readMVar sentContentType' actualAcceptHeader <- liftIO $ readMVar sentAcceptHeader' let expected = encode arbJson lbs === (Just expected) actualContentType === (Just "application/json") actualAcceptHeader === Nothing it "submits the correct accept header and gets the correct json body for a json response" $ \(_, _, sentAcceptHeader', returnJson', testEnv, jsonGenRanges) -> do hedgehog $ do arbJson <- forAll . genJsonValue $ jsonGenRanges _ <- liftIO $ swapMVar returnJson' $ Just (encode arbJson) let req = supplyBody json () $ post [https|https://testing123.com|] r :: HttpResponse Value <- liftIO $ runTestM testEnv $ expectAs json req actualAcceptHeader <- liftIO $ readMVar sentAcceptHeader' let actualJson = responseBody r actualJson === arbJson actualAcceptHeader === Nothing it "matches the expected url and method" $ \(_, _, _, _, testEnv, _) -> do hedgehog $ do let req = get [https|https://starfleet.com/captains|] r :: HttpResponse Value <- liftIO $ runTestM testEnv $ expectAs json req t <- liftIO . readMVar $ triggered testEnv t === True where setup = do sentJson' <- newMVar Nothing sentContentType' <- newMVar Nothing sentAcceptHeader' <- newMVar Nothing returnJson' <- newMVar Nothing triggered' <- newMVar False let testEnv = TestEnv { sentJson = sentJson' , sentContentType = sentContentType' , sentAcceptHeader = sentAcceptHeader' , returnJson = returnJson' , triggered = triggered' } let jsonGenRanges = JsonGenRanges { stringRanges = Range.constant 0 15 , doubleRanges = Range.constant (-100000) (1000000) , arrayLenRanges = Range.constant 0 7 } return (sentJson', sentContentType', sentAcceptHeader', returnJson', testEnv, jsonGenRanges)