packages feed

tmp-proc-0.5.2.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 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 System.TmpProc.Docker (
  HList (..),
  HandlesOf,
  Pinged (..),
  ProcHandle,
  handleOf,
  ixPing,
 )
import System.TmpProc.Warp (
  ServerHandle,
  handles,
  runServer,
  runTLSServer,
  serverPort,
  shutdown,
  testWithApplication,
  testWithTLSApplication,
 )
import Test.Hspec
import Test.Hspec.TmpProc (tdescribe)
import Test.HttpBin
import Test.SimpleServer (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 = do
--   tls <- defaultTLSSettings
--   runTLSServer tls 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 cont = do
--   tls <- defaultTLSSettings
--   testWithTLSApplication tls testProcs testApp cont

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` ( \(_ :: HC.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"