@@ -1,33 +1,29 @@
1{-# LANGUAGE CPP #-}
1{-# LANGUAGE CPP #-}
2module Servant.QuickCheck.InternalSpec (spec) where
2module Servant.QuickCheck.InternalSpec (spec) where
5import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
4import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
6−import Control.Exception (SomeException)
7−import Control.Monad (replicateM)
8import Control.Monad.IO.Class (liftIO)
5import Control.Monad.IO.Class (liftIO)
9import qualified Data.ByteString as BS
6import qualified Data.ByteString as BS
10import qualified Data.ByteString.Char8 as C
7import qualified Data.ByteString.Char8 as C
11−import Data.Maybe (fromJust)
12−import Network.HTTP.Client (path, queryString)
13import Prelude.Compat
8import Prelude.Compat
14import Servant
9import Servant
15−import Servant.HTML.Blaze (HTML)
16−import qualified Text.Blaze.Html as Blaze
17−import qualified Text.Blaze.Html5 as Blaze5
18import Test.Hspec (Spec, context, describe, it, shouldBe,
10import Test.Hspec (Spec, context, describe, it, shouldBe,
19 shouldContain)
11 shouldContain)
20−import Test.Hspec.Core.Spec (Arg, Example, Result (..), ResultStatus (..),
12+import Test.Hspec.Core.Spec (Arg, Example, Result (..),
21− defaultParams, safeEvaluateExample)
13+ defaultParams, evaluateExample)
22−import Test.QuickCheck.Gen (generate, unGen)
14+import Test.QuickCheck.Gen (unGen)
23import Test.QuickCheck.Random (mkQCGen)
15import Test.QuickCheck.Random (mkQCGen)
16+import Network.HTTP.Client (queryString)
25−
18+#if MIN_VERSION_servant(0,8,0)
26−import Servant.Test.ComprehensiveAPI (comprehensiveAPIWithoutStreamingOrRaw)
19+import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
21+import Servant.API.Internal.Test.ComprehensiveAPI (ComprehensiveAPI,
28import Servant.QuickCheck
25import Servant.QuickCheck
29−import Servant.QuickCheck.Internal (genRequest, runGenRequest,
26+import Servant.QuickCheck.Internal (genRequest, serverDoesntSatisfy)
32spec :: Spec
28spec :: Spec
@@ -37,10 +33,6 @@ spec = do
37 onlyJsonObjectSpec
33 onlyJsonObjectSpec
38 notLongerThanSpec
34 notLongerThanSpec
39 queryParamsSpec
35 queryParamsSpec
43− unbiasedGenerationSpec
45serversEqualSpec :: Spec
37serversEqualSpec :: Spec
46serversEqualSpec = describe "serversEqual" $ do
38serversEqualSpec = describe "serversEqual" $ do
@@ -51,42 +43,15 @@ serversEqualSpec = describe "serversEqual" $ do
51 serversEqual api burl1 burl2 args bodyEquality
43 serversEqual api burl1 burl2 args bodyEquality
53 context "when servers are not equal" $ do
45 context "when servers are not equal" $ do
54 it "provides the failing responses in the error message" $ do
48 it "provides the failing responses in the error message" $ do
55− FailedWith err <- withServantServer api2 server2 $ \burl1 ->
49+ Fail _ err <- withServantServer api2 server2 $ \burl1 ->
56 withServantServer api2 server3 $ \burl2 -> do
50 withServantServer api2 server3 $ \burl2 -> do
57 evalExample $ serversEqual api2 burl1 burl2 args bodyEquality
51 evalExample $ serversEqual api2 burl1 burl2 args bodyEquality
58− show err `shouldContain` "Server equality failed"
59 show err `shouldContain` "Body: 1"
52 show err `shouldContain` "Body: 1"
60 show err `shouldContain` "Body: 2"
53 show err `shouldContain` "Body: 2"
61− show err `shouldContain` "Path: /failplz"
54+ 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"
91serverSatisfiesSpec :: Spec
56serverSatisfiesSpec :: Spec
92serverSatisfiesSpec = describe "serverSatisfies" $ do
57serverSatisfiesSpec = describe "serverSatisfies" $ do
@@ -108,38 +73,33 @@ serverSatisfiesSpec = describe "serverSatisfies" $ do
108 context "when predicates are false" $ do
73 context "when predicates are false" $ do
110 it "fails with informative error messages" $ do
75 it "fails with informative error messages" $ do
111− FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
76+ Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
112− evalExample $ serverSatisfies api burl args (notAllowedContainsAllowHeader <%> mempty)
77+ evalExample $ serverSatisfies api burl args (getsHaveCacheControlHeader <%> mempty)
113− show err `shouldContain` "notAllowedContainsAllowHeader"
78+ err `shouldContain` "getsHaveCacheControlHeader"
114− show err `shouldContain` "Headers"
79+ err `shouldContain` "Headers"
115− show err `shouldContain` "Body"
80+ err `shouldContain` "Body"
118onlyJsonObjectSpec :: Spec
82onlyJsonObjectSpec :: Spec
119onlyJsonObjectSpec = describe "onlyJsonObjects" $ do
83onlyJsonObjectSpec = describe "onlyJsonObjects" $ do
121 it "fails correctly" $ do
85 it "fails correctly" $ do
122− FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
86+ Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
123 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
87 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
124 (onlyJsonObjects <%> mempty)
88 (onlyJsonObjects <%> mempty)
125− show err `shouldContain` "onlyJsonObjects"
89+ err `shouldContain` "onlyJsonObjects"
127 it "accepts non-JSON endpoints" $ do
91 it "accepts non-JSON endpoints" $ do
128 withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl ->
92 withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl ->
129 serverSatisfies octetAPI burl args (onlyJsonObjects <%> mempty)
93 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)
135notLongerThanSpec :: Spec
95notLongerThanSpec :: Spec
136notLongerThanSpec = describe "notLongerThan" $ do
96notLongerThanSpec = describe "notLongerThan" $ do
138 it "fails correctly" $ do
98 it "fails correctly" $ do
139− FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do
99+ Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
140 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
100 evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
141 (notLongerThan 1 <%> mempty)
101 (notLongerThan 1 <%> mempty)
142− show err `shouldContain` "notLongerThan"
102+ err `shouldContain` "notLongerThan"
144 it "succeeds correctly" $ do
104 it "succeeds correctly" $ do
145 withServantServerAndContext api ctx server $ \burl ->
105 withServantServerAndContext api ctx server $ \burl ->
@@ -149,82 +109,19 @@ isComprehensiveSpec :: Spec
149isComprehensiveSpec = describe "HasGenRequest" $ do
109isComprehensiveSpec = describe "HasGenRequest" $ do
151 it "has instances for all 'servant' combinators" $ do
111 it "has instances for all 'servant' combinators" $ do
152− let _g = genRequest comprehensiveAPIWithoutStreamingOrRaw
112+ let _g = genRequest comprehensiveAPIWithoutRaw
153 True `shouldBe` True -- This is a type-level check
113 True `shouldBe` True -- This is a type-level check
156−deepPathSpec = describe "Path components" $ do
158− it "are separated by slashes, without a trailing slash" $ do
160− burl = BaseUrl Http "localhost" 80 ""
161− gen = runGenRequest deepAPI
162− req = (unGen gen rng 0) burl
163− path req `shouldBe` ("/one/two/three")
166queryParamsSpec :: Spec
115queryParamsSpec :: Spec
167queryParamsSpec = describe "QueryParams" $ do
116queryParamsSpec = describe "QueryParams" $ do
169 it "reduce to an HTTP query string correctly" $ do
118 it "reduce to an HTTP query string correctly" $ do
170 let rng = mkQCGen 0
119 let rng = mkQCGen 0
171 burl = BaseUrl Http "localhost" 80 ""
120 burl = BaseUrl Http "localhost" 80 ""
172− gen = runGenRequest paramsAPI
121+ gen = genRequest paramsAPI
173− req = (unGen gen rng 0) burl
174− qs = C.unpack $ queryString req
175− qs `shouldBe` "one=_&two=_"
177−queryFlagsSpec :: Spec
178−queryFlagsSpec = describe "QueryFlags" $ do
180− it "reduce to an HTTP query string correctly" $ do
182− burl = BaseUrl Http "localhost" 80 ""
183− gen = runGenRequest flagsAPI
184 req = (unGen gen rng 0) burl
122 req = (unGen gen rng 0) burl
185 qs = C.unpack $ queryString req
123 qs = C.unpack $ queryString req
186− qs `shouldBe` "one&two"
124+ 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)
207−makeRandomRequest :: Proxy LargeAPI -> BaseUrl -> IO Integer
208−makeRandomRequest large burl = do
209− req <- generate $ runGenRequest large
210− pure $ fst . fromJust . C.readInteger . C.drop 1 . path $ req burl
213−unbiasedGenerationSpec :: Spec
214−unbiasedGenerationSpec = describe "Unbiased Generation of requests" $
216− it "frequency paired with generated endpoint should be more randomly distributed" $ do
217− let burl = BaseUrl Http "localhost" 80 ""
218− let runs = 10000 :: Double
219− someRequests <- replicateM 10000 (makeRandomRequest largeApi burl)
220− let mean = (sum $ map fromIntegral someRequests) / runs
221− let variancer x = let ix = fromIntegral x in (ix - mean) * (ix - mean)
222− let variance = (sum $ map variancer someRequests) / runs - 1
223− -- mean should be around 8.5. If this fails, we likely need more runs (or there's a bug!)
224− mean > 8 `shouldBe` True
225− mean < 9 `shouldBe` True
226− -- Std dev is likely around 4. Variance is probably greater than 20.
227− variance > 19.5 `shouldBe` True
229------------------------------------------------------------------------------
126------------------------------------------------------------------------------
@@ -237,17 +134,11 @@ type API = ReqBody '[JSON] String :> Post '[JSON] String
237api :: Proxy API
134api :: Proxy API
238api = Proxy
135api = Proxy
240−type ParamsAPI = QueryParam "one" () :> QueryParam "two" () :> Get '[JSON] ()
137+type ParamsAPI = QueryParam "one" String :> QueryParam "two" String :> Get '[JSON] ()
242paramsAPI :: Proxy ParamsAPI
139paramsAPI :: Proxy ParamsAPI
243paramsAPI = Proxy
140paramsAPI = Proxy
245−type FlagsAPI = QueryFlag "one" :> QueryFlag "two" :> Get '[JSON] ()
247−flagsAPI :: Proxy FlagsAPI
251server :: IO (Server API)
142server :: IO (Server API)
252server = do
143server = do
253 mvar <- newMVar ""
144 mvar <- newMVar ""
@@ -261,57 +152,12 @@ type API2 = "failplz" :> Get '[JSON] Int
261api2 :: Proxy API2
152api2 :: Proxy API2
262api2 = Proxy
153api2 = Proxy
264−type DeepAPI = "one" :> "two" :> "three":> Get '[JSON] ()
266−deepAPI :: Proxy DeepAPI
270server2 :: IO (Server API2)
155server2 :: IO (Server API2)
271server2 = return $ return 1
156server2 = return $ return 1
273server3 :: IO (Server API2)
158server3 :: IO (Server API2)
274server3 = return $ return 2
159server3 = 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
293−largeApi :: Proxy LargeAPI
297− = "1" :> Get '[JSON] Int
298− :<|> "2" :> Get '[JSON] Int
299− :<|> "3" :> Get '[JSON] Int
300− :<|> "4" :> Get '[JSON] Int
301− :<|> "5" :> Get '[JSON] Int
302− :<|> "6" :> Get '[JSON] Int
303− :<|> "7" :> Get '[JSON] Int
304− :<|> "8" :> Get '[JSON] Int
305− :<|> "9" :> Get '[JSON] Int
306− :<|> "10" :> Get '[JSON] Int
307− :<|> "11" :> Get '[JSON] Int
308− :<|> "12" :> Get '[JSON] Int
309− :<|> "13" :> Get '[JSON] Int
310− :<|> "14" :> Get '[JSON] Int
311− :<|> "15" :> Get '[JSON] Int
312− :<|> "16" :> Get '[JSON] Int
315type OctetAPI = Get '[OctetStream] BS.ByteString
161type OctetAPI = Get '[OctetStream] BS.ByteString
317octetAPI :: Proxy OctetAPI
163octetAPI :: Proxy OctetAPI
@@ -320,46 +166,17 @@ octetAPI = Proxy
320serverOctetAPI :: IO (Server OctetAPI)
166serverOctetAPI :: IO (Server OctetAPI)
321serverOctetAPI = return $ return "blah"
167serverOctetAPI = 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\"]}"
341ctx :: Context '[BasicAuthCheck ()]
169ctx :: Context '[BasicAuthCheck ()]
342ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
170ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
343------------------------------------------------------------------------------
171------------------------------------------------------------------------------
345------------------------------------------------------------------------------
173------------------------------------------------------------------------------
346−evalExample :: (Example e, Arg e ~ ()) => e -> IO EvalResult
175+evalExample :: (Example e, Arg e ~ ()) => e -> IO Result
348− r <- safeEvaluateExample e defaultParams ($ ()) progCallback
176+evalExample e = evaluateExample 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"
354 progCallback _ = return ()
178 progCallback _ = return ()
357− = AnException SomeException
363args :: Args
180args :: Args
364args = defaultArgs { maxSuccess = noOfTestCases }
181args = defaultArgs { maxSuccess = noOfTestCases }
@@ -369,3 +186,8 @@ noOfTestCases = 20000
370noOfTestCases = 1000
187noOfTestCases = 1000
190+#if !MIN_VERSION_servant(0,8,0)
191+comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPI
192+comprehensiveAPIWithoutRaw = comprehensiveAPI