@@ -1,32 +1,33 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2module Servant.QuickCheck.InternalSpec (spec) where
2module Servant.QuickCheck.InternalSpec (spec) where
4−import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
5+import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
6+import Control.Exception (SomeException)
5−import Control.Monad (replicateM)
7+import Control.Monad (replicateM)
6−import Control.Monad.IO.Class (liftIO)
8+import Control.Monad.IO.Class (liftIO)
7−import qualified Data.ByteString as BS
9+import qualified Data.ByteString as BS
8−import qualified Data.ByteString.Char8 as C
10+import qualified Data.ByteString.Char8 as C
9−import Data.Maybe (fromJust)
11+import Data.Maybe (fromJust)
12+import Network.HTTP.Client (path, queryString)
10import Prelude.Compat
13import Prelude.Compat
11import Servant
14import Servant
15+import Servant.HTML.Blaze (HTML)
16+import qualified Text.Blaze.Html as Blaze
17+import qualified Text.Blaze.Html5 as Blaze5
12−import Test.Hspec (Spec, context, describe, it, shouldBe,
18+import Test.Hspec (Spec, context, describe, it, shouldBe,
13− shouldContain)
19+ shouldContain)
14−import Test.Hspec.Core.Spec (Arg, Example, Result (..),
20+import Test.Hspec.Core.Spec (Arg, Example, Result (..), ResultStatus (..),
15− defaultParams, evaluateExample)
21+ defaultParams, safeEvaluateExample)
16−import Test.QuickCheck.Gen (unGen, generate)
22+import Test.QuickCheck.Gen (generate, unGen)
17−import Test.QuickCheck.Random (mkQCGen)
23+import Test.QuickCheck.Random (mkQCGen)
18−import Network.HTTP.Client (queryString, path)
20−#if MIN_VERSION_servant(0,8,0)
21−import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
23−import Servant.API.Internal.Test.ComprehensiveAPI (ComprehensiveAPI,
27−import Servant.QuickCheck
28−import Servant.QuickCheck.Internal (genRequest, runGenRequest, serverDoesntSatisfy)
26+import Servant.Test.ComprehensiveAPI (comprehensiveAPIWithoutStreamingOrRaw)
28+import Servant.QuickCheck
29+import Servant.QuickCheck.Internal (genRequest, runGenRequest,
31spec :: Spec
32spec :: Spec
@@ -38,6 +39,7 @@ spec = do
38 queryParamsSpec
39 queryParamsSpec
39 queryFlagsSpec
40 queryFlagsSpec
40 deepPathSpec
41 deepPathSpec
41 unbiasedGenerationSpec
43 unbiasedGenerationSpec
43serversEqualSpec :: Spec
45serversEqualSpec :: Spec
@@ -50,13 +52,42 @@ serversEqualSpec = describe "serversEqual" $ do
51 context "when servers are not equal" $ do
53 context "when servers are not equal" $ do
52 it "provides the failing responses in the error message" $ do
54 it "provides the failing responses in the error message" $ do
53− Fail _ err <- withServantServer api2 server2 $ \burl1 ->
55+ FailedWith err <- withServantServer api2 server2 $ \burl1 ->
54 withServantServer api2 server3 $ \burl2 -> do
56 withServantServer api2 server3 $ \burl2 -> do
55 evalExample $ serversEqual api2 burl1 burl2 args bodyEquality
57 evalExample $ serversEqual api2 burl1 burl2 args bodyEquality
58+ show err `shouldContain` "Server equality failed"
56 show err `shouldContain` "Body: 1"
59 show err `shouldContain` "Body: 1"
57 show err `shouldContain` "Body: 2"
60 show err `shouldContain` "Body: 2"
58 show err `shouldContain` "Path: /failplz"
61 show err `shouldContain` "Path: /failplz"
63+ context "when JSON is equal but looks a bit different as a ByteString" $ do
65+ it "sanity check: different whitespace same JSON objects bodyEquality fails" $ do
66+ FailedWith err <- withServantServer jsonApi jsonServer1 $ \burl1 ->
67+ withServantServer jsonApi jsonServer2 $ \burl2 -> do
68+ evalExample $ serversEqual jsonApi burl1 burl2 args bodyEquality
69+ show err `shouldContain` "Server equality failed"
71+ it "jsonEquality considers equal JSON apis equal regardless of key ordering or whitespace" $ do
72+ withServantServerAndContext jsonApi ctx jsonServer1 $ \burl1 ->
73+ withServantServerAndContext jsonApi ctx jsonServer2 $ \burl2 ->
74+ serversEqual jsonApi burl1 burl2 args jsonEquality
76+ it "sees when JSON apis are not equal because any value is different" $ do
77+ FailedWith err <- withServantServer jsonApi jsonServer2 $ \burl1 ->
78+ withServantServer jsonApi jsonServer3 $ \burl2 -> do
79+ evalExample $ serversEqual jsonApi burl1 burl2 args jsonEquality
80+ show err `shouldContain` "Server equality failed"
81+ show err `shouldContain` "Path: /jsonComparison"
83+ it "sees when JSON apis are not equal due to different keys but same values" $ do
84+ FailedWith err <- withServantServer jsonApi jsonServer2 $ \burl1 ->
85+ withServantServer jsonApi jsonServer4 $ \burl2 -> do
86+ evalExample $ serversEqual jsonApi burl1 burl2 args jsonEquality
87+ show err `shouldContain` "Server equality failed"
88+ show err `shouldContain` "Path: /jsonComparison"
60serverSatisfiesSpec :: Spec
91serverSatisfiesSpec :: Spec
61serverSatisfiesSpec = describe "serverSatisfies" $ do
92serverSatisfiesSpec = describe "serverSatisfies" $ do
@@ -77,34 +108,38 @@ serverSatisfiesSpec = describe "serverSatisfies" $ do
77 context "when predicates are false" $ do
108 context "when predicates are false" $ do
79 it "fails with informative error messages" $ do
110 it "fails with informative error messages" $ do
80− Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
111+ FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
81 evalExample $ serverSatisfies api burl args (notAllowedContainsAllowHeader <%> mempty)
112 evalExample $ serverSatisfies api burl args (notAllowedContainsAllowHeader <%> mempty)
82− err `shouldContain` "notAllowedContainsAllowHeader"
113+ show err `shouldContain` "notAllowedContainsAllowHeader"
83− err `shouldContain` "Headers"
114+ show err `shouldContain` "Headers"
84− err `shouldContain` "Body"
115+ show err `shouldContain` "Body"
87onlyJsonObjectSpec :: Spec
118onlyJsonObjectSpec :: Spec
88onlyJsonObjectSpec = describe "onlyJsonObjects" $ do
119onlyJsonObjectSpec = describe "onlyJsonObjects" $ do
90 it "fails correctly" $ do
121 it "fails correctly" $ do
91− Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
122+ FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
92 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
123 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
93 (onlyJsonObjects <%> mempty)
124 (onlyJsonObjects <%> mempty)
94− err `shouldContain` "onlyJsonObjects"
125+ show err `shouldContain` "onlyJsonObjects"
96 it "accepts non-JSON endpoints" $ do
127 it "accepts non-JSON endpoints" $ do
97 withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl ->
128 withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl ->
98 serverSatisfies octetAPI burl args (onlyJsonObjects <%> mempty)
129 serverSatisfies octetAPI burl args (onlyJsonObjects <%> mempty)
131+ it "does not fail when there is no content-type" $ do
132+ withServantServerAndContext api2 ctx serverFailing $ \burl ->
133+ serverSatisfies api2 burl args (onlyJsonObjects <%> mempty)
100notLongerThanSpec :: Spec
135notLongerThanSpec :: Spec
101notLongerThanSpec = describe "notLongerThan" $ do
136notLongerThanSpec = describe "notLongerThan" $ do
103 it "fails correctly" $ do
138 it "fails correctly" $ do
104− Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
139+ FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
105 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
140 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
106 (notLongerThan 1 <%> mempty)
141 (notLongerThan 1 <%> mempty)
107− err `shouldContain` "notLongerThan"
142+ show err `shouldContain` "notLongerThan"
109 it "succeeds correctly" $ do
144 it "succeeds correctly" $ do
110 withServantServerAndContext api ctx server $ \burl ->
145 withServantServerAndContext api ctx server $ \burl ->
@@ -114,7 +149,7 @@ isComprehensiveSpec :: Spec
114isComprehensiveSpec = describe "HasGenRequest" $ do
149isComprehensiveSpec = describe "HasGenRequest" $ do
116 it "has instances for all 'servant' combinators" $ do
151 it "has instances for all 'servant' combinators" $ do
117− let _g = genRequest comprehensiveAPIWithoutRaw
152+ let _g = genRequest comprehensiveAPIWithoutStreamingOrRaw
118 True `shouldBe` True -- This is a type-level check
153 True `shouldBe` True -- This is a type-level check
120deepPathSpec :: Spec
155deepPathSpec :: Spec
@@ -150,6 +185,25 @@ queryFlagsSpec = describe "QueryFlags" $ do
150 qs = C.unpack $ queryString req
185 qs = C.unpack $ queryString req
151 qs `shouldBe` "one&two"
186 qs `shouldBe` "one&two"
188+htmlDocTypesSpec :: Spec
189+htmlDocTypesSpec = describe "HtmlDocTypes" $ do
191+ it "fails HTML without doctype correctly" $ do
192+ err <- withServantServerAndContext docTypeApi ctx noDocTypeServer $ \burl -> do
193+ evalExample $ serverSatisfies docTypeApi burl args
194+ (htmlIncludesDoctype <%> mempty)
195+ show err `shouldContain` "htmlIncludesDoctype"
197+ it "passes HTML with a doctype at start" $ do
198+ withServantServerAndContext docTypeApi ctx docTypeServer $ \burl ->
199+ serverSatisfies docTypeApi burl args (htmlIncludesDoctype <%> mempty)
201+ it "accepts json endpoints and passes over them in silence" $ do
202+ withServantServerAndContext api ctx server $ \burl -> do
203+ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
204+ (htmlIncludesDoctype <%> mempty)
153makeRandomRequest :: Proxy LargeAPI -> BaseUrl -> IO Integer
207makeRandomRequest :: Proxy LargeAPI -> BaseUrl -> IO Integer
154makeRandomRequest large burl = do
208makeRandomRequest large burl = do
155 req <- generate $ runGenRequest large
209 req <- generate $ runGenRequest large
@@ -166,8 +220,8 @@ unbiasedGenerationSpec = describe "Unbiased Generation of requests" $
166 let mean = (sum $ map fromIntegral someRequests) / runs
220 let mean = (sum $ map fromIntegral someRequests) / runs
167 let variancer x = let ix = fromIntegral x in (ix - mean) * (ix - mean)
221 let variancer x = let ix = fromIntegral x in (ix - mean) * (ix - mean)
168 let variance = (sum $ map variancer someRequests) / runs - 1
222 let variance = (sum $ map variancer someRequests) / runs - 1
169− -- mean should be around 8
223+ -- mean should be around 8.5. If this fails, we likely need more runs (or there's a bug!)
170− mean > 7 `shouldBe` True
224+ mean > 8 `shouldBe` True
171 mean < 9 `shouldBe` True
225 mean < 9 `shouldBe` True
172 -- Std dev is likely around 4. Variance is probably greater than 20.
226 -- Std dev is likely around 4. Variance is probably greater than 20.
173 variance > 19.5 `shouldBe` True
227 variance > 19.5 `shouldBe` True
@@ -219,7 +273,23 @@ server2 = return $ return 1
219server3 :: IO (Server API2)
273server3 :: IO (Server API2)
220server3 = return $ return 2
274server3 = return $ return 2
276+serverFailing :: IO (Server API2)
277+serverFailing = return . throwError $ err405
280+type HtmlDoctype = Get '[HTML] Blaze.Html
282+docTypeApi :: Proxy HtmlDoctype
285+docTypeServer :: IO (Server HtmlDoctype)
286+docTypeServer = pure $ pure $ Blaze5.docTypeHtml $ Blaze5.span "Hello Test!"
288+noDocTypeServer :: IO (Server HtmlDoctype)
289+noDocTypeServer = pure $ pure $ Blaze.text "Hello Test!"
292+-- Api for unbiased generation of requests tests
223largeApi :: Proxy LargeAPI
293largeApi :: Proxy LargeAPI
224largeApi = Proxy
294largeApi = Proxy
@@ -250,17 +320,46 @@ octetAPI = Proxy
250serverOctetAPI :: IO (Server OctetAPI)
320serverOctetAPI :: IO (Server OctetAPI)
251serverOctetAPI = return $ return "blah"
321serverOctetAPI = return $ return "blah"
323+type JsonApi = "jsonComparison" :> Get '[OctetStream] BS.ByteString
325+jsonApi :: Proxy JsonApi
328+jsonServer1 :: IO (Server JsonApi)
329+jsonServer1 = return $ return "{ \"b\": [\"b\"], \"a\": 1 }" -- whitespace, ordering different
331+jsonServer2 :: IO (Server JsonApi)
332+jsonServer2 = return $ return "{\"a\": 1,\"b\":[\"b\"]}"
334+jsonServer3 :: IO (Server JsonApi)
335+jsonServer3 = return $ return "{\"a\": 2, \"b\": [\"b\"]}"
337+jsonServer4 :: IO (Server JsonApi)
338+jsonServer4 = return $ return "{\"c\": 1, \"d\": [\"b\"]}"
253ctx :: Context '[BasicAuthCheck ()]
341ctx :: Context '[BasicAuthCheck ()]
254ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
342ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
255------------------------------------------------------------------------------
343------------------------------------------------------------------------------
257------------------------------------------------------------------------------
345------------------------------------------------------------------------------
259−evalExample :: (Example e, Arg e ~ ()) => e -> IO Result
346+evalExample :: (Example e, Arg e ~ ()) => e -> IO EvalResult
260−evalExample e = evaluateExample e defaultParams ($ ()) progCallback
348+ r <- safeEvaluateExample e defaultParams ($ ()) progCallback
349+ case resultStatus r of
350+ Success -> return $ AllGood
351+ Failure _ reason -> return $ FailedWith $ show reason
352+ Pending {} -> error "should not happen"
262 progCallback _ = return ()
354 progCallback _ = return ()
357+ = AnException SomeException
264args :: Args
363args :: Args
265args = defaultArgs { maxSuccess = noOfTestCases }
364args = defaultArgs { maxSuccess = noOfTestCases }
@@ -270,8 +369,3 @@ noOfTestCases = 20000
271noOfTestCases = 1000
370noOfTestCases = 1000
274−#if !MIN_VERSION_servant(0,8,0)
275−comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPI
276−comprehensiveAPIWithoutRaw = comprehensiveAPI