on master help sign in
Add 'wai-test/' from commit '245f105fe1cbbaa69f183f93b74ae36921b0eff1'

git-subtree-dir: wai-test git-subtree-mainline: 2db47661beace40ff992dc0b03963e6c9303f50f git-subtree-split: 245f105fe1cbbaa69f183f93b74ae36921b0eff1

commitebedc4d08313552a85108039c0f29ad5975dc5e2 authorMichael Snoyman <michael@snoyman.com> dateFri 22 Jul 2011, 16:51 (15 years ago) parent2db4766245f105
5 files changed+210
wai-test/LICENSE+25 wai-test/Network/Wai/Test.hs+145 wai-test/README wai-test/Setup.lhs+8 wai-test/wai-test.cabal+32
wai-test/LICENSEnew file+25
@@ -0,0 +1,25 @@
1+The following license covers this documentation, and the source code, except
2+where otherwise indicated.
3+
4+Copyright 2010, Michael Snoyman. All rights reserved.
5+
6+Redistribution and use in source and binary forms, with or without
7+modification, are permitted provided that the following conditions are met:
8+
9+* Redistributions of source code must retain the above copyright notice, this
10+ list of conditions and the following disclaimer.
11+
12+* Redistributions in binary form must reproduce the above copyright notice,
13+ this list of conditions and the following disclaimer in the documentation
14+ and/or other materials provided with the distribution.
15+
16+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
17+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
20+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
22+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
24+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
wai-test/Network/Wai/Test.hsnew file+145
@@ -0,0 +1,145 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Network.Wai.Test
3+ ( -- * Session
4+ Session
5+ , runSession
6+ -- * Requests
7+ , request
8+ , srequest
9+ , SRequest (..)
10+ , SResponse (..)
11+ -- * Assertions
12+ , assertStatus
13+ , assertContentType
14+ , assertBody
15+ , assertHeader
16+ , assertNoHeader
17+ ) where
18+import Network.Wai
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)
23+import Data.Map (Map)
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)
34+
35+type Session = ReaderT Application (StateT ClientState IO)
36+
37+data ClientState = ClientState
38+ { clientCookies :: Map ByteString ByteString
39+ }
40+
41+initState :: ClientState
42+initState = ClientState Map.empty
43+
44+runSession :: Session a -> Application -> IO a
45+runSession session app = evalStateT (runReaderT session app) initState
46+
47+data SRequest = SRequest
48+ { simpleRequest :: Request
49+ , simpleRequestBody :: L.ByteString
50+ }
51+data SResponse = SResponse
52+ { simpleStatus :: H.Status
53+ , simpleHeaders :: H.ResponseHeaders
54+ , simpleBody :: L.ByteString
55+ }
56+ deriving (Show, Eq)
57+request :: Request -> Session SResponse
58+request = srequest . flip SRequest L.empty
59+
60+srequest :: SRequest -> Session SResponse
61+srequest (SRequest req bod) = do
62+ app <- ask
63+ res <- liftIO $ run_ $ enumList 4 (L.toChunks bod) $$ app req
64+ sres <- liftIO $ runResponse res
65+ -- FIXME cookie processing
66+ return sres
67+
68+runResponse :: Response -> IO SResponse
69+runResponse res =
70+ responseEnumerator res go
71+ where
72+ go s h = do
73+ bss <- joinI $ builderToByteString $$ consume
74+ return $ SResponse s h $ L.fromChunks bss
75+
76+assertBool :: String -> Bool -> Session ()
77+assertBool s b = liftIO $ H.assertBool s b
78+
79+assertString :: String -> Session ()
80+assertString s = liftIO $ H.assertString s
81+
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 "
87+ , show ct
88+ , ", but no content type provided"
89+ ]
90+ Just ct' -> assertBool (concat
91+ [ "Expected content type "
92+ , show ct
93+ , ", but received "
94+ , show ct'
95+ ]) (go ct == go ct')
96+ where
97+ go = S8.takeWhile (/= ';')
98+
99+assertStatus :: Int -> SResponse -> Session ()
100+assertStatus i SResponse{simpleStatus = s} = assertBool (concat
101+ [ "Expected status code "
102+ , show i
103+ , ", but received "
104+ , show sc
105+ ]) $ i == sc
106+ where
107+ sc = H.statusCode s
108+
109+assertBody :: L.ByteString -> SResponse -> Session ()
110+assertBody lbs SResponse{simpleBody = lbs'} = assertBool (concat
111+ [ "Expected response body "
112+ , show $ L8.unpack lbs
113+ , ", but received "
114+ , show $ L8.unpack lbs'
115+ ]) $ lbs == lbs'
116+
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 "
122+ , show header
123+ , " to be "
124+ , show value
125+ , ", but it was not present"
126+ ]
127+ Just value' -> assertBool (concat
128+ [ "Expected header "
129+ , show header
130+ , " to be "
131+ , show value
132+ , ", but received "
133+ , show value'
134+ ]) (value == value')
135+
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 "
142+ , show header
143+ , " containing "
144+ , show s
145+ ]
wai-test/READMEnew file

No changes to the content.

wai-test/Setup.lhsnew file+8
@@ -0,0 +1,8 @@
1+#!/usr/bin/env runhaskell
2+
3+> module Main where
4+> import Distribution.Simple
5+> import System.Cmd (system)
6+
7+> main :: IO ()
8+> main = defaultMain
wai-test/wai-test.cabalnew file+32
@@ -0,0 +1,32 @@
1+name: wai-test
2+version: 0.1.0.2
3+license: BSD3
4+license-file: LICENSE
5+author: Michael Snoyman <michael@snoyman.com>
6+maintainer: Michael Snoyman <michael@snoyman.com>
7+synopsis: Unit test framework (built on HUnit) for WAI applications.
8+category: Testing, Web, Yesod
9+stability: Stable
10+cabal-version: >= 1.6
11+build-type: Simple
12+homepage: http://github.com/snoyberg/wai-test/
13+
14+library
15+ build-depends: base >= 4 && < 5
16+ , wai >= 0.4 && < 0.5
17+ , bytestring >= 0.9.1.4 && < 0.10
18+ , blaze-builder >= 0.2.1 && < 0.4
19+ , transformers >= 0.2 && < 0.3
20+ , containers >= 0.2 && < 0.5
21+ , enumerator >= 0.4.7 && < 0.5
22+ , cookie >= 0.2 && < 0.4
23+ , blaze-builder-enumerator >= 0.2 && < 0.3
24+ , HUnit >= 1.2 && < 1.3
25+ , http-types >= 0.6 && < 0.7
26+ , case-insensitive >= 0.2 && < 0.4
27+ exposed-modules: Network.Wai.Test
28+ ghc-options: -Wall
29+
30+source-repository head
31+ type: git
32+ location: git://github.com/snoyberg/wai-test.git