{-# LANGUAGE QuasiQuotes, TypeFamilies, TemplateHaskell, MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
module YesodCoreTest.RequestBodySize (specs, Widget) where

import Test.Hspec

import Yesod.Core

import Network.Wai
import Network.Wai.Test
import Data.ByteString (ByteString)
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Char8 as S8
import Data.Text (Text)
import qualified Data.Text as T
import Data.Conduit
import Data.Conduit.List (consume)
import Data.Conduit.Binary (isolate)

data Y = Y

mkYesod "Y" [parseRoutes|
/post PostR POST
/consume ConsumeR POST
/partial-consume PartialConsumeR POST
/unused UnusedR POST
|]

instance Yesod Y where
    maximumContentLength _ _ = Just 10

postPostR, postConsumeR, postPartialConsumeR, postUnusedR :: Handler RepPlain

postPostR = do
    val <- lookupPostParams "foobarbaz"
    return $ RepPlain $ toContent $ T.concat val

postConsumeR = do
    body <- rawRequestBody $$ consume
    return $ RepPlain $ toContent $ S.concat body

postPartialConsumeR = do
    body <- rawRequestBody $$ isolate 5 =$ consume
    return $ RepPlain $ toContent $ S.concat body

postUnusedR = return $ RepPlain ""

runner :: Session () -> IO ()
runner f = toWaiApp Y >>= runSession f

caseHelper :: String -- ^ name
           -> Text -- ^ pathinfo
           -> ByteString -- ^ request body
           -> Int -- ^ expected status code, chunked
           -> Int -- ^ expected status code, non-chunked
           -> Spec
caseHelper name path body statusChunked statusNonChunked = describe name $ do
    it "chunked" $ runner $ do
        res <- mkRequest False
        assertStatus statusChunked res
    it "non-chunked" $ runner $ do
        res <- mkRequest True
        assertStatus statusNonChunked res
  where
    mkRequest includeLength = srequest $ SRequest defaultRequest
        { pathInfo = [path]
        , requestHeaders =
            ("content-type", "application/x-www-form-urlencoded") :

            if includeLength
                then [("content-length", S8.pack $ show $ S.length body)]
                else []
        , requestMethod = "POST"
        , requestBodyLength =
            if includeLength
                then KnownLength $ fromIntegral $ S.length body
                else ChunkedBody
        } $ L.fromChunks $ map S.singleton $ S.unpack body

specs :: Spec
specs = describe "Test.RequestBodySize" $ do
    caseHelper "lookupPostParam- large" "post" "foobarbaz=bin" 413 413
    caseHelper "lookupPostParam- small" "post" "foo=bin" 200 200
    caseHelper "consume- large" "consume" "this is longer than 10" 413 413
    caseHelper "consume- small" "consume" "smaller" 200 200
    caseHelper "partial consume- large" "partial-consume" "this is longer than 10" 200 413
    caseHelper "partial consume- small" "partial-consume" "smaller" 200 200
    caseHelper "unused- large" "unused" "this is longer than 10" 200 413
    caseHelper "unused- small" "unused" "smaller" 200 200
