hspec-wai 0.6.3 → 0.6.4
raw patch · 8 files changed
+321/−30 lines, 8 filesdep +base-compatdep ~bytestringdep ~hspec-coredep ~wai
Dependencies added: base-compat
Dependency ranges changed: bytestring, hspec-core, wai, wai-extra
Files
- LICENSE +1/−1
- hspec-wai.cabal +46/−26
- src/Test/Hspec/Wai/Internal.hs +3/−1
- src/Test/Hspec/Wai/Matcher.hs +3/−2
- src/Test/Hspec/Wai/QuickCheck.hs +44/−0
- test/Test/Hspec/Wai/MatcherSpec.hs +82/−0
- test/Test/Hspec/Wai/UtilSpec.hs +80/−0
- test/Test/Hspec/WaiSpec.hs +62/−0
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2012-2014 Fujimura Daisuke, Simon Hengel+Copyright (c) 2012-2015 Fujimura Daisuke, Simon Hengel Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
hspec-wai.cabal view
@@ -1,37 +1,39 @@+-- This file has been generated from package.yaml by hpack version 0.8.0.+--+-- see: https://github.com/sol/hpack+ name: hspec-wai-version: 0.6.3+version: 0.6.4+homepage: https://github.com/hspec/hspec-wai#readme+bug-reports: https://github.com/hspec/hspec-wai/issues license: MIT license-file: LICENSE copyright: (c) 2012-2014 Fujimura Daisuke,- (c) 2014 Simon Hengel-author: Fujimura Daisuke <me@fujimuradaisuke.com>, Simon Hengel <sol@typeful.net>-maintainer: Fujimura Daisuke <me@fujimuradaisuke.com>, Simon Hengel <sol@typeful.net>+ (c) 2014-2015 Simon Hengel+author: Fujimura Daisuke <me@fujimuradaisuke.com>,+ Simon Hengel <sol@typeful.net>+maintainer: Fujimura Daisuke <me@fujimuradaisuke.com>,+ Simon Hengel <sol@typeful.net> build-type: Simple-cabal-version: >= 1.8+cabal-version: >= 1.10 category: Testing synopsis: Experimental Hspec support for testing WAI applications description: Experimental Hspec support for testing WAI applications extra-source-files:- changelog+ changelog source-repository head type: git location: https://github.com/hspec/hspec-wai library- ghc-options:- -Wall hs-source-dirs: src- exposed-modules:- Test.Hspec.Wai- Test.Hspec.Wai.Internal- other-modules:- Test.Hspec.Wai.Util- Test.Hspec.Wai.Matcher+ ghc-options: -Wall build-depends: base == 4.*+ , base-compat , bytestring >= 0.10 , text , transformers@@ -41,27 +43,45 @@ , wai-extra >= 3 , hspec-core == 2.* , hspec-expectations+ , QuickCheck+ exposed-modules:+ Test.Hspec.Wai+ Test.Hspec.Wai.QuickCheck+ Test.Hspec.Wai.Internal+ other-modules:+ Test.Hspec.Wai.Matcher+ Test.Hspec.Wai.Util+ default-language: Haskell2010 test-suite spec- type:- exitcode-stdio-1.0- ghc-options:- -Wall+ type: exitcode-stdio-1.0+ main-is: Spec.hs hs-source-dirs:- src, test- main-is:- Spec.hs+ src+ , test+ ghc-options: -Wall build-depends: base == 4.*- , bytestring+ , base-compat+ , bytestring >= 0.10 , text , transformers , case-insensitive , http-types- , wai- , wai-extra- , hspec-core+ , wai >= 3+ , wai-extra >= 3+ , hspec-core == 2.* , hspec-expectations-+ , QuickCheck , hspec , QuickCheck+ other-modules:+ Test.Hspec.Wai+ Test.Hspec.Wai.Internal+ Test.Hspec.Wai.Matcher+ Test.Hspec.Wai.QuickCheck+ Test.Hspec.Wai.Util+ Test.Hspec.Wai.MatcherSpec+ Test.Hspec.Wai.UtilSpec+ Test.Hspec.WaiSpec+ default-language: Haskell2010
src/Test/Hspec/Wai/Internal.hs view
@@ -11,7 +11,9 @@ , formatHeader ) where -import Control.Applicative+import Prelude ()+import Prelude.Compat+ import Control.Monad.IO.Class import Control.Monad.Trans.Reader import Network.Wai (Application)
src/Test/Hspec/Wai/Matcher.hs view
@@ -6,10 +6,11 @@ , match ) where -import Control.Applicative+import Prelude ()+import Prelude.Compat+ import Control.Monad import Data.Maybe-import Data.Monoid import Data.String import Data.Text.Lazy.Encoding (encodeUtf8) import Data.ByteString (ByteString)
+ src/Test/Hspec/Wai/QuickCheck.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE FlexibleInstances #-}+module Test.Hspec.Wai.QuickCheck (+ property+, (==>)++-- * Re-exports+, Arbitrary (..)+, module Test.QuickCheck.Gen++-- * Internals+, Testable (..)+, WaiProperty (..)+) where++import Test.QuickCheck hiding (Testable, property, (==>))+import qualified Test.QuickCheck as QuickCheck+import Test.QuickCheck.Gen+import Network.Wai (Application)++import Test.Hspec.Wai.Internal++property :: Testable a => a -> Application -> Property+property = unWaiProperty . toProperty++data WaiProperty = WaiProperty {unWaiProperty :: Application -> Property}++class Testable a where+ toProperty :: a -> WaiProperty++instance Testable WaiProperty where+ toProperty = id++instance Testable WaiExpectation where+ toProperty action = WaiProperty (QuickCheck.property . runWaiSession action)++instance (Arbitrary a, Show a, Testable prop) => Testable (a -> prop) where+ toProperty prop = WaiProperty $ QuickCheck.property . (flip $ unWaiProperty . toProperty . prop)++infixr 0 ==>+(==>) :: Testable prop => Bool -> prop -> WaiProperty+(==>) = lift (QuickCheck.==>)++lift :: Testable prop => (a -> Property -> Property) -> a -> prop -> WaiProperty+lift f a prop = WaiProperty $ \app -> f a (unWaiProperty (toProperty prop) app)
+ test/Test/Hspec/Wai/MatcherSpec.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE OverloadedStrings #-}+module Test.Hspec.Wai.MatcherSpec (main, spec) where++import Test.Hspec++import Network.HTTP.Types+import Network.Wai.Test++import Test.Hspec.Wai.Matcher++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+ describe "match" $ do+ context "when both status and body do match" $ do+ it "returns Nothing" $ do+ SResponse status200 [] "" `match` 200+ `shouldBe` Nothing++ context "when status does not match" $ do+ it "returns an error message" $ do+ SResponse status404 [] "" `match` 200+ `shouldBe` (Just . unlines) [+ "status mismatch:"+ , " expected: 200"+ , " but got: 404"+ ]++ context "when body does not match" $ do+ it "returns an error message" $ do+ SResponse status200 [] "foo" `match` "bar"+ `shouldBe` (Just . unlines) [+ "body mismatch:"+ , " expected: bar"+ , " but got: foo"+ ]++ context "when one body contains unsafe characters" $ do+ it "uses show for both bodies in the error message" $ do+ SResponse status200 [] "foo\nbar" `match` "bar"+ `shouldBe` (Just . unlines) [+ "body mismatch:"+ , " expected: \"bar\""+ , " but got: \"foo\\nbar\""+ ]++ context "when both status and body do not match" $ do+ it "combines error messages" $ do+ SResponse status404 [] "foo" `match` "bar"+ `shouldBe` (Just . unlines) [+ "status mismatch:"+ , " expected: 200"+ , " but got: 404"+ , "body mismatch:"+ , " expected: bar"+ , " but got: foo"+ ]++ context "when matching headers" $ do+ context "when header is missing" $ do+ it "returns an error message" $ do+ SResponse status200 [] "" `match` 200 {matchHeaders = ["Content-Type" <:> "application/json"]}+ `shouldBe` (Just . unlines) [+ "missing header:"+ , " Content-Type: application/json"+ , "the actual headers were:"+ ]++ context "when multiple headers are missing" $ do+ it "combines error messages" $ do+ let expectedHeaders = ["Content-Type" <:> "application/json", "Content-Encoding" <:> "chunked"]+ SResponse status200 [(hContentLength, "23")] "" `match` 200 {matchHeaders = expectedHeaders}+ `shouldBe` (Just . unlines) [+ "missing header:"+ , " Content-Type: application/json"+ , "missing header:"+ , " Content-Encoding: chunked"+ , "the actual headers were:"+ , " Content-Length: 23"+ ]
+ test/Test/Hspec/Wai/UtilSpec.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE OverloadedStrings #-}+module Test.Hspec.Wai.UtilSpec (main, spec) where++import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck+import Test.Hspec.Wai.Util+import Data.Monoid+import Network.HTTP.Types (parseSimpleQuery)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as LB+import qualified Data.Text as T+import qualified Data.Text.Encoding as T++main :: IO ()+main = hspec spec++decodePair :: (ByteString, ByteString) -> (String, String)+decodePair (a, b) = (decode a, decode b)+ where+ decode :: ByteString -> String+ decode = newlineNormalize . T.unpack . T.decodeUtf8++ newlineNormalize :: String -> String+ newlineNormalize input = case input of+ [] -> []+ '\r' : '\n' : xs -> '\n': newlineNormalize xs+ x : xs -> x : newlineNormalize xs++spec :: Spec+spec = do+ describe "formUrlEncodeQuery" $ do+ it "separates keys from values by =" $ do+ formUrlEncodeQuery [("foo", "bar")] `shouldBe` "foo=bar"++ it "separates pairs by &" $ do+ formUrlEncodeQuery [("foo", "bar"), ("foo", "baz")] `shouldBe` "foo=bar&foo=baz"++ it "applies newline normalization" $ do+ formUrlEncodeQuery [("text", "foo\nbar\nbaz\n")] `shouldBe` "text=foo%0D%0Abar%0D%0Abaz%0D%0A"++ it "handles Unicode characters" $ do+ formUrlEncodeQuery [("foo","bar-\955-baz")] `shouldBe` "foo=bar-%CE%BB-baz"++ context "when encoding characters in the printable ASCII range" $ do+ let input = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"+ reference = "+%21%22%23%24%25%26%27%28%29*%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D%7E"++ it "behaves like Firefox" $ do+ formUrlEncodeQuery [("foo", input)] `shouldBe` "foo=" <> reference++ modifyMaxSize (`div` 5) $ do+ it "can be reverted by Network.HTTP.Types.parseSimpleQuery" $ do+ property $ \xs -> do+ (map decodePair . parseSimpleQuery . LB.toStrict . formUrlEncodeQuery) xs `shouldBe` xs++ describe "safeToString" $ do+ context "when used on an empty string" $ do+ it "returns Nothing" $ do+ safeToString "" `shouldBe` Nothing++ describe "formatHeader" $ do+ it "formats header" $ do+ let header = ("Content-Type", "application/json")+ formatHeader header `shouldBe` " Content-Type: application/json"++ describe "when ends with whitespace" $ do+ it "uses show" $ do+ let header = ("Content-Type", "application/json ")+ formatHeader header `shouldBe` " " ++ show header++ describe "when contains non-print characters" $ do+ it "uses show" $ do+ let header = ("Content-\nType", "application/json")+ formatHeader header `shouldBe` " " ++ show header++ describe "when header is not decodable as UTF-8" $ do+ it "uses show" $ do+ let header = ("Content-Type", "\xc3\x28")+ formatHeader header `shouldBe` " " ++ show header
+ test/Test/Hspec/WaiSpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE OverloadedStrings #-}+module Test.Hspec.WaiSpec (main, spec) where++import Test.Hspec+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as BL+import Network.HTTP.Types+import Network.Wai++import Test.Hspec.Wai++main :: IO ()+main = hspec spec++expectMethod :: Method -> Application+expectMethod method req respond = do+ requestMethod req `shouldBe` method+ respond $ responseLBS status200 [] ""++expectRequest :: Method -> ByteString -> ByteString -> [Header] -> Application+expectRequest method path body headers req respond = do+ requestMethod req `shouldBe` method+ rawPathInfo req `shouldBe` path+ requestHeaders req `shouldBe` headers+ rawBody <- requestBody req+ rawBody `shouldBe` body+ respond $ responseLBS status200 [] ""++spec :: Spec+spec = do+ describe "get" $ with (return $ expectMethod methodGet) $+ it "sends a get request" $+ get "/" `shouldRespondWith` 200++ describe "post" $ with (return $ expectMethod methodPost) $+ it "sends a post request" $+ post "/" "" `shouldRespondWith` 200++ describe "put" $ with (return $ expectMethod methodPut) $+ it "sends a put request" $+ put "/" "" `shouldRespondWith` 200++ describe "options" $ with (return $ expectMethod methodOptions) $+ it "sends an options request" $+ options "/" `shouldRespondWith` 200++ describe "delete" $ with (return $ expectMethod methodDelete) $+ it "sends a delete request" $+ delete "/" `shouldRespondWith` 200++ describe "request" $ with (return $ expectRequest methodGet "/foo" body accept) $+ it "sends method, path, headers, and body" $+ request methodGet "/foo" accept (BL.fromChunks [body]) `shouldRespondWith` 200++ describe "postHtmlForm" $ with (return $ expectRequest methodPost "/foo" "foo=bar" formEncoded) $+ it "sends a post request with form-encoded params" $+ postHtmlForm "/foo" [("foo", "bar")] `shouldRespondWith` 200++ where+ accept = [(hAccept, "application/json")]+ body = "{\"foo\": 1}"+ formEncoded = [(hContentType, "application/x-www-form-urlencoded")]