packages feed

restman-0.7.3.1: test/HTTP/EchoServer.hs

{-# language OverloadedStrings #-}
-- | A minimal WAI echo application and Tasty test fixture.
--
-- The server accepts any HTTP request and responds with a JSON object
-- reflecting the method, path, query string, request headers, and body.
-- Header names are normalised to lowercase for predictable assertions.
module HTTP.EchoServer (withEchoServer) where

import Data.Aeson (encode, object, (.=))
import qualified Data.ByteString.Lazy as BL
import qualified Data.CaseInsensitive as CI
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Network.HTTP.Types (status200)
import Network.Wai
  ( Application
  , rawPathInfo
  , rawQueryString
  , requestHeaders
  , requestMethod
  , responseLBS
  , strictRequestBody
  )
import Network.Wai.Handler.Warp (Port, testWithApplication)

-- | WAI application that echoes request details back as JSON.
echoApp :: Application
echoApp req respond = do
    body <- strictRequestBody req
    let method = TE.decodeUtf8 (requestMethod req)
        path = TE.decodeUtf8 (rawPathInfo req)
        query = TE.decodeUtf8 (rawQueryString req)
        hdrs = Map.fromList
                    [ (TE.decodeUtf8 (CI.foldedCase k), TE.decodeUtf8 v)
                    | (k, v) <- requestHeaders req
                    ] :: Map T.Text T.Text
        bodyTxt = TE.decodeUtf8 (BL.toStrict body)
        payload = object
            [ "method"  .= method
            , "path"    .= path
            , "query"   .= query
            , "headers" .= hdrs
            , "body"    .= bodyTxt
            ]
    respond $ responseLBS status200
        [("Content-Type", "application/json")]
        (encode payload)

-- | Bracket helper: spin up an echo server on a free OS-assigned port,
-- run the supplied action with that port number, then tear down the server.
--
-- Safe to use inside 'Test.Tasty.HUnit.testCase' — the server is
-- terminated even if the action throws an exception.
withEchoServer :: (Port -> IO a) -> IO a
withEchoServer = testWithApplication (pure echoApp)