wai-extra-3.1.14: test/Network/Wai/Middleware/CombineHeadersSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Wai.Middleware.CombineHeadersSpec (
main,
spec,
) where
import Data.ByteString (ByteString)
import Data.IORef (newIORef, readIORef, writeIORef)
import Network.HTTP.Types (status200)
import Network.HTTP.Types.Header
import Network.Wai
import Test.Hspec
import Network.Wai.Middleware.CombineHeaders (
CombineSettings,
combineHeaders,
defaultCombineSettings,
setRequestHeaders,
setResponseHeaders,
)
import Network.Wai.Test (SResponse (simpleHeaders), request, runSession)
main :: IO ()
main = hspec spec
spec :: Spec
spec = do
let test name settings reqHeaders expectedReqHeaders resHeaders expectedResHeaders = it name $ do
(reqHdrs, resHdrs) <- runApp settings reqHeaders resHeaders
reqHdrs `shouldBe` expectedReqHeaders
resHdrs `shouldBe` expectedResHeaders
testReqHdrs name a b =
test name defaultCombineSettings a b [] []
testResHdrs name a b =
test
name
(setRequestHeaders False $ setResponseHeaders True defaultCombineSettings)
[]
[]
a
b
-- Request Headers
testReqHdrs
"should reorder alphabetically (request)"
[host, userAgent, acceptHtml]
[acceptHtml, host, userAgent]
-- Response Headers
testResHdrs
"should reorder alphabetically (response)"
[expires, location, contentTypeHtml]
[contentTypeHtml, expires, location]
-- Request Headers
testReqHdrs
"combines Accept (in order)"
[userAgent, acceptHtml, host, acceptJSON]
[acceptHtml `combineHdrs` acceptJSON, host, userAgent]
-- Response Headers
testResHdrs
-- Using the default header map, Cache-Control is a "combineable" header, "Set-Cookie" is not
"combines Cache-Control (in order) and keeps Set-Cookie (in order)"
[cacheControlPublic, setCookie "2", date, cacheControlMax, setCookie "1"]
[ cacheControlPublic `combineHdrs` cacheControlMax
, date
, setCookie "2"
, setCookie "1"
]
-- Request Headers
testReqHdrs
"KeepOnly works as expected (present | request)"
-- "Alt-Svc" has (KeepOnly "clear")
[date, altSvc "wrong", altSvc "clear", altSvc "wrong again", host]
[altSvc "clear", date, host]
testReqHdrs
"KeepOnly works as expected ( absent | request)"
-- "Alt-Svc" has (KeepOnly "clear"), but will combine when there's no "clear" (AND keeps order)
[date, altSvc "wrong", altSvc "not clear", altSvc "wrong again", host]
[altSvc "wrong, not clear, wrong again", date, host]
-- Response Headers
testResHdrs
"KeepOnly works as expected (present | response)"
-- "If-None-Match" has (KeepOnly "*")
[date, ifNoneMatch "wrong", ifNoneMatch "*", ifNoneMatch "wrong again", host]
[date, host, ifNoneMatch "*"]
testResHdrs
"KeepOnly works as expected ( absent | response)"
-- "If-None-Match" has (KeepOnly "*"), but will combine when there's no "*" (AND keeps order)
[ date
, ifNoneMatch "wrong"
, ifNoneMatch "not *"
, ifNoneMatch "wrong again"
, host
]
[date, host, ifNoneMatch "wrong, not *, wrong again"]
-- Request Headers
testReqHdrs
"Technically acceptable headers get combined correctly (request)"
[ ifNoneMatch "correct, "
, ifNoneMatch "something else \t"
, ifNoneMatch "and more , "
]
[ifNoneMatch "correct, something else, and more"]
-- Response Headers
testResHdrs
"Technically acceptable headers get combined correctly (response)"
[altSvc "correct\t, ", altSvc "something else", altSvc "and more, , "]
[altSvc "correct, something else, and more"]
combineHdrs :: Header -> Header -> Header
combineHdrs (hname, h1) (_, h2) = (hname, h1 <> ", " <> h2)
acceptHtml
, acceptJSON
, cacheControlMax
, cacheControlPublic
, contentTypeHtml
, date
, expires
, host
, location
, userAgent
:: Header
acceptHtml = (hAccept, "text/html")
acceptJSON = (hAccept, "application/json")
altSvc :: ByteString -> Header
altSvc x = ("Alt-Svc", x)
cacheControlPublic = (hCacheControl, "public")
cacheControlMax = (hCacheControl, "public")
contentTypeHtml = (hContentType, "text/html")
date = (hDate, "Mon, 19 Aug 2022 18:18:31 GMT")
expires = (hExpires, "Mon, 19 Sep 2022 18:18:31 GMT")
host = (hHost, "google.com")
ifNoneMatch :: ByteString -> Header
ifNoneMatch x = (hIfNoneMatch, x)
location = (hLocation, "http://www.google.com/")
setCookie :: ByteString -> Header
setCookie val = (hSetCookie, val)
userAgent = (hUserAgent, "curl/7.68.0")
runApp
:: CombineSettings
-> RequestHeaders
-> ResponseHeaders
-> IO (RequestHeaders, ResponseHeaders)
runApp settings reqHeaders resHeaders = do
reqHdrs <- newIORef $ error "IORef not set"
sResponse <-
runSession
session
$ combineHeaders settings
$ app reqHdrs
finalReqHeaders <- readIORef reqHdrs
pure (finalReqHeaders, simpleHeaders sResponse)
where
session =
request
defaultRequest{requestHeaders = reqHeaders}
app hdrRef req respond = do
writeIORef hdrRef $ requestHeaders req
respond $ responseLBS status200 resHeaders ""