tmp-proc-0.6.2.0: test/Test/System/TmpProc/WarpSpec.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Test.System.TmpProc.WarpSpec where
import Control.Exception (catch)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import qualified Network.HTTP.Client as HC
import Network.HTTP.Types (status200, status400)
import Network.Wai (Application, pathInfo, responseLBS)
import Network.Wai.Handler.WarpTLS (tlsSettings)
import qualified Network.Wai.Handler.WarpTLS as Warp
import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)
import System.TmpProc.Docker
( HList (..)
, HandlesOf
, Pinged (..)
, ProcHandle
, handleOf
, ixPing
, only
)
import System.TmpProc.Warp
( ServerHandle
, handles
, runServer
, runTLSServer
, serverPort
, shutdown
, testWithApplication
, testWithTLSApplication
)
import Test.Certs.Temp (CertPaths (..), certificatePath, defaultConfig, generateAndStore, keyPath, withCertPathsInTmp')
import Test.Hspec
import Test.Hspec.TmpProc (tdescribe)
import Test.HttpBin
import Test.NginxGateway (NginxGateway (..))
import Test.SimpleServer (statusOfGet, statusOfGet')
spec :: Spec
spec = tdescribe "Tmp.Proc: Warp server with Tmp.Proc dependency" $ do
checkBeforeAll prefixHttp setupBeforeAll statusOfGet
checkBeforeAll prefixHttps setupBeforeAllTls statusOfGet'
httpSpec prefixHttp setupAround statusOfGet
httpSpec prefixHttps setupAroundTls statusOfGet'
type TmpProcs = '[HttpBinTest]
testProcs :: HList TmpProcs
testProcs = only HttpBinTest
theGateway :: NginxGateway
theGateway =
NginxGateway
{ ngCommonName = "localhost"
, ngTargetPort = 80
, ngTargetName = "http-bin-test"
}
testApp :: HandlesOf TmpProcs -> IO Application
testApp hs =
mkTestApp'
(pingOrFail $ handleOf @"http-bin-test" Proxy hs)
(pingOrFail $ handleOf @"http-bin-test" Proxy hs)
setupBeforeAll :: IO (ServerHandle TmpProcs)
setupBeforeAll = runServer testProcs testApp
setupBeforeAllTls :: IO (ServerHandle TmpProcs)
setupBeforeAllTls = do
cp <- genCertPaths
let tls = tlsSettings (certificatePath cp) (keyPath cp)
runTLSServer tls testProcs testApp
genCertPaths :: IO CertPaths
genCertPaths = do
tmpDir <- getCanonicalTemporaryDirectory
cpDir <- createTempDirectory tmpDir "tmp-proc-warp-spec"
let cp =
CertPaths
{ cpKey = "key.pem"
, cpCert = "certificate.pem"
, cpDir
}
generateAndStore cp defaultConfig
pure cp
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:"
checkBeforeAll ::
String ->
IO (ServerHandle TmpProcs) ->
(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 TmpProcs, Int) -> IO a) -> IO a
setupAround = testWithApplication testProcs testApp
setupAroundTls :: ((HandlesOf TmpProcs, Int) -> IO a) -> IO a
setupAroundTls cont = withCertPathsInTmp' $ \cp -> do
let tls = Warp.tlsSettings (certificatePath cp) (keyPath cp)
testWithTLSApplication tls testProcs testApp cont
httpSpec ::
String ->
(ActionWith (HandlesOf TmpProcs, Int) -> IO ()) ->
(Int -> Text -> IO Int) ->
Spec
httpSpec prefix setup getter = around setup $ do
describe (prefix ++ 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` ( \(_ :: HC.HttpException) ->
fail "tmp proc:httpbin:ping failed"
)
catchHttp $ do
gotStatus <- httpGet handle "/status/200"
if gotStatus == 200
then pure ()
else fail "tmp proc:httpbin:incorrect ping status"