@@ -0,0 +1,145 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Network.Wai.Test
19+import qualified Test.HUnit.Base as H
20+import Control.Monad.IO.Class (liftIO)
21+import Control.Monad.Trans.State (StateT, evalStateT)
22+import Control.Monad.Trans.Reader (ReaderT, runReaderT, ask)
24+import qualified Data.Map as Map
25+import Data.ByteString (ByteString)
26+import qualified Data.ByteString.Char8 as S8
27+import Data.Enumerator (joinI, ($$), run_, enumList)
28+import Data.Enumerator.List (consume)
29+import Blaze.ByteString.Builder.Enumerator (builderToByteString)
30+import qualified Data.ByteString.Lazy as L
31+import qualified Data.ByteString.Lazy.Char8 as L8
32+import qualified Network.HTTP.Types as H
33+import Data.CaseInsensitive (CI)
35+type Session = ReaderT Application (StateT ClientState IO)
37+data ClientState = ClientState
38+ { clientCookies :: Map ByteString ByteString
41+initState :: ClientState
42+initState = ClientState Map.empty
44+runSession :: Session a -> Application -> IO a
45+runSession session app = evalStateT (runReaderT session app) initState
47+data SRequest = SRequest
48+ { simpleRequest :: Request
49+ , simpleRequestBody :: L.ByteString
51+data SResponse = SResponse
52+ { simpleStatus :: H.Status
53+ , simpleHeaders :: H.ResponseHeaders
54+ , simpleBody :: L.ByteString
57+request :: Request -> Session SResponse
58+request = srequest . flip SRequest L.empty
60+srequest :: SRequest -> Session SResponse
61+srequest (SRequest req bod) = do
63+ res <- liftIO $ run_ $ enumList 4 (L.toChunks bod) $$ app req
64+ sres <- liftIO $ runResponse res
65+ -- FIXME cookie processing
68+runResponse :: Response -> IO SResponse
70+ responseEnumerator res go
73+ bss <- joinI $ builderToByteString $$ consume
74+ return $ SResponse s h $ L.fromChunks bss
76+assertBool :: String -> Bool -> Session ()
77+assertBool s b = liftIO $ H.assertBool s b
79+assertString :: String -> Session ()
80+assertString s = liftIO $ H.assertString s
82+assertContentType :: ByteString -> SResponse -> Session ()
83+assertContentType ct SResponse{simpleHeaders = h} =
84+ case lookup "content-type" h of
85+ Nothing -> assertString $ concat
86+ [ "Expected content type "
88+ , ", but no content type provided"
90+ Just ct' -> assertBool (concat
91+ [ "Expected content type "
97+ go = S8.takeWhile (/= ';')
99+assertStatus :: Int -> SResponse -> Session ()
100+assertStatus i SResponse{simpleStatus = s} = assertBool (concat
101+ [ "Expected status code "
109+assertBody :: L.ByteString -> SResponse -> Session ()
110+assertBody lbs SResponse{simpleBody = lbs'} = assertBool (concat
111+ [ "Expected response body "
112+ , show $ L8.unpack lbs
114+ , show $ L8.unpack lbs'
117+assertHeader :: CI ByteString -> ByteString -> SResponse -> Session ()
118+assertHeader header value SResponse{simpleHeaders = h} =
119+ case lookup header h of
120+ Nothing -> assertString $ concat
121+ [ "Expected header "
125+ , ", but it was not present"
127+ Just value' -> assertBool (concat
128+ [ "Expected header "
134+ ]) (value == value')
136+assertNoHeader :: CI ByteString -> SResponse -> Session ()
137+assertNoHeader header SResponse{simpleHeaders = h} =
138+ case lookup header h of
139+ Nothing -> return ()
140+ Just s -> assertString $ concat
141+ [ "Unexpected header "