Compare commits

..

No commits in common. "master" and "0.0.2.2" have entirely different histories.

16 changed files with 313 additions and 955 deletions

View File

@ -1,158 +1,26 @@
# This Travis job script has been generated by a script via sudo: false
#
# haskell-ci '--config=cabal.haskell-ci' 'servant-quickcheck.cabal'
#
# To regenerate the script (for example after adjusting tested-with) run
#
# haskell-ci regenerate
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.10.1
#
version: ~> 1.0
language: c language: c
os: linux
dist: xenial matrix:
git: include:
# whether to recursively clone submodules - env: CABALVER=1.22 GHCVER=7.10.1
submodules: false addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.1],sources: [hvr-ghc]}}
branches: - env: CABALVER=1.24 GHCVER=8.0.1
only: addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.1], sources: [hvr-ghc]}}
- master
install:
- (mkdir -p $HOME/.local/bin && cd $HOME/.local/bin && wget https://zalora-public.s3.amazonaws.com/tinc && chmod +x tinc)
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
- ghc --version
- cabal --version
- travis_retry cabal update
- sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
script:
- tinc && cabal configure --enable-tests && cabal build && cabal test
- cabal check
cache: cache:
directories: directories:
- $HOME/.cabal/packages - $HOME/.tinc/cache
- $HOME/.cabal/store
- $HOME/.hlint
before_cache:
- rm -fv $CABALHOME/packages/hackage.haskell.org/build-reports.log
# remove files that are regenerated by 'cabal update'
- rm -fv $CABALHOME/packages/hackage.haskell.org/00-index.*
- rm -fv $CABALHOME/packages/hackage.haskell.org/*.json
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.cache
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar
- rm -fv $CABALHOME/packages/hackage.haskell.org/01-index.tar.idx
- rm -rfv $CABALHOME/packages/head.hackage
jobs:
include:
- compiler: ghc-8.8.3
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.8.3","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.6.5
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.6.5","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.4.4
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.4.4","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.2.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.2.2","cabal-install-3.2"]}}
os: linux
- compiler: ghc-8.0.2
addons: {"apt":{"sources":[{"sourceline":"deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main","key_url":"https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x063dab2bdc0b3f9fcebc378bff3aeacef6f88286"}],"packages":["ghc-8.0.2","cabal-install-3.2"]}}
os: linux
before_install:
- HC=$(echo "/opt/$CC/bin/ghc" | sed 's/-/\//')
- WITHCOMPILER="-w $HC"
- HADDOCK=$(echo "/opt/$CC/bin/haddock" | sed 's/-/\//')
- HCPKG="$HC-pkg"
- unset CC
- CABAL=/opt/ghc/bin/cabal
- CABALHOME=$HOME/.cabal
- export PATH="$CABALHOME/bin:$PATH"
- TOP=$(pwd)
- "HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\\d+)\\.(\\d+)\\.(\\d+)(\\.(\\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')"
- echo $HCNUMVER
- CABAL="$CABAL -vnormal+nowrap"
- set -o pipefail
- TEST=--enable-tests
- BENCH=--enable-benchmarks
- HEADHACKAGE=false
- rm -f $CABALHOME/config
- |
echo "verbose: normal +nowrap +markoutput" >> $CABALHOME/config
echo "remote-build-reporting: anonymous" >> $CABALHOME/config
echo "write-ghc-environment-files: always" >> $CABALHOME/config
echo "remote-repo-cache: $CABALHOME/packages" >> $CABALHOME/config
echo "logs-dir: $CABALHOME/logs" >> $CABALHOME/config
echo "world-file: $CABALHOME/world" >> $CABALHOME/config
echo "extra-prog-path: $CABALHOME/bin" >> $CABALHOME/config
echo "symlink-bindir: $CABALHOME/bin" >> $CABALHOME/config
echo "installdir: $CABALHOME/bin" >> $CABALHOME/config
echo "build-summary: $CABALHOME/logs/build.log" >> $CABALHOME/config
echo "store-dir: $CABALHOME/store" >> $CABALHOME/config
echo "install-dirs user" >> $CABALHOME/config
echo " prefix: $CABALHOME" >> $CABALHOME/config
echo "repository hackage.haskell.org" >> $CABALHOME/config
echo " url: http://hackage.haskell.org/" >> $CABALHOME/config
install:
- ${CABAL} --version
- echo "$(${HC} --version) [$(${HC} --print-project-git-commit-id 2> /dev/null || echo '?')]"
- |
echo "program-default-options" >> $CABALHOME/config
echo " ghc-options: $GHCJOBS +RTS -M6G -RTS" >> $CABALHOME/config
- cat $CABALHOME/config
- rm -fv cabal.project cabal.project.local cabal.project.freeze
- travis_retry ${CABAL} v2-update -v
# Generate cabal.project
- rm -rf cabal.project cabal.project.local cabal.project.freeze
- touch cabal.project
- |
echo "packages: ." >> cabal.project
- if [ $HCNUMVER -ge 80200 ] ; then echo 'package servant-quickcheck' >> cabal.project ; fi
- "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi"
- |
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant-quickcheck)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
- if [ -f "./configure.ac" ]; then (cd "." && autoreconf -i); fi
- ${CABAL} v2-freeze $WITHCOMPILER ${TEST} ${BENCH}
- "cat cabal.project.freeze | sed -E 's/^(constraints: *| *)//' | sed 's/any.//'"
- rm cabal.project.freeze
- travis_wait 40 ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} --dep -j2 all
- travis_wait 40 ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --dep -j2 all
script:
- DISTDIR=$(mktemp -d /tmp/dist-test.XXXX)
# Packaging...
- ${CABAL} v2-sdist all
# Unpacking...
- mv dist-newstyle/sdist/*.tar.gz ${DISTDIR}/
- cd ${DISTDIR} || false
- find . -maxdepth 1 -type f -name '*.tar.gz' -exec tar -xvf '{}' \;
- find . -maxdepth 1 -type f -name '*.tar.gz' -exec rm '{}' \;
- PKGDIR_servant_quickcheck="$(find . -maxdepth 1 -type d -regex '.*/servant-quickcheck-[0-9.]*')"
# Generate cabal.project
- rm -rf cabal.project cabal.project.local cabal.project.freeze
- touch cabal.project
- |
echo "packages: ${PKGDIR_servant_quickcheck}" >> cabal.project
- if [ $HCNUMVER -ge 80200 ] ; then echo 'package servant-quickcheck' >> cabal.project ; fi
- "if [ $HCNUMVER -ge 80200 ] ; then echo ' ghc-options: -Werror=missing-methods' >> cabal.project ; fi"
- |
- "for pkg in $($HCPKG list --simple-output); do echo $pkg | sed 's/-[^-]*$//' | (grep -vE -- '^(servant-quickcheck)$' || true) | sed 's/^/constraints: /' | sed 's/$/ installed/' >> cabal.project.local; done"
- cat cabal.project || true
- cat cabal.project.local || true
# Building...
# this builds all libraries and executables (without tests/benchmarks)
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
# Building with tests and benchmarks...
# build & run tests, build benchmarks
- ${CABAL} v2-build $WITHCOMPILER ${TEST} ${BENCH} all
# Testing...
- ${CABAL} v2-test $WITHCOMPILER ${TEST} ${BENCH} all
# cabal check...
- (cd ${PKGDIR_servant_quickcheck} && ${CABAL} -vnormal check)
# haddock...
- ${CABAL} v2-haddock $WITHCOMPILER --with-haddock $HADDOCK ${TEST} ${BENCH} all
# Building without installed constraints for packages in global-db...
- rm -f cabal.project.local
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks all
# Constraint sets
- rm -rf cabal.project.local
# Constraint set base-compat-0.10
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='base-compat ==0.10.*' all
# Constraint set base-compat-0.11
- ${CABAL} v2-build $WITHCOMPILER --disable-tests --disable-benchmarks --constraint='base-compat ==0.11.*' all
# REGENDATA ("0.10.1",["--config=cabal.haskell-ci","servant-quickcheck.cabal"])
# EOF

View File

@ -1,143 +1,6 @@
upcoming:
releases: releases:
- version: "0.0.9.0"
changes:
- description: "Support servant-0.15 (#65)"
authors: fizruk
date: 2020-06-25
- description: "Relax constraints for GHC 8.10 (#70)"
authors: felixmulder
date: 2020-06-20
- version: "0.0.8.0"
changes:
- description: Support for servant-0.17
authors: phadej
date: 2019-01-23
- version: "0.0.7.3"
changes:
- description: Support for servant-0.14
issue: 53
authors: phadej
date: 2018-06-12
- description: Fix a failure from OnlyJsonObjects when there is no content-type.
issue: 55
authors: Phenitei
date: 2018-08-27
- description: A bug fix where notAllowedContainsAllowHeader would print the initial request alongside the failure instead of the request causing the failure when it failed.
issue: 57
authors: Phenitei
date: 2018-08-29
- description: QuickCheck 2.12 compatibility
issue: 58
authors: parsonsmatt
date: 2018-10-12
- description: GHC 8.6 compatibility
issue: 59
authors: phadej
date: 2018-10-15
- version: "0.0.7.2"
changes:
- description: Allow client to pass an HTTP Manager in to functions
issue: 47
authors: parsonsmatt
date: 2018-05-10
- description: Fix "should not happen" error when exceptions are thrown
issue: 48
authors: parsonsmatt
date: 2018-05-10
- version: "0.0.7.0"
changes:
- description: Support for base-compat-0.10
issue: none
authors: phadej
date: 2018-04-12
- version: "0.0.7.0"
changes:
- description: Support for GHC-8.4.1
issue: none
authors: phadej
date: 2018-03-23
- description: Requires hspec-2.5
issue: none
authors: phadej
date: 2018-03-23
- version: "0.0.6.0"
changes:
- description: Support for servant-0.12
issue: none
authors: phadej
date: 2018-02-09
- version: "0.0.5.0"
changes:
- description: Export forgotten predicates
issue: none
pr: 40
authors: Phenitei
date: 2017-12-14
- version: "0.0.4"
changes:
- description: Support for Servant 0.12
issue: none
authors: phadej
date: 2017-11-07
- description: Support for Servant 0.11
issue: none
pr: 32
authors: adinapoli-iohk
date: 2017-10-18
notes: Includes 0-weighted instance for EmptyAPI
- version: "0.0.3.0"
changes:
- description: Add jsonEquality
issue: 2
pr: 30
authors: erewok
date: 2017-10-15
- description: Support hspec >= 2.4
issue: 27
pr: 29
authors: erewok, jkarni
date: 2017-10-15
- version: "0.0.2.4"
changes:
- description: Don't append slashes to paths
issue: 22
authors: declension
date: 2017-03-11
- version: "0.0.2.3"
changes:
- description: Fix QueryParam and QueryFlag requests
issue: 23
authors: declension
date: 2017-03-10
- version: "0.0.2.2" - version: "0.0.2.2"
changes: changes:

View File

@ -1,7 +0,0 @@
branches: master
constraint-set base-compat-0.10
constraints: base-compat ==0.10.*
constraint-set base-compat-0.11
constraints: base-compat ==0.11.*

View File

@ -1,4 +0,0 @@
packages: .
tests: true
allow-newer: servant-blaze:servant

View File

@ -1,31 +0,0 @@
{-# LANGUAGE OverloadedStrings, DataKinds #-}
module Main (main) where
import Servant
import Servant.QuickCheck
import Test.Hspec
import Data.Text (Text)
import System.Environment (getArgs)
main :: IO ()
main = do
args <- getArgs
case args of
[] -> putStrLn "Not running without arguments. Try --help or --fail-fast."
_ -> hspec spec
-- Change to String to reproduce
-- https://github.com/haskell-servant/servant-quickcheck/issues/41
type API = Get '[PlainText] Text
api :: Proxy API
api = Proxy
server :: Server API
server = return "Sigurð Fáfnirslayer"
spec :: Spec
spec = describe "example server" $
it "mangles UTF-8 in error messages" $
withServantServer api (return server) $ \burl ->
serverSatisfies api burl defaultArgs (getsHaveCacheControlHeader <%> mempty)

View File

@ -1,5 +1,5 @@
name: servant-quickcheck name: servant-quickcheck
version: 0.0.9.1 version: 0.0.2.2
synopsis: QuickCheck entire APIs synopsis: QuickCheck entire APIs
description: description:
This packages provides QuickCheck properties that are tested across an entire This packages provides QuickCheck properties that are tested across an entire
@ -12,8 +12,8 @@ maintainer: jkarni@gmail.com
category: Web category: Web
build-type: Simple build-type: Simple
cabal-version: >=1.10 cabal-version: >=1.10
extra-source-files: CHANGELOG.yaml extra-source-files:
tested-with: GHC ==8.0.2 || ==8.2.2 || ==8.4.4 || ==8.6.5 || == 8.8.3 CHANGELOG.yaml
source-repository head source-repository head
type: git type: git
@ -24,63 +24,55 @@ flag long-tests
default: False default: False
library library
exposed-modules: exposed-modules: Servant.QuickCheck
Servant.QuickCheck , Servant.QuickCheck.Internal
Servant.QuickCheck.Internal , Servant.QuickCheck.Internal.Predicates
Servant.QuickCheck.Internal.Equality , Servant.QuickCheck.Internal.HasGenRequest
Servant.QuickCheck.Internal.ErrorTypes , Servant.QuickCheck.Internal.QuickCheck
Servant.QuickCheck.Internal.HasGenRequest , Servant.QuickCheck.Internal.Equality
Servant.QuickCheck.Internal.Predicates , Servant.QuickCheck.Internal.ErrorTypes
Servant.QuickCheck.Internal.QuickCheck build-depends: base >=4.8 && <4.10
, base-compat == 0.9.*
build-depends: , aeson > 0.8 && < 2
aeson >=0.8 && <2 , bytestring == 0.10.*
, base >=4.9 && <4.15 , case-insensitive == 1.2.*
, base-compat-batteries >=0.10.1 && <0.12 , clock >= 0.7 && < 0.8
, bytestring >=0.10 && <0.11 , data-default-class >= 0.0 && < 0.2
, case-insensitive >=1.2 && <1.3 , hspec == 2.2.*
, clock >=0.7 && <0.9 , http-client >= 0.4.30 && < 0.6
, data-default-class >=0.0 && <0.2 , http-media == 0.6.*
, hspec >=2.5.6 && <2.8 , http-types > 0.8 && < 0.10
, http-client >=0.4.30 && <0.8 , mtl > 2.1 && < 2.3
, http-media >=0.6 && <0.9 , pretty == 1.1.*
, http-types >=0.8 && <0.13 , process >= 1.2 && < 1.5
, mtl >=2.1 && <2.3 , QuickCheck > 2.7 && < 2.10
, pretty >=1.1 && <1.2 , servant > 0.6 && < 0.10
, process >=1.2 && <1.7 , servant-client > 0.6 && < 0.10
, QuickCheck >=2.7 && <2.15 , servant-server > 0.6 && < 0.10
, servant >=0.17 && <0.19 , split == 0.2.*
, servant-client >=0.17 && <0.19 , string-conversions > 0.3 && < 0.5
, servant-server >=0.17 && <0.19 , temporary == 1.2.*
, split >=0.2 && <0.3 , text == 1.*
, string-conversions >=0.3 && <0.5 , time >= 1.5 && < 1.7
, temporary >=1.2 && <1.4 , warp >= 3.2.4 && < 3.3
, text >=1 && <2
, time >=1.5 && <1.11
, warp >=3.2.4 && <3.4
if !impl(ghc >=8.0)
build-depends: semigroups >=0.18.3 && <0.20
hs-source-dirs: src hs-source-dirs: src
default-extensions: default-extensions: TypeOperators
NoImplicitPrelude , FlexibleInstances
ConstraintKinds , FlexibleContexts
DataKinds , DataKinds
DeriveDataTypeable , GADTs
DeriveFunctor , MultiParamTypeClasses
DeriveGeneric , DeriveFunctor
FlexibleContexts , KindSignatures
FlexibleInstances , RankNTypes
FunctionalDependencies , ConstraintKinds
GADTs , DeriveGeneric
KindSignatures , ScopedTypeVariables
MultiParamTypeClasses , OverloadedStrings
OverloadedStrings , FunctionalDependencies
RankNTypes , NoImplicitPrelude
ScopedTypeVariables , DeriveDataTypeable
TypeOperators
default-language: Haskell2010 default-language: Haskell2010
test-suite spec test-suite spec
@ -90,49 +82,27 @@ test-suite spec
hs-source-dirs: test hs-source-dirs: test
main-is: Spec.hs main-is: Spec.hs
other-modules: Servant.QuickCheck.InternalSpec other-modules: Servant.QuickCheck.InternalSpec
build-tool-depends: hspec-discover:hspec-discover -any build-depends: base == 4.*
build-depends: , base-compat
aeson , servant-quickcheck
, base
, base-compat-batteries
, blaze-html
, bytestring , bytestring
, hspec , hspec
, hspec-core >=2.5.5 && <2.8 , hspec-core
, http-client , http-client
, warp
, servant-server
, servant-client
, servant
, transformers
, QuickCheck , QuickCheck
, quickcheck-io , quickcheck-io
, servant default-extensions: TypeOperators
, servant-blaze , FlexibleInstances
, servant-client , FlexibleContexts
, servant-quickcheck , GADTs
, servant-server , DataKinds
, transformers , NoImplicitPrelude
, warp , OverloadedStrings
, ScopedTypeVariables
default-extensions:
NoImplicitPrelude
DataKinds
FlexibleContexts
FlexibleInstances
GADTs
OverloadedStrings
ScopedTypeVariables
TypeOperators
if flag(long-tests) if flag(long-tests)
cpp-options: -DLONG_TESTS cpp-options: -DLONG_TESTS
test-suite example
type: exitcode-stdio-1.0
main-is: Main.hs
hs-source-dirs: example
ghc-options: -Wall
build-depends:
base
, hspec
, servant-quickcheck
, servant-server
, text
default-language: Haskell2010

View File

@ -29,16 +29,11 @@ module Servant.QuickCheck
, not500 , not500
, notLongerThan , notLongerThan
, onlyJsonObjects , onlyJsonObjects
, honoursAcceptHeader
, notAllowedContainsAllowHeader , notAllowedContainsAllowHeader
, unauthorizedContainsWWWAuthenticate , unauthorizedContainsWWWAuthenticate
, getsHaveLastModifiedHeader
, getsHaveCacheControlHeader , getsHaveCacheControlHeader
, headsHaveCacheControlHeader , headsHaveCacheControlHeader
, createContainsValidLocation , createContainsValidLocation
-- * Html Predicates
, htmlIncludesDoctype
-- *** Predicate utilities and types -- *** Predicate utilities and types
, (<%>) , (<%>)
, Predicates , Predicates
@ -54,7 +49,6 @@ module Servant.QuickCheck
-- represents other means of checking equality -- represents other means of checking equality
-- *** Useful @ResponseEquality@s -- *** Useful @ResponseEquality@s
, bodyEquality , bodyEquality
, jsonEquality
, allEquality , allEquality
-- ** Response equality type -- ** Response equality type
, ResponseEquality(..) , ResponseEquality(..)

View File

@ -1,23 +1,16 @@
module Servant.QuickCheck.Internal.Equality where module Servant.QuickCheck.Internal.Equality where
import Data.Aeson (Value, decode, decodeStrict)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as LB
import Data.Function (on) import Data.Function (on)
import Network.HTTP.Client (Response, responseBody) import Network.HTTP.Client (Response, responseBody)
import Data.Semigroup (Semigroup (..))
import Prelude.Compat import Prelude.Compat
newtype ResponseEquality b newtype ResponseEquality b
= ResponseEquality { getResponseEquality :: Response b -> Response b -> Bool } = ResponseEquality { getResponseEquality :: Response b -> Response b -> Bool }
instance Semigroup (ResponseEquality b) where
ResponseEquality a <> ResponseEquality b = ResponseEquality $ \x y ->
a x y && b x y
instance Monoid (ResponseEquality b) where instance Monoid (ResponseEquality b) where
mempty = ResponseEquality $ \_ _ -> True mempty = ResponseEquality $ \_ _ -> True
mappend = (<>) ResponseEquality a `mappend` ResponseEquality b = ResponseEquality $ \x y ->
a x y && b x y
-- | Use `Eq` instance for `Response` -- | Use `Eq` instance for `Response`
-- --
@ -30,29 +23,3 @@ allEquality = ResponseEquality (==)
-- /Since 0.0.0.0/ -- /Since 0.0.0.0/
bodyEquality :: Eq b => ResponseEquality b bodyEquality :: Eq b => ResponseEquality b
bodyEquality = ResponseEquality ((==) `on` responseBody) bodyEquality = ResponseEquality ((==) `on` responseBody)
-- | Equality as 'Value'. This means that if two bodies are equal as JSON
-- (e.g., insignificant whitespace difference) they are considered equal.
--
-- /Since 0.0.3.0/
jsonEquality :: (JsonEq b) => ResponseEquality b
jsonEquality = ResponseEquality (jsonEq `on` responseBody)
class JsonEq a where
decode' :: a -> Maybe Value
jsonEq :: a -> a -> Bool
jsonEq first second = compareDecodedResponses (decode' first) (decode' second)
instance JsonEq LB.ByteString where
decode' = decode
instance JsonEq ByteString where
decode' = decodeStrict
compareDecodedResponses :: Maybe Value -> Maybe Value -> Bool
compareDecodedResponses resp1 resp2 =
case resp1 of
Nothing -> False -- if decoding fails we assume failure
(Just r1) -> case resp2 of
Nothing -> False -- another decode failure
(Just r2) -> r1 == r2

View File

@ -1,4 +1,3 @@
{-# LANGUAGE CPP #-}
module Servant.QuickCheck.Internal.ErrorTypes where module Servant.QuickCheck.Internal.ErrorTypes where
import Control.Exception (Exception (..)) import Control.Exception (Exception (..))
@ -9,10 +8,9 @@ import Data.Typeable (Typeable)
import GHC.Generics (Generic) import GHC.Generics (Generic)
import qualified Network.HTTP.Client as C import qualified Network.HTTP.Client as C
import Network.HTTP.Types (Header, statusCode) import Network.HTTP.Types (Header, statusCode)
import Prelude.Compat
import Text.PrettyPrint import Text.PrettyPrint
import Prelude.Compat hiding ((<>))
data PredicateFailure data PredicateFailure
= PredicateFailure T.Text (Maybe C.Request) (C.Response LBS.ByteString) = PredicateFailure T.Text (Maybe C.Request) (C.Response LBS.ByteString)
deriving (Typeable, Generic) deriving (Typeable, Generic)

View File

@ -2,6 +2,7 @@
{-# LANGUAGE PolyKinds #-} {-# LANGUAGE PolyKinds #-}
module Servant.QuickCheck.Internal.HasGenRequest where module Servant.QuickCheck.Internal.HasGenRequest where
import Data.Monoid ((<>))
import Data.String (fromString) import Data.String (fromString)
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import GHC.TypeLits (KnownSymbol, Nat, symbolVal) import GHC.TypeLits (KnownSymbol, Nat, symbolVal)
@ -13,136 +14,101 @@ import Prelude.Compat
import Servant import Servant
import Servant.API.ContentTypes (AllMimeRender (..)) import Servant.API.ContentTypes (AllMimeRender (..))
import Servant.Client (BaseUrl (..), Scheme (..)) import Servant.Client (BaseUrl (..), Scheme (..))
import Test.QuickCheck (Arbitrary (..), Gen, elements, frequency) import Test.QuickCheck (Arbitrary (..), Gen, elements, oneof)
#if MIN_VERSION_servant(0,8,0)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Internal as BS (c2w) #endif
-- -----------------------------------------------------------------------------
-- runGenRequest
-- | This function returns a QuickCheck `Gen a` when passed a servant API value,
-- typically a `Proxy API`. The generator returned is a function
-- that accepts a `BaseUrl` and returns a `Request`, which can then be used
-- to issue network requests. This `Gen` type makes it easier to compare distinct
-- APIs across different `BaseUrl`s.
runGenRequest :: HasGenRequest a => Proxy a -> Gen (BaseUrl -> Request)
runGenRequest = snd . genRequest
-- -----------------------------------------------------------------------------
-- HasGenRequest
-- | This is the core Servant-Quickcheck generator, which, when given a `Proxy API`
-- will return a pair of `Int` and `Gen a`, where `a` is a function from
-- `BaseUrl` to a `Network.Http.Client.Request`. The `Int` is a weight for the
-- QuickCheck `frequency` function which ensures a random distribution across
-- all endpoints in an API.
class HasGenRequest a where class HasGenRequest a where
genRequest :: Proxy a -> (Int, Gen (BaseUrl -> Request)) genRequest :: Proxy a -> Gen (BaseUrl -> Request)
instance (HasGenRequest a, HasGenRequest b) => HasGenRequest (a :<|> b) where instance (HasGenRequest a, HasGenRequest b) => HasGenRequest (a :<|> b) where
genRequest _ genRequest _
= (lf + rf, frequency [l, r]) = oneof [ genRequest (Proxy :: Proxy a)
where , genRequest (Proxy :: Proxy b)
l@(lf, _) = genRequest (Proxy :: Proxy a) ]
r@(rf, _) = genRequest (Proxy :: Proxy b)
instance (KnownSymbol path, HasGenRequest b) => HasGenRequest (path :> b) where instance (KnownSymbol path, HasGenRequest b) => HasGenRequest (path :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old old' <- old
return $ \burl -> let r = old' burl return $ \burl -> let r = old' burl in r { path = new <> path r }
oldPath = path r
oldPath' = BS.dropWhile (== BS.c2w '/') oldPath
paths = filter (not . BS.null) [new, oldPath']
in r { path = "/" <> BS.intercalate "/" paths })
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
new = cs $ symbolVal (Proxy :: Proxy path) new = cs $ symbolVal (Proxy :: Proxy path)
instance HasGenRequest EmptyAPI where
genRequest _ = (0, error "EmptyAPIs cannot be queried.")
instance HasGenRequest api => HasGenRequest (Summary d :> api) where
genRequest _ = genRequest (Proxy :: Proxy api)
instance HasGenRequest api => HasGenRequest (Description d :> api) where
genRequest _ = genRequest (Proxy :: Proxy api)
instance (Arbitrary c, HasGenRequest b, ToHttpApiData c ) instance (Arbitrary c, HasGenRequest b, ToHttpApiData c )
=> HasGenRequest (Capture' mods x c :> b) where => HasGenRequest (Capture x c :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old old' <- old
new' <- toUrlPiece <$> new new' <- toUrlPiece <$> new
return $ \burl -> let r = old' burl in r { path = cs new' <> path r }) return $ \burl -> let r = old' burl in r { path = cs new' <> path r }
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
new = arbitrary :: Gen c new = arbitrary :: Gen c
#if MIN_VERSION_servant(0,8,0)
instance (Arbitrary c, HasGenRequest b, ToHttpApiData c ) instance (Arbitrary c, HasGenRequest b, ToHttpApiData c )
=> HasGenRequest (CaptureAll x c :> b) where => HasGenRequest (CaptureAll x c :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old old' <- old
new' <- fmap (cs . toUrlPiece) <$> new new' <- fmap (cs . toUrlPiece) <$> new
let new'' = BS.intercalate "/" new' let new'' = BS.intercalate "/" new'
return $ \burl -> let r = old' burl in r { path = new'' <> path r }) return $ \burl -> let r = old' burl in r { path = new'' <> path r }
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
new = arbitrary :: Gen [c] new = arbitrary :: Gen [c]
#endif
instance (Arbitrary c, KnownSymbol h, HasGenRequest b, ToHttpApiData c) instance (Arbitrary c, KnownSymbol h, HasGenRequest b, ToHttpApiData c)
=> HasGenRequest (Header' mods h c :> b) where => HasGenRequest (Header h c :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old old' <- old
new' <- toUrlPiece <$> new -- TODO: generate lenient or/and optional new' <- toUrlPiece <$> new
return $ \burl -> let r = old' burl in r { return $ \burl -> let r = old' burl in r {
requestHeaders = (hdr, cs new') : requestHeaders r }) requestHeaders = (hdr, cs new') : requestHeaders r }
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
hdr = fromString $ symbolVal (Proxy :: Proxy h) hdr = fromString $ symbolVal (Proxy :: Proxy h)
new = arbitrary :: Gen c new = arbitrary :: Gen c
instance (AllMimeRender x c, Arbitrary c, HasGenRequest b) instance (AllMimeRender x c, Arbitrary c, HasGenRequest b)
=> HasGenRequest (ReqBody' mods x c :> b) where => HasGenRequest (ReqBody x c :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old -- TODO: generate lenient old' <- old
new' <- new new' <- new
(ct, bd) <- elements $ allMimeRender (Proxy :: Proxy x) new' (ct, bd) <- elements $ allMimeRender (Proxy :: Proxy x) new'
return $ \burl -> let r = old' burl in r { return $ \burl -> let r = old' burl in r {
requestBody = RequestBodyLBS bd requestBody = RequestBodyLBS bd
, requestHeaders = ("Content-Type", renderHeader ct) : requestHeaders r , requestHeaders = ("Content-Type", renderHeader ct) : requestHeaders r
}) }
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
new = arbitrary :: Gen c new = arbitrary :: Gen c
instance (KnownSymbol x, Arbitrary c, ToHttpApiData c, HasGenRequest b) instance (KnownSymbol x, Arbitrary c, ToHttpApiData c, HasGenRequest b)
=> HasGenRequest (QueryParam' mods x c :> b) where => HasGenRequest (QueryParam x c :> b) where
genRequest _ = (oldf, do genRequest _ = do
new' <- new -- TODO: generate lenient or/and optional new' <- new
old' <- old old' <- old
return $ \burl -> let r = old' burl return $ \burl -> let r = old' burl in r {
newExpr = param <> "=" <> cs (toQueryParam new') queryString = queryString r
qs = queryString r in r { <> param <> "=" <> cs (toQueryParam new') }
queryString = if BS.null qs then newExpr else newExpr <> "&" <> qs })
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
param = cs $ symbolVal (Proxy :: Proxy x) param = cs $ symbolVal (Proxy :: Proxy x)
new = arbitrary :: Gen c new = arbitrary :: Gen c
instance (KnownSymbol x, Arbitrary c, ToHttpApiData c, HasGenRequest b) instance (KnownSymbol x, Arbitrary c, ToHttpApiData c, HasGenRequest b)
=> HasGenRequest (QueryParams x c :> b) where => HasGenRequest (QueryParams x c :> b) where
genRequest _ = (oldf, do genRequest _ = do
new' <- new new' <- new
old' <- old old' <- old
return $ \burl -> let r = old' burl in r { return $ \burl -> let r = old' burl in r {
queryString = queryString r queryString = queryString r
<> if length new' > 0 then fold (toParam <$> new') else ""}) <> if length new' > 0 then fold (toParam <$> new') else ""}
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
param = cs $ symbolVal (Proxy :: Proxy x) param = cs $ symbolVal (Proxy :: Proxy x)
new = arbitrary :: Gen [c] new = arbitrary :: Gen [c]
toParam c = param <> "[]=" <> cs (toQueryParam c) toParam c = param <> "[]=" <> cs (toQueryParam c)
@ -150,32 +116,22 @@ instance (KnownSymbol x, Arbitrary c, ToHttpApiData c, HasGenRequest b)
instance (KnownSymbol x, HasGenRequest b) instance (KnownSymbol x, HasGenRequest b)
=> HasGenRequest (QueryFlag x :> b) where => HasGenRequest (QueryFlag x :> b) where
genRequest _ = (oldf, do genRequest _ = do
old' <- old old' <- old
return $ \burl -> let r = old' burl return $ \burl -> let r = old' burl in r {
qs = queryString r in r { queryString = queryString r <> param <> "=" }
queryString = if BS.null qs then param else param <> "&" <> qs })
where where
(oldf, old) = genRequest (Proxy :: Proxy b) old = genRequest (Proxy :: Proxy b)
param = cs $ symbolVal (Proxy :: Proxy x) param = cs $ symbolVal (Proxy :: Proxy x)
instance (ReflectMethod method) instance (ReflectMethod method)
=> HasGenRequest (Verb (method :: k) (status :: Nat) (cts :: [*]) a) where => HasGenRequest (Verb (method :: k) (status :: Nat) (cts :: [*]) a) where
genRequest _ = (1, return $ \burl -> defaultRequest genRequest _ = return $ \burl -> defaultRequest
{ host = cs $ baseUrlHost burl { host = cs $ baseUrlHost burl
, port = baseUrlPort burl , port = baseUrlPort burl
, secure = baseUrlScheme burl == Https , secure = baseUrlScheme burl == Https
, method = reflectMethod (Proxy :: Proxy method) , method = reflectMethod (Proxy :: Proxy method)
}) }
instance (ReflectMethod method)
=> HasGenRequest (NoContentVerb (method :: k)) where
genRequest _ = (1, return $ \burl -> defaultRequest
{ host = cs $ baseUrlHost burl
, port = baseUrlPort burl
, secure = baseUrlScheme burl == Https
, method = reflectMethod (Proxy :: Proxy method)
})
instance (HasGenRequest a) => HasGenRequest (RemoteHost :> a) where instance (HasGenRequest a) => HasGenRequest (RemoteHost :> a) where
genRequest _ = genRequest (Proxy :: Proxy a) genRequest _ = genRequest (Proxy :: Proxy a)

View File

@ -7,12 +7,11 @@ import Data.Bifunctor (first)
import qualified Data.ByteString as SBS import qualified Data.ByteString as SBS
import qualified Data.ByteString.Char8 as SBSC import qualified Data.ByteString.Char8 as SBSC
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import Data.CaseInsensitive (foldCase, foldedCase, mk) import Data.CaseInsensitive (mk, foldedCase)
import Data.Either (isRight) import Data.Either (isRight)
import Data.List.Split (wordsBy) import Data.List.Split (wordsBy)
import Data.Maybe (fromMaybe, isJust) import Data.Maybe (fromMaybe, isJust)
import Data.Semigroup (Semigroup (..)) import Data.Monoid ((<>))
import qualified Data.Text as T
import Data.Time (UTCTime, defaultTimeLocale, parseTimeM, import Data.Time (UTCTime, defaultTimeLocale, parseTimeM,
rfc822DateFormat) rfc822DateFormat)
import GHC.Generics (Generic) import GHC.Generics (Generic)
@ -43,7 +42,7 @@ import Servant.QuickCheck.Internal.ErrorTypes
-- /Since 0.0.0.0/ -- /Since 0.0.0.0/
not500 :: ResponsePredicate not500 :: ResponsePredicate
not500 = ResponsePredicate $ \resp -> not500 = ResponsePredicate $ \resp ->
when (responseStatus resp == status500) $ throw $ PredicateFailure "not500" Nothing resp when (responseStatus resp == status500) $ fail "not500"
-- | [__Optional__] -- | [__Optional__]
-- --
@ -84,13 +83,15 @@ notLongerThan maxAllowed
-- /Since 0.0.0.0/ -- /Since 0.0.0.0/
onlyJsonObjects :: ResponsePredicate onlyJsonObjects :: ResponsePredicate
onlyJsonObjects onlyJsonObjects
= ResponsePredicate (\resp -> do = ResponsePredicate (\resp -> case go resp of
case lookup "content-type" (first foldedCase <$> responseHeaders resp) of
Nothing -> return ()
Just ctype -> when ("application/json" `SBS.isPrefixOf` ctype) $ do
case (decode (responseBody resp) :: Maybe Object) of
Nothing -> throw $ PredicateFailure "onlyJsonObjects" Nothing resp Nothing -> throw $ PredicateFailure "onlyJsonObjects" Nothing resp
Just _ -> return ()) Just () -> return ())
where
go r = do
ctyp <- lookup "content-type" (first foldedCase <$> responseHeaders r)
when ("application/json" `SBS.isPrefixOf` ctyp) $ do
(_ :: Object) <- decode (responseBody r)
return ()
-- | __Optional__ -- | __Optional__
-- --
@ -118,12 +119,12 @@ createContainsValidLocation
resp <- httpLbs req mgr resp <- httpLbs req mgr
if responseStatus resp == status201 if responseStatus resp == status201
then case lookup "Location" $ responseHeaders resp of then case lookup "Location" $ responseHeaders resp of
Nothing -> throw $ PredicateFailure n (Just req) resp Nothing -> fail n
Just l -> case parseRequest $ SBSC.unpack l of Just l -> case parseRequest $ SBSC.unpack l of
Nothing -> throw $ PredicateFailure n (Just req) resp Nothing -> fail n
Just x -> do Just x -> do
resp2 <- httpLbs x mgr resp2 <- httpLbs x mgr
status2XX (Just req) resp2 n status2XX resp2 n
return [resp, resp2] return [resp, resp2]
else return [resp] else return [resp]
@ -187,15 +188,14 @@ getsHaveLastModifiedHeader
notAllowedContainsAllowHeader :: RequestPredicate notAllowedContainsAllowHeader :: RequestPredicate
notAllowedContainsAllowHeader notAllowedContainsAllowHeader
= RequestPredicate $ \req mgr -> do = RequestPredicate $ \req mgr -> do
let reqs = [ req { method = renderStdMethod m } | m <- [minBound .. maxBound] resp <- mapM (flip httpLbs mgr) $ [ req { method = renderStdMethod m }
| m <- [minBound .. maxBound ]
, renderStdMethod m /= method req ] , renderStdMethod m /= method req ]
resp <- mapM (flip httpLbs mgr) reqs case filter pred' resp of
(x:_) -> throw $ PredicateFailure "notAllowedContainsAllowHeader" (Just req) x
case filter pred' (zip reqs resp) of
(x:_) -> throw $ PredicateFailure "notAllowedContainsAllowHeader" (Just $ fst x) (snd x)
[] -> return resp [] -> return resp
where where
pred' (_, resp) = responseStatus resp == status405 && not (hasValidHeader "Allow" go resp) pred' resp = responseStatus resp == status405 && not (hasValidHeader "Allow" go resp)
where where
go x = all (\y -> isRight $ parseMethod $ SBSC.pack y) go x = all (\y -> isRight $ parseMethod $ SBSC.pack y)
$ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x) $ wordsBy (`elem` (", " :: [Char])) (SBSC.unpack x)
@ -225,7 +225,7 @@ honoursAcceptHeader
sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req) sacc = fromMaybe "*/*" $ lookup "Accept" (requestHeaders req)
if status100 < scode && scode < status300 if status100 < scode && scode < status300
then if isJust $ sctype >>= \x -> matchAccept [x] sacc then if isJust $ sctype >>= \x -> matchAccept [x] sacc
then throw $ PredicateFailure "honoursAcceptHeader" (Just req) resp then fail "honoursAcceptHeader"
else return [resp] else return [resp]
else return [resp] else return [resp]
@ -336,29 +336,7 @@ unauthorizedContainsWWWAuthenticate
= ResponsePredicate $ \resp -> = ResponsePredicate $ \resp ->
if responseStatus resp == status401 if responseStatus resp == status401
then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $ then unless (hasValidHeader "WWW-Authenticate" (const True) resp) $
throw $ PredicateFailure "unauthorizedContainsWWWAuthenticate" Nothing resp fail "unauthorizedContainsWWWAuthenticate"
else return ()
-- | [__RFC Compliance__]
--
-- [An HTML] document will start with exactly this string: <!DOCTYPE html>
--
-- This function checks that HTML documents (those with `Content-Type: text/html...`)
-- include a DOCTYPE declaration at the top. We do not enforce capital case for the string `DOCTYPE`.
--
-- __References__:
--
-- * HTML5 Doctype: <https://tools.ietf.org/html/rfc7992#section-6.1 RFC 7992 Section 6.1>
-- /Since 0.3.0.0/
htmlIncludesDoctype :: ResponsePredicate
htmlIncludesDoctype
= ResponsePredicate $ \resp ->
if hasValidHeader "Content-Type" (SBS.isPrefixOf . foldCase $ "text/html") resp
then do
let htmlContent = foldCase . LBS.take 20 $ responseBody resp
unless (LBS.isPrefixOf (foldCase "<!doctype html>") htmlContent) $
throw $ PredicateFailure "htmlIncludesDoctype" Nothing resp
else return () else return ()
-- * Predicate logic -- * Predicate logic
@ -376,12 +354,9 @@ newtype ResponsePredicate = ResponsePredicate
{ getResponsePredicate :: Response LBS.ByteString -> IO () { getResponsePredicate :: Response LBS.ByteString -> IO ()
} deriving (Generic) } deriving (Generic)
instance Semigroup ResponsePredicate where
ResponsePredicate a <> ResponsePredicate b = ResponsePredicate $ \x -> a x >> b x
instance Monoid ResponsePredicate where instance Monoid ResponsePredicate where
mempty = ResponsePredicate $ const $ return () mempty = ResponsePredicate $ const $ return ()
mappend = (<>) ResponsePredicate a `mappend` ResponsePredicate b = ResponsePredicate $ \x -> a x >> b x
-- | A predicate that depends on both the request and the response. -- | A predicate that depends on both the request and the response.
-- --
@ -393,11 +368,7 @@ newtype RequestPredicate = RequestPredicate
-- TODO: This isn't actually a monoid -- TODO: This isn't actually a monoid
instance Monoid RequestPredicate where instance Monoid RequestPredicate where
mempty = RequestPredicate (\r m -> httpLbs r m >>= \x -> return ([x])) mempty = RequestPredicate (\r m -> httpLbs r m >>= \x -> return ([x]))
mappend = (<>) RequestPredicate a `mappend` RequestPredicate b = RequestPredicate $ \r mgr ->
-- TODO: This isn't actually a monoid
instance Semigroup RequestPredicate where
RequestPredicate a <> RequestPredicate b = RequestPredicate $ \r mgr ->
liftM2 (<>) (a r mgr) (b r mgr) liftM2 (<>) (a r mgr) (b r mgr)
-- | A set of predicates. Construct one with 'mempty' and '<%>'. -- | A set of predicates. Construct one with 'mempty' and '<%>'.
@ -406,13 +377,10 @@ data Predicates = Predicates
, responsePredicates :: ResponsePredicate , responsePredicates :: ResponsePredicate
} deriving (Generic) } deriving (Generic)
instance Semigroup Predicates where
a <> b = Predicates (requestPredicates a <> requestPredicates b)
(responsePredicates a <> responsePredicates b)
instance Monoid Predicates where instance Monoid Predicates where
mempty = Predicates mempty mempty mempty = Predicates mempty mempty
mappend = (<>) a `mappend` b = Predicates (requestPredicates a <> requestPredicates b)
(responsePredicates a <> responsePredicates b)
class JoinPreds a where class JoinPreds a where
joinPreds :: a -> Predicates -> Predicates joinPreds :: a -> Predicates -> Predicates
@ -454,8 +422,8 @@ isRFC822Date s
Nothing -> False Nothing -> False
Just (_ :: UTCTime) -> True Just (_ :: UTCTime) -> True
status2XX :: Monad m => Maybe Request -> Response LBS.ByteString -> T.Text -> m () status2XX :: Monad m => Response b -> String -> m ()
status2XX mreq resp t status2XX r t
| status200 <= responseStatus resp && responseStatus resp < status300 | status200 <= responseStatus r && responseStatus r < status300
= return () = return ()
| otherwise = throw $ PredicateFailure t mreq resp | otherwise = fail t

View File

@ -2,7 +2,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
module Servant.QuickCheck.Internal.QuickCheck where module Servant.QuickCheck.Internal.QuickCheck where
import Control.Concurrent (tryReadMVar, newEmptyMVar, tryPutMVar) import Control.Concurrent (modifyMVar_, newMVar, readMVar)
import Control.Monad (unless) import Control.Monad (unless)
import qualified Data.ByteString.Lazy as LBS import qualified Data.ByteString.Lazy as LBS
import Data.Proxy (Proxy) import Data.Proxy (Proxy)
@ -11,13 +11,11 @@ import Network.Wai.Handler.Warp (withApplication)
import Prelude.Compat import Prelude.Compat
import Servant (Context (EmptyContext), HasServer, import Servant (Context (EmptyContext), HasServer,
Server, serveWithContext) Server, serveWithContext)
#if MIN_VERSION_servant_server(0,18,0)
import Servant (DefaultErrorFormatters, ErrorFormatters, HasContextEntry, type (.++))
#endif
import Servant.Client (BaseUrl (..), Scheme (..)) import Servant.Client (BaseUrl (..), Scheme (..))
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
import Test.Hspec (Expectation, expectationFailure) import Test.Hspec (Expectation, expectationFailure)
import Test.QuickCheck (Args (..), Result (..), quickCheckWithResult) import Test.QuickCheck (Args (..), Result (..),
quickCheckWithResult)
import Test.QuickCheck.Monadic (assert, forAllM, monadicIO, monitor, import Test.QuickCheck.Monadic (assert, forAllM, monadicIO, monitor,
run) run)
import Test.QuickCheck.Property (counterexample) import Test.QuickCheck.Property (counterexample)
@ -40,11 +38,7 @@ withServantServer api = withServantServerAndContext api EmptyContext
-- application. -- application.
-- --
-- /Since 0.0.0.0/ -- /Since 0.0.0.0/
#if MIN_VERSION_servant_server(0,18,0)
withServantServerAndContext :: (HasServer a ctx, HasContextEntry (ctx .++ DefaultErrorFormatters) ErrorFormatters)
#else
withServantServerAndContext :: HasServer a ctx withServantServerAndContext :: HasServer a ctx
#endif
=> Proxy a -> Context ctx -> IO (Server a) -> (BaseUrl -> IO r) -> IO r => Proxy a -> Context ctx -> IO (Server a) -> (BaseUrl -> IO r) -> IO r
withServantServerAndContext api ctx server t withServantServerAndContext api ctx server t
= withApplication (return . serveWithContext api ctx =<< server) $ \port -> = withApplication (return . serveWithContext api ctx =<< server) $ \port ->
@ -77,32 +71,25 @@ withServantServerAndContext api ctx server t
serversEqual :: HasGenRequest a => serversEqual :: HasGenRequest a =>
Proxy a -> BaseUrl -> BaseUrl -> Args -> ResponseEquality LBS.ByteString -> Expectation Proxy a -> BaseUrl -> BaseUrl -> Args -> ResponseEquality LBS.ByteString -> Expectation
serversEqual api burl1 burl2 args req = do serversEqual api burl1 burl2 args req = do
let reqs = (\f -> (f burl1, f burl2)) <$> runGenRequest api let reqs = (\f -> (f burl1, f burl2)) <$> genRequest api
-- This MVar stuff is clunky! But there doesn't seem to be an easy way to -- This MVar stuff is clunky! But there doesn't seem to be an easy way to
-- return results when a test fails, since an exception is throw. -- return results when a test fails, since an exception is throw.
deetsMVar <- newEmptyMVar deetsMVar <- newMVar $ error "should not be called"
r <- quickCheckWithResult args { chatty = False } $ monadicIO $ forAllM reqs $ \(req1, req2) -> do r <- quickCheckWithResult args { chatty = False } $ monadicIO $ forAllM reqs $ \(req1, req2) -> do
resp1 <- run $ C.httpLbs (noCheckStatus req1) defManager resp1 <- run $ C.httpLbs (noCheckStatus req1) defManager
resp2 <- run $ C.httpLbs (noCheckStatus req2) defManager resp2 <- run $ C.httpLbs (noCheckStatus req2) defManager
unless (getResponseEquality req resp1 resp2) $ do unless (getResponseEquality req resp1 resp2) $ do
monitor (counterexample "hi" ) monitor (counterexample "hi" )
_ <- run $ tryPutMVar deetsMVar $ ServerEqualityFailure req1 resp1 resp2 run $ modifyMVar_ deetsMVar $ const $ return $
ServerEqualityFailure req1 resp1 resp2
assert False assert False
case r of case r of
Success {} -> return () Success {} -> return ()
Failure{..} -> do Failure{..} -> readMVar deetsMVar >>= \x -> expectationFailure $
mx <- tryReadMVar deetsMVar "Failed:\n" ++ show x
case mx of
Just x ->
expectationFailure $ "Failed:\n" ++ show x
Nothing ->
expectationFailure $ "We failed to record a reason for failure: " <> show r
GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests" GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests"
NoExpectedFailure {} -> expectationFailure "No expected failure" NoExpectedFailure {} -> expectationFailure $ "No expected failure"
#if MIN_VERSION_QuickCheck(2,12,0) InsufficientCoverage {} -> expectationFailure $ "Insufficient coverage"
#else
InsufficientCoverage {} -> expectationFailure "Insufficient coverage"
#endif
-- | Check that a server satisfies the set of properties specified. -- | Check that a server satisfies the set of properties specified.
-- --
@ -125,61 +112,37 @@ serversEqual api burl1 burl2 args req = do
-- /Since 0.0.0.0/ -- /Since 0.0.0.0/
serverSatisfies :: (HasGenRequest a) => serverSatisfies :: (HasGenRequest a) =>
Proxy a -> BaseUrl -> Args -> Predicates -> Expectation Proxy a -> BaseUrl -> Args -> Predicates -> Expectation
serverSatisfies api = serverSatisfiesMgr api defManager serverSatisfies api burl args preds = do
let reqs = ($ burl) <$> genRequest api
-- | Check that a server satisfies the set of properties specified, and deetsMVar <- newMVar $ error "should not be called"
-- accept a 'Manager' for running the HTTP requests through.
--
-- See 'serverSatisfies' for more details.
--
-- @since 0.0.7.2
serverSatisfiesMgr :: (HasGenRequest a) =>
Proxy a -> C.Manager -> BaseUrl -> Args -> Predicates -> Expectation
serverSatisfiesMgr api manager burl args preds = do
let reqs = ($ burl) <$> runGenRequest api
deetsMVar <- newEmptyMVar
r <- quickCheckWithResult args { chatty = False } $ monadicIO $ forAllM reqs $ \req -> do r <- quickCheckWithResult args { chatty = False } $ monadicIO $ forAllM reqs $ \req -> do
v <- run $ finishPredicates preds (noCheckStatus req) manager v <- run $ finishPredicates preds (noCheckStatus req) defManager
_ <- run $ tryPutMVar deetsMVar v run $ modifyMVar_ deetsMVar $ const $ return v
case v of case v of
Just _ -> assert False Just _ -> assert False
_ -> return () _ -> return ()
case r of case r of
Success {} -> return () Success {} -> return ()
Failure {..} -> do Failure{..} -> readMVar deetsMVar >>= \x -> expectationFailure $
mx <- tryReadMVar deetsMVar "Failed:\n" ++ show x
case mx of
Just x ->
expectationFailure $ "Failed:\n" ++ show x
Nothing ->
expectationFailure $ "We failed to record a reason for failure: " <> show r
GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests" GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests"
NoExpectedFailure {} -> expectationFailure $ "No expected failure" NoExpectedFailure {} -> expectationFailure $ "No expected failure"
#if MIN_VERSION_QuickCheck(2,12,0) InsufficientCoverage {} -> expectationFailure $ "Insufficient coverage"
#else
InsufficientCoverage {} -> expectationFailure "Insufficient coverage"
#endif
serverDoesntSatisfy :: (HasGenRequest a) => serverDoesntSatisfy :: (HasGenRequest a) =>
Proxy a -> BaseUrl -> Args -> Predicates -> Expectation Proxy a -> BaseUrl -> Args -> Predicates -> Expectation
serverDoesntSatisfy api = serverDoesntSatisfyMgr api defManager serverDoesntSatisfy api burl args preds = do
let reqs = ($ burl) <$> genRequest api
serverDoesntSatisfyMgr :: (HasGenRequest a) =>
Proxy a -> C.Manager -> BaseUrl -> Args -> Predicates -> Expectation
serverDoesntSatisfyMgr api manager burl args preds = do
let reqs = ($ burl) <$> runGenRequest api
r <- quickCheckWithResult args $ monadicIO $ forAllM reqs $ \req -> do r <- quickCheckWithResult args $ monadicIO $ forAllM reqs $ \req -> do
v <- run $ finishPredicates preds (noCheckStatus req) manager v <- run $ finishPredicates preds (noCheckStatus req) defManager
assert $ not $ null v assert $ not $ null v
case r of case r of
Success {} -> return () Success {} -> return ()
GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests" GaveUp { numTests = n } -> expectationFailure $ "Gave up after " ++ show n ++ " tests"
Failure { output = m } -> expectationFailure $ "Failed:\n" ++ show m Failure { output = m } -> expectationFailure $ "Failed:\n" ++ show m
NoExpectedFailure {} -> expectationFailure $ "No expected failure" NoExpectedFailure {} -> expectationFailure $ "No expected failure"
#if MIN_VERSION_QuickCheck(2,12,0) InsufficientCoverage {} -> expectationFailure $ "Insufficient coverage"
#else
InsufficientCoverage {} -> expectationFailure "Insufficient coverage"
#endif
noCheckStatus :: C.Request -> C.Request noCheckStatus :: C.Request -> C.Request
#if MIN_VERSION_http_client(0,5,0) #if MIN_VERSION_http_client(0,5,0)

10
stack-ghc-8.0.1.yaml Normal file
View File

@ -0,0 +1,10 @@
# Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
resolver: nightly-2016-10-03
packages:
- '.'
extra-deps:
- 'servant-0.9'
- 'servant-server-0.9'
- 'servant-client-0.9'
flags: {}
extra-package-dbs: []

View File

@ -1,10 +1,7 @@
resolver: nightly-2018-09-03 # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)
resolver: lts-6.17
packages: packages:
- '.' - '.'
extra-deps: extra-deps: []
- hspec-discover-2.5.6
- hspec-core-2.5.6
- hspec-2.5.6
- QuickCheck-2.12
flags: {} flags: {}
extra-package-dbs: [] extra-package-dbs: []

44
test/Doctest.hs Normal file
View File

@ -0,0 +1,44 @@
module Main where
import Data.List (isPrefixOf)
import System.Directory
import System.FilePath
import System.FilePath.Find
import Test.DocTest
main :: IO ()
main = do
files <- find always (extension ==? ".hs") "src"
mCabalMacrosFile <- getCabalMacrosFile
doctest $ "-isrc" : "-Iinclude" :
(maybe [] (\ f -> ["-optP-include", "-optP" ++ f]) mCabalMacrosFile) ++
"-XOverloadedStrings" :
"-XDeriveFunctor" :
"-XFlexibleInstances" :
"-XFlexibleContexts" :
"-XMultiParamTypeClasses" :
"-XDataKinds" :
"-XTypeOperators" :
"-XGADTs" :
files
getCabalMacrosFile :: IO (Maybe FilePath)
getCabalMacrosFile = do
exists <- doesDirectoryExist "dist"
if exists
then do
contents <- getDirectoryContents "dist"
let rest = "build" </> "autogen" </> "cabal_macros.h"
whenExists $ case filter ("dist-sandbox-" `isPrefixOf`) contents of
[x] -> "dist" </> x </> rest
[] -> "dist" </> rest
xs -> error $ "ran doctests with multiple dist/dist-sandbox-xxxxx's: \n"
++ show xs ++ "\nTry cabal clean"
else return Nothing
where
whenExists :: FilePath -> IO (Maybe FilePath)
whenExists file = do
exists <- doesFileExist file
return $ if exists
then Just file
else Nothing

View File

@ -1,33 +1,25 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
module Servant.QuickCheck.InternalSpec (spec) where module Servant.QuickCheck.InternalSpec (spec) where
import Control.Concurrent.MVar (newMVar, readMVar, swapMVar) import Control.Concurrent.MVar (newMVar, readMVar, swapMVar)
import Control.Exception (SomeException)
import Control.Monad (replicateM)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import Data.Maybe (fromJust)
import Network.HTTP.Client (path, queryString)
import Prelude.Compat import Prelude.Compat
import Servant import Servant
import Servant.HTML.Blaze (HTML)
import qualified Text.Blaze.Html as Blaze
import qualified Text.Blaze.Html5 as Blaze5
import Test.Hspec (Spec, context, describe, it, shouldBe, import Test.Hspec (Spec, context, describe, it, shouldBe,
shouldContain) shouldContain)
import Test.Hspec.Core.Spec (Arg, Example, Result (..), ResultStatus (..), import Test.Hspec.Core.Spec (Arg, Example, Result (..),
defaultParams, safeEvaluateExample) defaultParams, evaluateExample)
import Test.QuickCheck.Gen (generate, unGen)
import Test.QuickCheck.Random (mkQCGen)
#if MIN_VERSION_servant(0,8,0)
import Servant.Test.ComprehensiveAPI (comprehensiveAPIWithoutStreamingOrRaw) import Servant.API.Internal.Test.ComprehensiveAPI (comprehensiveAPIWithoutRaw)
#else
import Servant.API.Internal.Test.ComprehensiveAPI (ComprehensiveAPI,
comprehensiveAPI)
#endif
import Servant.QuickCheck import Servant.QuickCheck
import Servant.QuickCheck.Internal (genRequest, runGenRequest, import Servant.QuickCheck.Internal (genRequest, serverDoesntSatisfy)
serverDoesntSatisfy)
spec :: Spec spec :: Spec
spec = do spec = do
@ -36,11 +28,6 @@ spec = do
isComprehensiveSpec isComprehensiveSpec
onlyJsonObjectSpec onlyJsonObjectSpec
notLongerThanSpec notLongerThanSpec
queryParamsSpec
queryFlagsSpec
deepPathSpec
htmlDocTypesSpec
unbiasedGenerationSpec
serversEqualSpec :: Spec serversEqualSpec :: Spec
serversEqualSpec = describe "serversEqual" $ do serversEqualSpec = describe "serversEqual" $ do
@ -51,42 +38,15 @@ serversEqualSpec = describe "serversEqual" $ do
serversEqual api burl1 burl2 args bodyEquality serversEqual api burl1 burl2 args bodyEquality
context "when servers are not equal" $ do context "when servers are not equal" $ do
it "provides the failing responses in the error message" $ do it "provides the failing responses in the error message" $ do
FailedWith err <- withServantServer api2 server2 $ \burl1 -> Fail _ err <- withServantServer api2 server2 $ \burl1 ->
withServantServer api2 server3 $ \burl2 -> do withServantServer api2 server3 $ \burl2 -> do
evalExample $ serversEqual api2 burl1 burl2 args bodyEquality evalExample $ serversEqual api2 burl1 burl2 args bodyEquality
show err `shouldContain` "Server equality failed"
show err `shouldContain` "Body: 1" show err `shouldContain` "Body: 1"
show err `shouldContain` "Body: 2" show err `shouldContain` "Body: 2"
show err `shouldContain` "Path: /failplz" show err `shouldContain` "Path: failplz/"
context "when JSON is equal but looks a bit different as a ByteString" $ do
it "sanity check: different whitespace same JSON objects bodyEquality fails" $ do
FailedWith err <- withServantServer jsonApi jsonServer1 $ \burl1 ->
withServantServer jsonApi jsonServer2 $ \burl2 -> do
evalExample $ serversEqual jsonApi burl1 burl2 args bodyEquality
show err `shouldContain` "Server equality failed"
it "jsonEquality considers equal JSON apis equal regardless of key ordering or whitespace" $ do
withServantServerAndContext jsonApi ctx jsonServer1 $ \burl1 ->
withServantServerAndContext jsonApi ctx jsonServer2 $ \burl2 ->
serversEqual jsonApi burl1 burl2 args jsonEquality
it "sees when JSON apis are not equal because any value is different" $ do
FailedWith err <- withServantServer jsonApi jsonServer2 $ \burl1 ->
withServantServer jsonApi jsonServer3 $ \burl2 -> do
evalExample $ serversEqual jsonApi burl1 burl2 args jsonEquality
show err `shouldContain` "Server equality failed"
show err `shouldContain` "Path: /jsonComparison"
it "sees when JSON apis are not equal due to different keys but same values" $ do
FailedWith err <- withServantServer jsonApi jsonServer2 $ \burl1 ->
withServantServer jsonApi jsonServer4 $ \burl2 -> do
evalExample $ serversEqual jsonApi burl1 burl2 args jsonEquality
show err `shouldContain` "Server equality failed"
show err `shouldContain` "Path: /jsonComparison"
serverSatisfiesSpec :: Spec serverSatisfiesSpec :: Spec
serverSatisfiesSpec = describe "serverSatisfies" $ do serverSatisfiesSpec = describe "serverSatisfies" $ do
@ -108,38 +68,33 @@ serverSatisfiesSpec = describe "serverSatisfies" $ do
context "when predicates are false" $ do context "when predicates are false" $ do
it "fails with informative error messages" $ do it "fails with informative error messages" $ do
FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
evalExample $ serverSatisfies api burl args (notAllowedContainsAllowHeader <%> mempty) evalExample $ serverSatisfies api burl args (getsHaveCacheControlHeader <%> mempty)
show err `shouldContain` "notAllowedContainsAllowHeader" err `shouldContain` "getsHaveCacheControlHeader"
show err `shouldContain` "Headers" err `shouldContain` "Headers"
show err `shouldContain` "Body" err `shouldContain` "Body"
onlyJsonObjectSpec :: Spec onlyJsonObjectSpec :: Spec
onlyJsonObjectSpec = describe "onlyJsonObjects" $ do onlyJsonObjectSpec = describe "onlyJsonObjects" $ do
it "fails correctly" $ do it "fails correctly" $ do
FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
(onlyJsonObjects <%> mempty) (onlyJsonObjects <%> mempty)
show err `shouldContain` "onlyJsonObjects" err `shouldContain` "onlyJsonObjects"
it "accepts non-JSON endpoints" $ do it "accepts non-JSON endpoints" $ do
withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl -> withServantServerAndContext octetAPI ctx serverOctetAPI $ \burl ->
serverSatisfies octetAPI burl args (onlyJsonObjects <%> mempty) serverSatisfies octetAPI burl args (onlyJsonObjects <%> mempty)
it "does not fail when there is no content-type" $ do
withServantServerAndContext api2 ctx serverFailing $ \burl ->
serverSatisfies api2 burl args (onlyJsonObjects <%> mempty)
notLongerThanSpec :: Spec notLongerThanSpec :: Spec
notLongerThanSpec = describe "notLongerThan" $ do notLongerThanSpec = describe "notLongerThan" $ do
it "fails correctly" $ do it "fails correctly" $ do
FailedWith err <- withServantServerAndContext api ctx server $ \burl -> do Fail _ err <- withServantServerAndContext api ctx server $ \burl -> do
evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args evalExample $ serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
(notLongerThan 1 <%> mempty) (notLongerThan 1 <%> mempty)
show err `shouldContain` "notLongerThan" err `shouldContain` "notLongerThan"
it "succeeds correctly" $ do it "succeeds correctly" $ do
withServantServerAndContext api ctx server $ \burl -> withServantServerAndContext api ctx server $ \burl ->
@ -149,82 +104,9 @@ isComprehensiveSpec :: Spec
isComprehensiveSpec = describe "HasGenRequest" $ do isComprehensiveSpec = describe "HasGenRequest" $ do
it "has instances for all 'servant' combinators" $ do it "has instances for all 'servant' combinators" $ do
let _g = genRequest comprehensiveAPIWithoutStreamingOrRaw let _g = genRequest comprehensiveAPIWithoutRaw
True `shouldBe` True -- This is a type-level check True `shouldBe` True -- This is a type-level check
deepPathSpec :: Spec
deepPathSpec = describe "Path components" $ do
it "are separated by slashes, without a trailing slash" $ do
let rng = mkQCGen 0
burl = BaseUrl Http "localhost" 80 ""
gen = runGenRequest deepAPI
req = (unGen gen rng 0) burl
path req `shouldBe` ("/one/two/three")
queryParamsSpec :: Spec
queryParamsSpec = describe "QueryParams" $ do
it "reduce to an HTTP query string correctly" $ do
let rng = mkQCGen 0
burl = BaseUrl Http "localhost" 80 ""
gen = runGenRequest paramsAPI
req = (unGen gen rng 0) burl
qs = C.unpack $ queryString req
qs `shouldBe` "one=_&two=_"
queryFlagsSpec :: Spec
queryFlagsSpec = describe "QueryFlags" $ do
it "reduce to an HTTP query string correctly" $ do
let rng = mkQCGen 0
burl = BaseUrl Http "localhost" 80 ""
gen = runGenRequest flagsAPI
req = (unGen gen rng 0) burl
qs = C.unpack $ queryString req
qs `shouldBe` "one&two"
htmlDocTypesSpec :: Spec
htmlDocTypesSpec = describe "HtmlDocTypes" $ do
it "fails HTML without doctype correctly" $ do
err <- withServantServerAndContext docTypeApi ctx noDocTypeServer $ \burl -> do
evalExample $ serverSatisfies docTypeApi burl args
(htmlIncludesDoctype <%> mempty)
show err `shouldContain` "htmlIncludesDoctype"
it "passes HTML with a doctype at start" $ do
withServantServerAndContext docTypeApi ctx docTypeServer $ \burl ->
serverSatisfies docTypeApi burl args (htmlIncludesDoctype <%> mempty)
it "accepts json endpoints and passes over them in silence" $ do
withServantServerAndContext api ctx server $ \burl -> do
serverSatisfies (Proxy :: Proxy (Get '[JSON] Int)) burl args
(htmlIncludesDoctype <%> mempty)
makeRandomRequest :: Proxy LargeAPI -> BaseUrl -> IO Integer
makeRandomRequest large burl = do
req <- generate $ runGenRequest large
pure $ fst . fromJust . C.readInteger . C.drop 1 . path $ req burl
unbiasedGenerationSpec :: Spec
unbiasedGenerationSpec = describe "Unbiased Generation of requests" $
it "frequency paired with generated endpoint should be more randomly distributed" $ do
let burl = BaseUrl Http "localhost" 80 ""
let runs = 10000 :: Double
someRequests <- replicateM 10000 (makeRandomRequest largeApi burl)
let mean = (sum $ map fromIntegral someRequests) / runs
let variancer x = let ix = fromIntegral x in (ix - mean) * (ix - mean)
let variance = (sum $ map variancer someRequests) / runs - 1
-- mean should be around 8.5. If this fails, we likely need more runs (or there's a bug!)
mean > 8 `shouldBe` True
mean < 9 `shouldBe` True
-- Std dev is likely around 4. Variance is probably greater than 20.
variance > 19.5 `shouldBe` True
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- APIs -- APIs
@ -237,17 +119,6 @@ type API = ReqBody '[JSON] String :> Post '[JSON] String
api :: Proxy API api :: Proxy API
api = Proxy api = Proxy
type ParamsAPI = QueryParam "one" () :> QueryParam "two" () :> Get '[JSON] ()
paramsAPI :: Proxy ParamsAPI
paramsAPI = Proxy
type FlagsAPI = QueryFlag "one" :> QueryFlag "two" :> Get '[JSON] ()
flagsAPI :: Proxy FlagsAPI
flagsAPI = Proxy
server :: IO (Server API) server :: IO (Server API)
server = do server = do
mvar <- newMVar "" mvar <- newMVar ""
@ -261,57 +132,12 @@ type API2 = "failplz" :> Get '[JSON] Int
api2 :: Proxy API2 api2 :: Proxy API2
api2 = Proxy api2 = Proxy
type DeepAPI = "one" :> "two" :> "three":> Get '[JSON] ()
deepAPI :: Proxy DeepAPI
deepAPI = Proxy
server2 :: IO (Server API2) server2 :: IO (Server API2)
server2 = return $ return 1 server2 = return $ return 1
server3 :: IO (Server API2) server3 :: IO (Server API2)
server3 = return $ return 2 server3 = return $ return 2
serverFailing :: IO (Server API2)
serverFailing = return . throwError $ err405
-- With Doctypes
type HtmlDoctype = Get '[HTML] Blaze.Html
docTypeApi :: Proxy HtmlDoctype
docTypeApi = Proxy
docTypeServer :: IO (Server HtmlDoctype)
docTypeServer = pure $ pure $ Blaze5.docTypeHtml $ Blaze5.span "Hello Test!"
noDocTypeServer :: IO (Server HtmlDoctype)
noDocTypeServer = pure $ pure $ Blaze.text "Hello Test!"
-- Api for unbiased generation of requests tests
largeApi :: Proxy LargeAPI
largeApi = Proxy
type LargeAPI
= "1" :> Get '[JSON] Int
:<|> "2" :> Get '[JSON] Int
:<|> "3" :> Get '[JSON] Int
:<|> "4" :> Get '[JSON] Int
:<|> "5" :> Get '[JSON] Int
:<|> "6" :> Get '[JSON] Int
:<|> "7" :> Get '[JSON] Int
:<|> "8" :> Get '[JSON] Int
:<|> "9" :> Get '[JSON] Int
:<|> "10" :> Get '[JSON] Int
:<|> "11" :> Get '[JSON] Int
:<|> "12" :> Get '[JSON] Int
:<|> "13" :> Get '[JSON] Int
:<|> "14" :> Get '[JSON] Int
:<|> "15" :> Get '[JSON] Int
:<|> "16" :> Get '[JSON] Int
type OctetAPI = Get '[OctetStream] BS.ByteString type OctetAPI = Get '[OctetStream] BS.ByteString
octetAPI :: Proxy OctetAPI octetAPI :: Proxy OctetAPI
@ -320,46 +146,17 @@ octetAPI = Proxy
serverOctetAPI :: IO (Server OctetAPI) serverOctetAPI :: IO (Server OctetAPI)
serverOctetAPI = return $ return "blah" serverOctetAPI = return $ return "blah"
type JsonApi = "jsonComparison" :> Get '[OctetStream] BS.ByteString
jsonApi :: Proxy JsonApi
jsonApi = Proxy
jsonServer1 :: IO (Server JsonApi)
jsonServer1 = return $ return "{ \"b\": [\"b\"], \"a\": 1 }" -- whitespace, ordering different
jsonServer2 :: IO (Server JsonApi)
jsonServer2 = return $ return "{\"a\": 1,\"b\":[\"b\"]}"
jsonServer3 :: IO (Server JsonApi)
jsonServer3 = return $ return "{\"a\": 2, \"b\": [\"b\"]}"
jsonServer4 :: IO (Server JsonApi)
jsonServer4 = return $ return "{\"c\": 1, \"d\": [\"b\"]}"
ctx :: Context '[BasicAuthCheck ()] ctx :: Context '[BasicAuthCheck ()]
ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext ctx = BasicAuthCheck (const . return $ NoSuchUser) :. EmptyContext
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
-- Utils -- Utils
------------------------------------------------------------------------------ ------------------------------------------------------------------------------
evalExample :: (Example e, Arg e ~ ()) => e -> IO EvalResult
evalExample e = do evalExample :: (Example e, Arg e ~ ()) => e -> IO Result
r <- safeEvaluateExample e defaultParams ($ ()) progCallback evalExample e = evaluateExample e defaultParams ($ ()) progCallback
case resultStatus r of
Success -> return $ AllGood
Failure _ reason -> return $ FailedWith $ show reason
Pending {} -> error "should not happen"
where where
progCallback _ = return () progCallback _ = return ()
data EvalResult
= AnException SomeException
| AllGood
| FailedWith String
deriving (Show)
args :: Args args :: Args
args = defaultArgs { maxSuccess = noOfTestCases } args = defaultArgs { maxSuccess = noOfTestCases }
@ -369,3 +166,8 @@ noOfTestCases = 20000
#else #else
noOfTestCases = 1000 noOfTestCases = 1000
#endif #endif
#if !MIN_VERSION_servant(0,8,0)
comprehensiveAPIWithoutRaw :: Proxy ComprehensiveAPI
comprehensiveAPIWithoutRaw = comprehensiveAPI
#endif