packages feed

tmp-proc-0.5.0.0: test/Test/System/TmpProc/WarpSpec.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE PolyKinds           #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications    #-}
{-# LANGUAGE TypeFamilies        #-}
module Test.System.TmpProc.WarpSpec where

import           Test.Hspec

import           Control.Exception     (catch)
import           Data.Proxy            (Proxy (..))
import           Data.Text             (Text)
import           Network.HTTP.Req
import           Network.HTTP.Types    (status200, status400)
import           Network.Wai           (Application, pathInfo, responseLBS)

import           System.TmpProc.Docker (HList (..), HandlesOf, Pinged (..),
                                        ProcHandle, handleOf, ixPing)
import           System.TmpProc.Warp   (ServerHandle, handles, runServer,
                                        runTLSServer, serverPort, shutdown,
                                        testWithApplication,
                                        testWithTLSApplication)
import           Test.Hspec.TmpProc    (tdescribe)
import           Test.HttpBin
import           Test.SimpleServer     (defaultTLSSettings, statusOfGet,
                                        statusOfGet')


spec :: Spec
spec = tdescribe "Tmp.Proc: Warp server with Tmp.Proc dependency" $ do
  beforeAllSpec >> aroundSpec


testProcs :: HList '[HttpBinTest]
testProcs = HttpBinTest `HCons` HNil


testApp :: HandlesOf '[HttpBinTest] -> IO Application
testApp hs = mkTestApp'
  (pingOrFail $ handleOf @"http-bin-test" Proxy hs)
  (pingOrFail $ handleOf @"http-bin-test" Proxy hs)


setupBeforeAll :: IO (ServerHandle '[HttpBinTest])
setupBeforeAll = runServer testProcs testApp

setupBeforeAllTls :: IO (ServerHandle '[HttpBinTest])
setupBeforeAllTls = runTLSServer defaultTLSSettings testProcs testApp


suffixAround, suffixBeforeAll, prefixHttp, prefixHttps :: String
suffixAround = " when the server is restarted for each test"
suffixBeforeAll = " when the server starts beforeAll tests"
prefixHttp = "Warp+HTTP:"
prefixHttps = "Warp+HTTPS:"


beforeAllSpec :: Spec
beforeAllSpec = do
  checkBeforeAll prefixHttp setupBeforeAll statusOfGet
  checkBeforeAll prefixHttps setupBeforeAllTls statusOfGet'


checkBeforeAll
  :: String
  -> IO (ServerHandle '[HttpBinTest])
  -> (Int -> Text -> IO Int) -> Spec
checkBeforeAll descPrefix setup getter =  beforeAll setup $ afterAll shutdown $ do
  describe (descPrefix ++ suffixBeforeAll) $ do

    it "should ping the proc handle" $ \sh ->
      ixPing @"http-bin-test" Proxy (handles sh) `shouldReturn` OK

    it "should invoke the warp server via its port" $ \sh ->
      getter (serverPort sh) "test" `shouldReturn` 200


setupAround :: ((HandlesOf '[HttpBinTest], Int) -> IO a) -> IO a
setupAround = testWithApplication testProcs testApp


setupAroundTls :: ((HandlesOf '[HttpBinTest], Int) -> IO a) -> IO a
setupAroundTls = testWithTLSApplication defaultTLSSettings testProcs testApp


aroundSpec :: Spec
aroundSpec = do
  checkEachTest prefixHttp setupAround statusOfGet
  checkEachTest prefixHttps setupAroundTls statusOfGet'


checkEachTest
  :: String
  -> (ActionWith (HandlesOf '[HttpBinTest], Int) -> IO ())
  -> (Int -> Text -> IO Int)
  -> Spec
checkEachTest descPrefix setup getter = around setup $ do
  describe (descPrefix ++ suffixAround) $ do

    it "should ping the proc handle" $ \(h, _) ->
        ixPing @"http-bin-test" Proxy h `shouldReturn` OK

    it "should invoke the warp server via its port" $ \(_, p) ->
        getter p "test" `shouldReturn` 200


-- | A WAI app that triggers an action on a TmpProc dependency on /test, and
-- responds to health checks on /health.
mkTestApp'
  :: IO ()
  -> IO ()
  -> IO Application
mkTestApp' onStart onTest = onStart >> pure app
  where
    app rq respond
      | isHealthReq rq = respond $ responseLBS status200 [] "ok"
    app rq respond
      | isTestReq rq = onTest >> respond (responseLBS status200 [] "ok")
    app _ respond = respond $ responseLBS status400 [] "Incorrect request"

    isHealthReq = isReqPathsEq ["health"]
    isTestReq = isReqPathsEq ["test"]
    isReqPathsEq x rq = x == pathInfo rq


pingOrFail :: ProcHandle a -> IO ()
pingOrFail handle = do
  let catchHttp x = x `catch` (\(_ :: HttpException) ->
                                  fail "tmp proc:httpbin:ping failed")
  catchHttp $ do
    gotStatus <- handleGet handle "/status/200"
    if (gotStatus == 200) then pure () else
      fail "tmp proc:httpbin:incorrect ping status"