hspec-yesod 0.1.0 → 0.2.0
raw patch · 4 files changed
+131/−25 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Test.Hspec.Yesod: [yedRequest] :: YesodExampleData site -> !Maybe (RequestBuilderData site)
+ Test.Hspec.Yesod: formatRequestBuilderDataForDebugging :: RequestBuilderData site -> Text
+ Test.Hspec.Yesod: getLatestRequest :: YesodExample site (Maybe (RequestBuilderData site))
+ Test.Hspec.Yesod: requireLatestRequest :: YesodExample site (RequestBuilderData site)
+ Test.Hspec.Yesod.Internal: BinaryPostData :: ByteString -> RBDPostData
+ Test.Hspec.Yesod.Internal: MultipleItemsPostData :: [RequestPart] -> RBDPostData
+ Test.Hspec.Yesod.Internal: ReqFilePart :: Text -> FilePath -> ByteString -> Text -> RequestPart
+ Test.Hspec.Yesod.Internal: ReqKvPart :: Text -> Text -> RequestPart
+ Test.Hspec.Yesod.Internal: RequestBuilderData :: RBDPostData -> Maybe SResponse -> Method -> site -> [Text] -> Query -> RequestHeaders -> RequestBuilderData site
+ Test.Hspec.Yesod.Internal: [rbdGets] :: RequestBuilderData site -> Query
+ Test.Hspec.Yesod.Internal: [rbdHeaders] :: RequestBuilderData site -> RequestHeaders
+ Test.Hspec.Yesod.Internal: [rbdMethod] :: RequestBuilderData site -> Method
+ Test.Hspec.Yesod.Internal: [rbdPath] :: RequestBuilderData site -> [Text]
+ Test.Hspec.Yesod.Internal: [rbdPostData] :: RequestBuilderData site -> RBDPostData
+ Test.Hspec.Yesod.Internal: [rbdResponse] :: RequestBuilderData site -> Maybe SResponse
+ Test.Hspec.Yesod.Internal: [rbdSite] :: RequestBuilderData site -> site
+ Test.Hspec.Yesod.Internal: data RBDPostData
+ Test.Hspec.Yesod.Internal: data RequestBuilderData site
+ Test.Hspec.Yesod.Internal: data RequestPart
- Test.Hspec.Yesod: YesodExampleData :: !site -> Middleware -> IO Application -> !Middleware -> !site -> !Cookies -> !Maybe SResponse -> !IO () -> YesodExampleData site
+ Test.Hspec.Yesod: YesodExampleData :: !site -> Middleware -> IO Application -> !Middleware -> !site -> !Cookies -> !Maybe (RequestBuilderData site) -> !Maybe SResponse -> !IO () -> YesodExampleData site
- Test.Hspec.Yesod: data () => SIO s a
+ Test.Hspec.Yesod: data SIO s a
- Test.Hspec.Yesod: type RequestBuilder site = SIO (RequestBuilderData site)
+ Test.Hspec.Yesod: type RequestBuilder site = SIO RequestBuilderData site
- Test.Hspec.Yesod: type YSpec site = SpecWith (YesodExampleData site)
+ Test.Hspec.Yesod: type YSpec site = SpecWith YesodExampleData site
- Test.Hspec.Yesod: type YesodExample site = SIO (YesodExampleData site)
+ Test.Hspec.Yesod: type YesodExample site = SIO YesodExampleData site
- Test.Hspec.Yesod: type YesodSpec site = SpecWith (YesodExampleData site)
+ Test.Hspec.Yesod: type YesodSpec site = SpecWith YesodExampleData site
Files
- ChangeLog.md +10/−0
- hspec-yesod.cabal +2/−2
- src/Test/Hspec/Yesod.hs +48/−23
- src/Test/Hspec/Yesod/Internal.hs +71/−0
ChangeLog.md view
@@ -1,5 +1,15 @@ # ChangeLog for hspec-yesod +## 0.2.0++- `statusIs` now prints the request method, path, and query string on failing requests+- Tracks the latest request via `yedRequest` on `YesodExampleData`+- Adds `getLatestRequest` and `requireLatestRequest` as helpful accessors for it +- Adds `formatRequestBuilderDataForDebugging` to format the request for use in error messages+- Adds a new `.Internal` module to access fields of `RequestBuilderData`++Together these changes are intended to allow for better error messages, like the one made to `statusIs`.+ ## 0.1.0 - Initial release and fork from `yesod-test`.
hspec-yesod.cabal view
@@ -1,5 +1,5 @@ name: hspec-yesod-version: 0.1.0+version: 0.2.0 license: MIT license-file: LICENSE author: Nubis <nubis@woobiz.com.ar>, Matt Parsons <parsonsmatt@gmail.com>@@ -44,7 +44,7 @@ , yesod-core >= 1.6.17 , yesod-test - exposed-modules: Test.Hspec.Yesod+ exposed-modules: Test.Hspec.Yesod, Test.Hspec.Yesod.Internal ghc-options: -Wall -fwarn-redundant-constraints hs-source-dirs: src
src/Test/Hspec/Yesod.hs view
@@ -219,6 +219,9 @@ -- * Grab information , getTestYesod+ , getLatestRequest+ , requireLatestRequest+ , formatRequestBuilderDataForDebugging , getResponse , getRequestCookies @@ -278,6 +281,7 @@ import qualified Yesod.Test.Internal.SIO as YT.SIO import Yesod.Test.Internal.SIO (SIO, execSIO, runSIO) import qualified Yesod.Test.Internal as YT.Internal (getBodyTextPreview, contentTypeHeaderIsUtf8)+import Test.Hspec.Yesod.Internal -- | The state used in a single test case defined using 'yit' --@@ -287,6 +291,7 @@ , yedMiddleware :: !Middleware , yedSite :: !site , yedCookies :: !Cookies+ , yedRequest :: !(Maybe (RequestBuilderData site)) , yedResponse :: !(Maybe SResponse) , yedTestCleanup :: !(IO ()) }@@ -317,30 +322,31 @@ getTestYesod :: YesodExample site site getTestYesod = fmap yedSite MS.get +-- | Get the most recently provided request value, if available.+--+-- This is intended to allow assertions to add context on the request in their error messages.+-- Currently the contents of RequestBuilderData are only accessible via Test.Hspec.Yesod.Internal and formatRequestBuilderDataForDebugging.+--+-- @since 0.2.0+getLatestRequest :: YesodExample site (Maybe (RequestBuilderData site))+getLatestRequest = fmap yedRequest MS.get++-- | Like 'getLatestRequest', but throws an error if no request has been made.+--+-- @since 0.2.0+requireLatestRequest :: YesodExample site (RequestBuilderData site)+requireLatestRequest = do+ mRequest <- getLatestRequest+ case mRequest of+ Nothing -> failure "Called requireLatestRequest, but no request has been made"+ Just r -> pure r+ -- | Get the most recently provided response value, if available. -- -- Since 1.2.0 getResponse :: YesodExample site (Maybe SResponse) getResponse = fmap yedResponse MS.get -data RequestBuilderData site = RequestBuilderData- { rbdPostData :: RBDPostData- , rbdResponse :: (Maybe SResponse)- , rbdMethod :: H.Method- , rbdSite :: site- , rbdPath :: [T.Text]- , rbdGets :: H.Query- , rbdHeaders :: H.RequestHeaders- }--data RBDPostData = MultipleItemsPostData [RequestPart]- | BinaryPostData BSL8.ByteString---- | Request parts let us discern regular key/values from files sent in the request.-data RequestPart- = ReqKvPart T.Text T.Text- | ReqFilePart T.Text FilePath BSL8.ByteString T.Text- -- | The 'RequestBuilder' state monad constructs a URL encoded string of arguments -- to send with your requests. Some of the functions that run on it use the current -- response to analyze the forms that the server is expecting to receive.@@ -362,6 +368,7 @@ , yedMiddleware = id , yedSite = site , yedCookies = M.empty+ , yedRequest = Nothing , yedResponse = Nothing , yedTestCleanup = pure () }@@ -393,6 +400,7 @@ , yedMiddleware = id , yedSite = site , yedCookies = M.empty+ , yedRequest = Nothing , yedResponse = Nothing , yedTestCleanup = pure () }@@ -591,6 +599,7 @@ -- > statusIs 200 statusIs :: HasCallStack => Int -> YesodExample site () statusIs number = do+ latestRequest <- requireLatestRequest withResponse $ \(SResponse status headers body) -> do let mContentType = lookup hContentType headers isUTF8ContentType = maybe False YT.Internal.contentTypeHeaderIsUtf8 mContentType@@ -598,6 +607,7 @@ liftIO $ flip HUnit.assertBool (H.statusCode status == number) $ concat [ "Expected status was ", show number , " but received status was ", show $ H.statusCode status+ , " for " <> (T.unpack $ formatRequestBuilderDataForDebugging latestRequest) , if isUTF8ContentType then ". For debugging, the body was: " <> (T.unpack $ YT.Internal.getBodyTextPreview body) else ""@@ -1359,6 +1369,22 @@ mkApplication = do liftIO . yesodExampleDataToApplication =<< MS.get +-- | Provides a helpful summary of the request, meant to be used in assertion functions+--+-- Currently formats as METHOD PATH?QUERY, e.g. GET /foo/bar?q=true+-- The exact format is subject to change.+--+-- @since 0.2.0+formatRequestBuilderDataForDebugging :: RequestBuilderData site -> T.Text+formatRequestBuilderDataForDebugging RequestBuilderData{..} =+ (TE.decodeUtf8 rbdMethod) <> " " <> getEncodedPath rbdPath <> (TE.decodeUtf8 $ H.renderQuery True rbdGets)++getEncodedPath :: [T.Text] -> T.Text+getEncodedPath pathSegments = + if null pathSegments + then "/"+ else TE.decodeUtf8 $ Builder.toByteString $ H.encodePathSegments pathSegments+ -- | The general interface for performing requests. 'request' takes a 'RequestBuilder', -- constructs a request, and executes it. --@@ -1381,7 +1407,7 @@ mRes <- MS.gets yedResponse oldCookies <- MS.gets yedCookies - RequestBuilderData {..} <- liftIO $ execSIO reqBuilder RequestBuilderData+ rbd@RequestBuilderData {..} <- liftIO $ execSIO reqBuilder RequestBuilderData { rbdPostData = MultipleItemsPostData [] , rbdResponse = mRes , rbdMethod = "GET"@@ -1390,9 +1416,7 @@ , rbdGets = [] , rbdHeaders = [] }- let path- | null rbdPath = "/"- | otherwise = TE.decodeUtf8 $ Builder.toByteString $ H.encodePathSegments rbdPath+ let path = getEncodedPath rbdPath -- expire cookies and filter them for the current path. TODO: support max age currentUtc <- liftIO getCurrentTime@@ -1421,7 +1445,7 @@ }) app let newCookies = parseSetCookies $ simpleHeaders response cookies' = M.fromList [(Cookie.setCookieName c, c) | c <- newCookies] `M.union` cookies- modify $ \e -> e { yedCookies = cookies', yedResponse = Just response }+ modify $ \e -> e { yedCookies = cookies', yedRequest = Just rbd, yedResponse = Just response } where isFile (ReqFilePart _ _ _ _) = True isFile _ = False@@ -1545,6 +1569,7 @@ , yedMiddleware = id , yedSite = site , yedCookies = M.empty+ , yedRequest = Nothing , yedResponse = Nothing , yedTestCleanup = pure () }
+ src/Test/Hspec/Yesod/Internal.hs view
@@ -0,0 +1,71 @@+-- | This module exposes functions that are used internally by yesod-test. The functions exposed here are _not_ a stable API—they may be changed or removed without any major version bump.+--+-- That said, you may find them useful if your application can accept API breakage.+module Test.Hspec.Yesod.Internal+ ( RequestBuilderData(..)+ , RBDPostData(..)+ , RequestPart(..)+ ) where++import Control.Monad.Catch (finally)+import Test.Hspec.Core.Spec+import Test.Hspec.Core.Hooks+import qualified Data.List as DL+import qualified Data.ByteString.Char8 as BS8+import Data.ByteString (ByteString)+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Encoding.Error as TErr+import qualified Data.ByteString.Lazy.Char8 as BSL8+import qualified Test.HUnit as HUnit+import qualified Network.HTTP.Types as H+import qualified Network.Socket as Sock+import Data.CaseInsensitive (CI)+import qualified Data.CaseInsensitive as CI+import Network.Wai+import Network.Wai.Test hiding (assertHeader, assertNoHeader, request)+import Control.Monad.IO.Class+import qualified Control.Monad.State.Class as MS+import Control.Monad.State.Class hiding (get)+import System.IO+import Yesod.Core.Unsafe (runFakeHandler)+import Yesod.Core+import qualified Data.Text.Lazy as TL+import Data.Text.Lazy.Encoding (encodeUtf8, decodeUtf8, decodeUtf8With)+import Text.XML.Cursor hiding (element)+import qualified Text.XML.Cursor as C+import qualified Text.HTML.DOM as HD+import qualified Data.Map as M+import qualified Web.Cookie as Cookie+import qualified Blaze.ByteString.Builder as Builder+import Data.Time.Clock (getCurrentTime)+import Control.Applicative ((<$>))+import Text.Show.Pretty (ppShow)+import Data.Monoid (mempty)+import Data.ByteArray.Encoding (convertToBase, Base(..))+import Network.HTTP.Types.Header (hContentType)+import Data.Aeson (eitherDecode')+import Control.Monad+import qualified Yesod.Test.TransversingCSS as YT.CSS+import Yesod.Test.TransversingCSS (HtmlLBS, Query)+import qualified Yesod.Test.Internal.SIO as YT.SIO+import Yesod.Test.Internal.SIO (SIO, execSIO, runSIO)+import qualified Yesod.Test.Internal as YT.Internal (getBodyTextPreview, contentTypeHeaderIsUtf8)++data RequestBuilderData site = RequestBuilderData+ { rbdPostData :: RBDPostData+ , rbdResponse :: (Maybe SResponse)+ , rbdMethod :: H.Method+ , rbdSite :: site+ , rbdPath :: [T.Text]+ , rbdGets :: H.Query+ , rbdHeaders :: H.RequestHeaders+ }++data RBDPostData = MultipleItemsPostData [RequestPart]+ | BinaryPostData BSL8.ByteString++-- | Request parts let us discern regular key/values from files sent in the request.+data RequestPart+ = ReqKvPart T.Text T.Text+ | ReqFilePart T.Text FilePath BSL8.ByteString T.Text