wai-extra-3.1.17: test/WaiExtraSpec.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module WaiExtraSpec (spec, toRequest) where
import Codec.Compression.GZip (decompress)
import Control.Applicative ((<|>))
import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
import qualified Data.IORef as I
import Data.Maybe (fromMaybe)
#if __GLASGOW_HASKELL__ < 804
import Data.Monoid ((<>))
#if __GLASGOW_HASKELL__ < 710
import Data.Monoid (mempty, mappend)
#endif
#endif
import qualified Data.Text as TS
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as T
import Network.HTTP.Types (
Header,
RequestHeaders,
ResponseHeaders,
hContentEncoding,
hContentLength,
hContentType,
partialContent206,
status200,
)
import Network.HTTP.Types.Header (hAcceptEncoding, hVary)
import Network.Wai
import System.Directory (listDirectory)
import System.IO.Temp (withSystemTempDirectory)
import System.Log.FastLogger (fromLogStr)
import Test.HUnit (Assertion, assertBool, assertEqual, (@?=))
import Test.Hspec
import Network.Wai.Header (parseQValueList)
import Network.Wai.Middleware.AcceptOverride (acceptOverride)
import Network.Wai.Middleware.Autohead (autohead)
import Network.Wai.Middleware.Gzip (
GzipFiles (..),
GzipSettings (..),
defaultGzipSettings,
defaultCheckMime,
gzip,
)
import Network.Wai.Middleware.Jsonp (jsonp)
import Network.Wai.Middleware.MethodOverride (methodOverride)
import Network.Wai.Middleware.MethodOverridePost (methodOverridePost)
import Network.Wai.Middleware.RequestLogger
import Network.Wai.Middleware.StreamFile (streamFile)
import Network.Wai.Middleware.Vhost (vhost)
import Network.Wai.Test
import Network.Wai.UrlMap (mapUrls, mount, mountRoot)
spec :: Spec
spec = do
describe "Network.Wai.UrlMap" $ do
mapM_ (uncurry it) casesUrlMap
describe "Network.Wai" $ do
{-
, it "findBound" caseFindBound
, it "sinkTillBound" caseSinkTillBound
, it "killCR" caseKillCR
, it "killCRLF" caseKillCRLF
, it "takeLine" caseTakeLine
-}
it "jsonp" caseJsonp
describe "gzip" $ do
it "gzip" caseGzip
it "more gzip" caseGzip2
it "gzip not on partial content" caseGzipPartial
it "gzip removes length header" caseGzipLength
it "gzip not for MSIE" caseGzipMSIE
it "gzip bypass when precompressed" caseGzipBypassPre
it "defaultCheckMime" caseDefaultCheckMime
it "gzip checking of files" caseGzipFiles
it "vhost" caseVhost
it "autohead" caseAutohead
it "method override" caseMethodOverride
it "method override post" caseMethodOverridePost
it "accept override" caseAcceptOverride
it "debug request body" caseDebugRequestBody
it "stream file" caseStreamFile
it "stream LBS" caseStreamLBS
it "can modify POST params before logging" caseModifyPostParamsInLogs
it "can filter requests in logs" caseFilterRequestsInLogs
it "can parse Q values" caseQValues
toRequest :: S8.ByteString -> S8.ByteString -> SRequest
toRequest ctype content =
SRequest
defaultRequest
{ requestHeaders = [("Content-Type", ctype)]
, requestMethod = "POST"
, rawPathInfo = "/"
, rawQueryString = ""
, queryString = []
}
(L.fromChunks [content])
{-
caseFindBound :: Assertion
caseFindBound = do
findBound "def" "abcdefghi" @?=
FoundBound "abc" "ghi"
findBound "def" "ABC" @?= NoBound
findBound "def" "abcd" @?= PartialBound
findBound "def" "abcdE" @?= NoBound
findBound "def" "abcdEdef" @?=
FoundBound "abcdE" ""
caseSinkTillBound :: Assertion
caseSinkTillBound = do
let iter () _ = return ()
let src = "this is some text"
bound1 = "some"
bound2 = "some!"
let enum = enumList 1 [src]
let helper _ _ = return ()
(_, res1) <- run_ $ enum $$ sinkTillBound bound1 helper ()
res1 @?= True
(_, res2) <- run_ $ enum $$ sinkTillBound bound2 helper ()
res2 @?= False
caseKillCR :: Assertion
caseKillCR = do
"foo" @=? killCR "foo"
"foo" @=? killCR "foo\r"
"foo\r\n" @=? killCR "foo\r\n"
"foo\r'" @=? killCR "foo\r'"
caseKillCRLF :: Assertion
caseKillCRLF = do
"foo" @=? killCRLF "foo"
"foo\r" @=? killCRLF "foo\r"
"foo" @=? killCRLF "foo\r\n"
"foo\r'" @=? killCRLF "foo\r'"
"foo" @=? killCRLF "foo\n"
caseTakeLine :: Assertion
caseTakeLine = do
helper "foo\nbar\nbaz" "foo"
helper "foo\r\nbar\nbaz" "foo"
helper "foo\nbar\r\nbaz" "foo"
helper "foo\rbar\r\nbaz" "foo\rbar"
where
helper haystack needle = do
x <- run_ $ enumList 1 [haystack] $$ takeLine
Just needle @=? x
-}
jsonpApp :: Application
jsonpApp = jsonp $ \_ f ->
f $
responseLBS
status200
[("Content-Type", "application/json")]
"{\"foo\":\"bar\"}"
caseJsonp :: Assertion
caseJsonp = withSession jsonpApp $ do
sres1 <-
request
defaultRequest
{ queryString = [("callback", Just "test")]
, requestHeaders = [("Accept", "text/javascript")]
}
assertContentType "text/javascript" sres1
assertBody "test({\"foo\":\"bar\"})" sres1
sres2 <-
request
defaultRequest
{ queryString = [("call_back", Just "test")]
, requestHeaders = [("Accept", "text/javascript")]
}
assertContentType "application/json" sres2
assertBody "{\"foo\":\"bar\"}" sres2
sres3 <-
request
defaultRequest
{ queryString = [("callback", Just "test")]
, requestHeaders = [("Accept", "text/html")]
}
assertContentType "application/json" sres3
assertBody "{\"foo\":\"bar\"}" sres3
gzipApp :: Application
gzipApp = gzipApp' id
gzipApp' :: (Response -> Response) -> Application
gzipApp' changeRes =
gzip defaultGzipSettings $ \_ f ->
f . changeRes $
responseLBS
status200
[("Content-Type", "text/plain")]
"test"
gzipAppWithHeaders :: ResponseHeaders -> Application
gzipAppWithHeaders hdrs = gzipApp' $ mapResponseHeaders (hdrs ++)
gzipFileApp :: GzipSettings -> Application
gzipFileApp = flip gzipFileApp' id
gzipJSONFile, gzipNoPreCompressFile :: FilePath
gzipJSONFile = "test/json"
gzipNoPreCompressFile = "test/noprecompress"
gzipJSONBody, gzipNocompressBody :: L.ByteString
#if WINDOWS
gzipJSONBody = "{\"data\":\"this is some data\"}\r\n"
gzipNocompressBody = "noprecompress\r\n"
#else
gzipJSONBody = "{\"data\":\"this is some data\"}\n"
gzipNocompressBody = "noprecompress\n"
#endif
-- | Use 'changeRes' to make r
gzipFileApp' :: GzipSettings -> (Response -> Response) -> Application
gzipFileApp' set changeRes =
gzip set $ \_ f ->
f . changeRes $
responseFile status200 [(hContentType, "application/json")] gzipJSONFile Nothing
acceptGzip :: Header
acceptGzip = (hAcceptEncoding, "gzip")
doesEncodeGzip :: RequestHeaders -> Session SResponse
doesEncodeGzip = doesEncodeGzip' "test"
doesEncodeGzipJSON :: RequestHeaders -> Session SResponse
doesEncodeGzipJSON = doesEncodeGzip' gzipJSONBody
doesEncodeGzipNoPreCompress :: RequestHeaders -> Session SResponse
doesEncodeGzipNoPreCompress = doesEncodeGzip' gzipNocompressBody
doesEncodeGzip' :: L.ByteString -> RequestHeaders -> Session SResponse
doesEncodeGzip' body hdrs = do
sres <-
request
defaultRequest
{ requestHeaders = hdrs
}
assertHeader hContentEncoding "gzip" sres
assertHeader hVary "Accept-Encoding" sres
liftIO $ decompress (simpleBody sres) @?= body
pure sres
doesNotEncodeGzip :: RequestHeaders -> Session SResponse
doesNotEncodeGzip = doesNotEncodeGzip' "test"
doesNotEncodeGzipJSON :: RequestHeaders -> Session SResponse
doesNotEncodeGzipJSON = doesNotEncodeGzip' gzipJSONBody
doesNotEncodeGzipNoPreCompress :: RequestHeaders -> Session SResponse
doesNotEncodeGzipNoPreCompress = doesNotEncodeGzip' gzipNocompressBody
doesNotEncodeGzip' :: L.ByteString -> RequestHeaders -> Session SResponse
doesNotEncodeGzip' body hdrs = do
sres <-
request
defaultRequest
{ requestHeaders = hdrs
}
assertNoHeader hContentEncoding sres
assertHeader hVary "Accept-Encoding" sres
assertBody body sres
pure sres
caseGzip :: Assertion
caseGzip = do
withSession gzipApp $ do
_ <- doesEncodeGzip [acceptGzip]
_ <- doesNotEncodeGzip []
_ <- doesEncodeGzip [(hAcceptEncoding, "compress , gzip ; q=0.8")]
pure ()
withSession (gzipAppWithHeaders [(hContentLength, "200")]) $ do
sres4 <- doesNotEncodeGzip [acceptGzip]
assertHeader hContentLength "200" sres4
caseGzipLength :: Assertion
caseGzipLength = do
withSession (gzipAppWithHeaders [(hContentLength, "4000")]) $ do
sres <- doesEncodeGzip [acceptGzip]
assertNoHeader hContentLength sres
caseGzipPartial :: Assertion
caseGzipPartial =
withSession partialApp $ do
_ <- doesNotEncodeGzip [acceptGzip]
pure ()
where
partialApp = gzipApp' $ mapResponseStatus $ const partialContent206
-- | Checking that it doesn't compress when already compressed AND
-- doesn't replace already set "Vary" header.
caseGzip2 :: Assertion
caseGzip2 =
withSession gzipVariantApp $ do
sres1 <-
request
defaultRequest
{ requestHeaders = [(hAcceptEncoding, "compress, gzip")]
}
assertHeader hContentEncoding "compress" sres1
assertHeader hVary "Accept-Encoding, foobar" sres1
where
gzipVariantApp =
gzipAppWithHeaders
[ ("Content-Encoding", "compress")
, ("Vary", "foobar")
]
-- | Testing of the GzipSettings's 'GzipFiles' setting
-- with 'ResponseFile' responses.
caseGzipFiles :: Assertion
caseGzipFiles = do
-- Default GzipSettings ignore compressing files
withSession (gzipFileApp defaultGzipSettings) $ do
_ <- doesNotEncodeGzipJSON [acceptGzip]
_ <- doesNotEncodeGzipJSON []
pure ()
-- Just compresses the file
withSession (gzipFileApp defaultGzipSettings{gzipFiles = GzipCompress}) $ do
_ <- doesEncodeGzipJSON [acceptGzip]
_ <- doesNotEncodeGzipJSON []
pure ()
-- Checks for a "filename.gz" file in the same folder
withSession (gzipFileApp defaultGzipSettings{gzipFiles = GzipPreCompressed GzipIgnore}) $ do
sres <-
request
defaultRequest
{ requestHeaders = [acceptGzip]
}
assertHeader hContentEncoding "gzip" sres
assertHeader hVary "Accept-Encoding" sres
-- json.gz has body "test\n"
assertBody
#if WINDOWS
"test\r\n"
#else
"test\n"
#endif
sres
_ <- doesNotEncodeGzipJSON []
pure ()
-- If no "filename.gz" file is in the same folder, just ignore
withSession (noPreCompressApp $ GzipPreCompressed GzipIgnore) $ do
_ <- doesNotEncodeGzipNoPreCompress [acceptGzip]
_ <- doesNotEncodeGzipNoPreCompress []
pure ()
-- If no "filename.gz" file is in the same folder, just compress
withSession (noPreCompressApp $ GzipPreCompressed GzipCompress) $ do
_ <- doesEncodeGzipNoPreCompress [acceptGzip]
_ <- doesNotEncodeGzipNoPreCompress []
pure ()
-- Using a caching directory
withSystemTempDirectory "gziptest" $ \path -> do
let checkTempDir n s = do
fs <- listDirectory path
assertBool s $ length fs == n
checkTempDir 0 "temp directory should be empty"
-- Respond with "test/json" file
withSession (gzipFileApp defaultGzipSettings{gzipFiles = GzipCacheFolder path}) $ do
_ <- doesEncodeGzipJSON [acceptGzip]
liftIO $ checkTempDir 1 "should have one file"
_ <- doesEncodeGzipJSON [acceptGzip]
liftIO $ checkTempDir 1 "should still have only one file"
_ <- doesNotEncodeGzipJSON []
liftIO $ checkTempDir 1 "should not have done anything"
-- Respond with "test/noprecompress" file
withSession (noPreCompressApp $ GzipCacheFolder path) $ do
_ <- doesEncodeGzipNoPreCompress [acceptGzip]
liftIO $ checkTempDir 2 "should now have 2 files"
_ <- doesEncodeGzipNoPreCompress [acceptGzip]
liftIO $ checkTempDir 2 "should still only have 2 files"
_ <- doesNotEncodeGzipNoPreCompress []
liftIO $ checkTempDir 2 "again should not have done anything"
-- try "test/json" again, just to make sure it isn't a weird Session bug
withSession (gzipFileApp defaultGzipSettings{gzipFiles = GzipCacheFolder path}) $ do
_ <- doesEncodeGzipJSON [acceptGzip]
liftIO $ checkTempDir 2 "just to make sure it isn't a fluke"
where
noPreCompressApp set =
gzipFileApp'
defaultGzipSettings{gzipFiles = set}
$ const
$ responseFile
status200
[(hContentType, "text/plain")]
gzipNoPreCompressFile
Nothing
caseDefaultCheckMime :: Assertion
caseDefaultCheckMime = do
let go x y = (x, defaultCheckMime x) `shouldBe` (x, y)
go "application/json" True
go "application/javascript" True
go "application/something" False
go "text/something" True
go "foo/bar" False
go "application/json; charset=utf-8" True
caseGzipMSIE :: Assertion
caseGzipMSIE = withSession gzipApp $ do
sres1 <-
doesNotEncodeGzip
[ acceptGzip
, ("User-Agent", "Mozilla/4.0 (Windows; MSIE 6.0; Windows NT 6.0)")
]
assertHeader "Vary" "Accept-Encoding" sres1
caseGzipBypassPre :: Assertion
caseGzipBypassPre =
-- Lie a little and don't compress the body. This way we test
-- that the compression is skipped based on the presence of
-- the Content-Encoding header.
withSession (gzipAppWithHeaders [(hContentEncoding, "gzip")]) $ do
sres1 <- request defaultRequest{requestHeaders = [acceptGzip]}
assertHeader "Content-Encoding" "gzip" sres1
assertHeader "Vary" "Accept-Encoding" sres1
assertBody "test" sres1 -- the body is not actually compressed
vhostApp1, vhostApp2, vhostApp :: Application
vhostApp1 _ f = f $ responseLBS status200 [] "app1"
vhostApp2 _ f = f $ responseLBS status200 [] "app2"
vhostApp =
vhost
[ ((== Just "foo.com") . lookup "host" . requestHeaders, vhostApp1)
]
vhostApp2
caseVhost :: Assertion
caseVhost = withSession vhostApp $ do
sres1 <-
request
defaultRequest
{ requestHeaders = [("Host", "foo.com")]
}
assertBody "app1" sres1
sres2 <-
request
defaultRequest
{ requestHeaders = [("Host", "bar.com")]
}
assertBody "app2" sres2
autoheadApp :: Application
autoheadApp = autohead $ \_ f ->
f $
responseLBS
status200
[("Foo", "Bar")]
"body"
caseAutohead :: Assertion
caseAutohead = withSession autoheadApp $ do
sres1 <-
request
defaultRequest
{ requestMethod = "GET"
}
assertHeader "Foo" "Bar" sres1
assertBody "body" sres1
sres2 <-
request
defaultRequest
{ requestMethod = "HEAD"
}
assertHeader "Foo" "Bar" sres2
assertBody "" sres2
moApp :: Application
moApp = methodOverride $ \req f ->
f $
responseLBS
status200
[("Method", requestMethod req)]
""
caseMethodOverride :: Assertion
caseMethodOverride = withSession moApp $ do
sres1 <-
request
defaultRequest
{ requestMethod = "GET"
, queryString = []
}
assertHeader "Method" "GET" sres1
sres2 <-
request
defaultRequest
{ requestMethod = "POST"
, queryString = []
}
assertHeader "Method" "POST" sres2
sres3 <-
request
defaultRequest
{ requestMethod = "POST"
, queryString = [("_method", Just "PUT")]
}
assertHeader "Method" "PUT" sres3
mopApp :: Application
mopApp = methodOverridePost $ \req f -> f $ responseLBS status200 [("Method", requestMethod req)] ""
caseMethodOverridePost :: Assertion
caseMethodOverridePost = withSession mopApp $ do
-- Get Request are unmodified
sres1 <-
let r = toRequest "application/x-www-form-urlencoded" "_method=PUT&foo=bar&baz=bin"
s = simpleRequest r
m = s{requestMethod = "GET"}
b = r{simpleRequest = m}
in srequest b
assertHeader "Method" "GET" sres1
-- Post requests are modified if _method comes first
sres2 <-
srequest $
toRequest "application/x-www-form-urlencoded" "_method=PUT&foo=bar&baz=bin"
assertHeader "Method" "PUT" sres2
-- Post requests are unmodified if _method doesn't come first
sres3 <-
srequest $
toRequest "application/x-www-form-urlencoded" "foo=bar&_method=PUT&baz=bin"
assertHeader "Method" "POST" sres3
-- Post requests are unmodified if Content-Type header isn't set to "application/x-www-form-urlencoded"
sres4 <-
srequest $ toRequest "text/html; charset=utf-8" "foo=bar&_method=PUT&baz=bin"
assertHeader "Method" "POST" sres4
aoApp :: Application
aoApp = acceptOverride $ \req f ->
f $
responseLBS
status200
[("Accept", fromMaybe "" $ lookup "Accept" $ requestHeaders req)]
""
caseAcceptOverride :: Assertion
caseAcceptOverride = withSession aoApp $ do
sres1 <-
request
defaultRequest
{ queryString = []
, requestHeaders = [("Accept", "foo")]
}
assertHeader "Accept" "foo" sres1
sres2 <-
request
defaultRequest
{ queryString = []
, requestHeaders = [("Accept", "bar")]
}
assertHeader "Accept" "bar" sres2
sres3 <-
request
defaultRequest
{ queryString = [("_accept", Just "baz")]
, requestHeaders = [("Accept", "bar")]
}
assertHeader "Accept" "baz" sres3
caseDebugRequestBody :: Assertion
caseDebugRequestBody = do
withSession (debugApp postOutput) $ do
let req = toRequest "application/x-www-form-urlencoded" "foo=bar&baz=bin"
res <- srequest req
assertStatus 200 res
let qs = "?foo=bar&baz=bin"
withSession (debugApp $ getOutput params) $ do
assertStatus 200
=<< request
defaultRequest
{ requestMethod = "GET"
, queryString = map (\(k, v) -> (k, Just v)) params
, rawQueryString = qs
, requestHeaders = []
, rawPathInfo = "/location"
}
where
params = [("foo", "bar"), ("baz", "bin")]
-- the time cannot be known, so match around it
postOutput = (T.pack $ "POST /\n Params: " ++ show params, "s\n")
getOutput params' =
( "GET /location\n Params: "
<> T.pack (show params')
<> "\n Accept: \n Status: 200 OK 0"
, "s\n"
)
debugApp (beginning, ending) req send = do
iactual <- I.newIORef mempty
middleware <-
mkRequestLogger
defaultRequestLoggerSettings
{ destination = Callback $ \strs -> I.modifyIORef iactual (`mappend` strs)
, outputFormat = Detailed False
}
res <- middleware (\_req f -> f $ responseLBS status200 [] "") req send
actual <- logToBs <$> I.readIORef iactual
actual `shouldSatisfy` S.isPrefixOf begin
actual `shouldSatisfy` S.isSuffixOf end
return res
where
begin = TE.encodeUtf8 $ T.toStrict beginning
end = TE.encodeUtf8 $ T.toStrict ending
logToBs = fromLogStr
{-debugApp = debug $ \req -> do-}
{-return $ responseLBS status200 [ ] ""-}
urlMapTestApp :: Application
urlMapTestApp =
mapUrls $
mount "bugs" bugsApp
<|> mount "helpdesk" helpdeskApp
<|> mount
"api"
( mount "v1" apiV1
<|> mount "v2" apiV2
)
<|> mountRoot mainApp
where
trivialApp :: S.ByteString -> Application
trivialApp name req f =
f $
responseLBS
status200
[ ("content-type", "text/plain")
, ("X-pathInfo", S8.pack . show . pathInfo $ req)
, ("X-rawPathInfo", rawPathInfo req)
, ("X-appName", name)
]
""
bugsApp = trivialApp "bugs"
helpdeskApp = trivialApp "helpdesk"
apiV1 = trivialApp "apiv1"
apiV2 = trivialApp "apiv2"
mainApp = trivialApp "main"
casesUrlMap :: [(String, Assertion)]
casesUrlMap = [pair1, pair2, pair3, pair4]
where
makePair name session = (name, runSession session urlMapTestApp)
get reqPath = request $ setPath defaultRequest reqPath
s = S8.pack . show :: [TS.Text] -> S.ByteString
pair1 = makePair "should mount root" $ do
res1 <- get "/"
assertStatus 200 res1
assertHeader "X-rawPathInfo" "/" res1
assertHeader "X-pathInfo" (s []) res1
assertHeader "X-appName" "main" res1
pair2 = makePair "should mount apps" $ do
res2 <- get "/bugs"
assertStatus 200 res2
assertHeader "X-rawPathInfo" "/" res2
assertHeader "X-pathInfo" (s []) res2
assertHeader "X-appName" "bugs" res2
pair3 = makePair "should preserve extra path info" $ do
res3 <- get "/helpdesk/issues/11"
assertStatus 200 res3
assertHeader "X-rawPathInfo" "/issues/11" res3
assertHeader "X-pathInfo" (s ["issues", "11"]) res3
pair4 = makePair "should 404 if none match" $ do
res4 <- get "/api/v3"
assertStatus 404 res4
testFile :: FilePath
testFile = "test/WaiExtraSpec.hs"
streamFileApp :: Application
streamFileApp = streamFile $ \_ f -> f $ responseFile status200 [] testFile Nothing
caseStreamFile :: Assertion
caseStreamFile = withSession streamFileApp $ do
sres <- request defaultRequest
assertStatus 200 sres
assertBodyContains "caseStreamFile" sres
assertNoHeader "Transfer-Encoding" sres
streamLBSApp :: Application
streamLBSApp = streamFile $ \_ f ->
f $
responseLBS
status200
[("Content-Type", "text/plain")]
"test"
caseStreamLBS :: Assertion
caseStreamLBS = withSession streamLBSApp $ do
sres <- request defaultRequest
assertStatus 200 sres
assertBody "test" sres
caseModifyPostParamsInLogs :: Assertion
caseModifyPostParamsInLogs = do
let formatUnredacted = DetailedWithSettings $ DetailedSettings False Nothing Nothing False
outputUnredacted = [("username", "some_user"), ("password", "dont_show_me")]
formatRedacted =
DetailedWithSettings $ DetailedSettings False (Just hidePasswords) Nothing False
hidePasswords p@(k, _) = Just $ if k == "password" then (k, "***REDACTED***") else p
outputRedacted = [("username", "some_user"), ("password", "***REDACTED***")]
testLogs formatUnredacted outputUnredacted
testLogs formatRedacted outputRedacted
where
testLogs :: OutputFormat -> [(String, String)] -> Assertion
testLogs format output = withSession (debugApp format output) $ do
let req =
toRequest
"application/x-www-form-urlencoded"
"username=some_user&password=dont_show_me"
res <- srequest req
assertStatus 200 res
postOutputStart params = TE.encodeUtf8 $ T.toStrict $ "POST /\n Params: " <> (T.pack . show $ params)
postOutputEnd = TE.encodeUtf8 $ T.toStrict "s\n"
debugApp format output req send = do
iactual <- I.newIORef mempty
middleware <-
mkRequestLogger
defaultRequestLoggerSettings
{ destination = Callback $ \strs -> I.modifyIORef iactual (`mappend` strs)
, outputFormat = format
}
res <- middleware (\_req f -> f $ responseLBS status200 [] "") req send
actual <- fromLogStr <$> I.readIORef iactual
actual `shouldSatisfy` S.isPrefixOf (postOutputStart output)
actual `shouldSatisfy` S.isSuffixOf postOutputEnd
return res
caseFilterRequestsInLogs :: Assertion
caseFilterRequestsInLogs = do
let formatUnfiltered = DetailedWithSettings $ DetailedSettings False Nothing Nothing False
formatFiltered =
DetailedWithSettings $
DetailedSettings False Nothing (Just hideHealthCheck) False
pathHidden = "/health-check"
pathNotHidden = "/foobar"
-- filter is off
testLogs formatUnfiltered pathNotHidden True
testLogs formatUnfiltered pathHidden True
-- filter is on, path does not match
testLogs formatFiltered pathNotHidden True
-- filter is on, path matches
testLogs formatFiltered pathHidden False
where
testLogs :: OutputFormat -> S8.ByteString -> Bool -> Assertion
testLogs format rpath haslogs = withSession (debugApp format rpath haslogs) $ do
let req = flip SRequest "" $ setPath defaultRequest rpath
res <- srequest req
assertStatus 200 res
hideHealthCheck req _res = pathInfo req /= ["health-check"]
debugApp format rpath haslogs req send = do
iactual <- I.newIORef mempty
middleware <-
mkRequestLogger
defaultRequestLoggerSettings
{ destination = Callback $ \strs -> I.modifyIORef iactual (`mappend` strs)
, outputFormat = format
}
res <- middleware (\_req f -> f $ responseLBS status200 [] "") req send
actual <- fromLogStr <$> I.readIORef iactual
if haslogs
then do
actual `shouldSatisfy` S.isPrefixOf ("GET " <> rpath <> "\n")
actual `shouldSatisfy` S.isSuffixOf "s\n"
else actual `shouldBe` ""
return res
-- | Unit test to make sure 'parseQValueList' works correctly
caseQValues :: Assertion
caseQValues = do
let encodings =
mconcat
-- This has weird white space on purpose, because this
-- should be acceptable according to RFC 7231
[ "deflate, compress; q=0.813 ,gzip ; q=0.9, * ;q=0, "
, "notq;charset=bar, "
, "noq;q=, "
, "toolong;q=0.0023, "
, "toohigh ;q=1.1"
]
qList = parseQValueList encodings
expected =
[ ("deflate", Just 1000)
, ("compress", Just 813)
, ("gzip", Just 900)
, ("*", Just 0)
, ("notq", Nothing)
, ("noq", Nothing)
, ("toolong", Nothing)
, ("toohigh", Nothing)
]
assertEqual "invalid Q values" expected qList