mirror of
https://github.com/ilyakooo0/roboservant.git
synced 2024-10-05 12:37:17 +03:00
171 lines
6.5 KiB
Haskell
171 lines
6.5 KiB
Haskell
{-# LANGUAGE DerivingVia #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
{-# LANGUAGE StandaloneDeriving #-}
|
|
{-# LANGUAGE TypeApplications #-}
|
|
{-# LANGUAGE RankNTypes #-}
|
|
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
|
{-# LANGUAGE DataKinds #-}
|
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
|
{-# LANGUAGE TypeFamilies #-}
|
|
|
|
import qualified Breakdown
|
|
import Data.Dynamic (toDyn)
|
|
import Data.Hashable (Hashable (hash))
|
|
import Data.Maybe (isNothing)
|
|
import Data.Void (Void)
|
|
import qualified Foo
|
|
import qualified Headers
|
|
import qualified Nested
|
|
import qualified Post
|
|
import qualified Put
|
|
import qualified Product
|
|
import qualified QueryParams
|
|
import qualified Roboservant as R
|
|
import qualified Roboservant.Server as RS
|
|
import qualified Roboservant.Client as RC
|
|
import qualified Seeded
|
|
import Test.Hspec
|
|
import Test.Hspec.Core.Spec (FailureReason (Reason), ResultStatus (Failure, Success), itemExample, mapSpecItem_, resultStatus)
|
|
import qualified Valid
|
|
import Servant ( Server, Proxy(..), serve, Endpoints, HasServer )
|
|
|
|
import Servant.Client(ClientEnv, mkClientEnv, baseUrlPort, parseBaseUrl,HasClient,ClientM)
|
|
import Network.Wai(Application)
|
|
import qualified Network.Wai.Handler.Warp as Warp
|
|
import Network.HTTP.Client (newManager, defaultManagerSettings)
|
|
import Control.Monad((>=>))
|
|
|
|
main :: IO ()
|
|
main = hspec spec
|
|
|
|
fuzzBoth
|
|
:: forall a .
|
|
(R.ToReifiedApi (Endpoints a), HasServer a '[], RS.FlattenServer a, RC.ToReifiedClientApi (Endpoints a), RC.FlattenClient a,
|
|
HasClient ClientM a)
|
|
=> String -> Server a -> R.Config -> (Maybe R.Report -> IO ()) -> Spec
|
|
fuzzBoth name server config condition = do
|
|
it (name <> " via server") $
|
|
RS.fuzz @a server config >>= condition
|
|
|
|
around (withServer (serve (Proxy :: Proxy a) server)) $
|
|
it (name <> " via client") $ \(clientEnv::ClientEnv) -> do
|
|
RC.fuzz @a clientEnv config >>= condition
|
|
|
|
withServer :: Application -> ActionWith ClientEnv -> IO ()
|
|
withServer app action = Warp.testWithApplication (pure app) (genClientEnv >=> action)
|
|
where genClientEnv port = do
|
|
baseUrl <- parseBaseUrl "http://localhost"
|
|
manager <- newManager defaultManagerSettings
|
|
pure $ mkClientEnv manager (baseUrl { baseUrlPort = port })
|
|
|
|
spec :: Spec
|
|
spec = do
|
|
describe "Basic usage" $ do
|
|
describe "noError" $ do
|
|
fuzzBoth @Valid.Api "find no error in a basic app" Valid.server R.defaultConfig (`shouldSatisfy` isNothing)
|
|
fuzzBoth @Valid.RoutedApi "finds no error in a valid generic app" Valid.routedServer R.defaultConfig (`shouldSatisfy` isNothing)
|
|
fuzzBoth @Valid.Api "fails coverage check" Valid.server R.defaultConfig {R.coverageThreshold = 0.6}
|
|
(\r ->
|
|
fmap (R.failureReason . R.rsException) r
|
|
`shouldSatisfy` ( \case
|
|
Just (R.InsufficientCoverage _) -> True
|
|
_ -> False
|
|
))
|
|
describe "posted body" $
|
|
fuzzBoth @Post.Api "passes a coverage check using a posted body" Post.server R.defaultConfig {R.coverageThreshold = 0.99}
|
|
(`shouldSatisfy` isNothing)
|
|
|
|
|
|
describe "PUTted body" $
|
|
fuzzBoth @Put.Api "passes a coverage check using a posted body" Put.server R.defaultConfig {R.coverageThreshold = 0.99}
|
|
(`shouldSatisfy` isNothing)
|
|
|
|
|
|
describe "seeded" $ do
|
|
let res = Seeded.Seed 1
|
|
shouldFail $ fuzzBoth @Seeded.Api "finds an error using information passed in" Seeded.server
|
|
(R.defaultConfig {R.seed = [(toDyn res, hash res)]})
|
|
(`shouldSatisfy` isNothing)
|
|
|
|
describe "Foo" $
|
|
fuzzBoth @Foo.Api "finds an error in a basic app" Foo.server R.defaultConfig (`shouldSatisfy` serverFailure)
|
|
|
|
describe "QueryParams" $
|
|
fuzzBoth @QueryParams.Api "can handle query params" QueryParams.server R.defaultConfig { R.seed = [R.hashedDyn (12::Int)] }
|
|
(`shouldSatisfy` isNothing)
|
|
|
|
describe "BuildFrom" $ do
|
|
describe "headers (and sum types)" $
|
|
fuzzBoth @Headers.Api "should find a failure that's dependent on using header info" Headers.server R.defaultConfig
|
|
(`shouldSatisfy` serverFailure)
|
|
describe "product types" $
|
|
fuzzBoth @Product.Api "should find a failure that's dependent on creating a product" Product.server
|
|
R.defaultConfig {R.seed = [R.hashedDyn 'a', R.hashedDyn (1 :: Int)]}
|
|
(`shouldSatisfy` serverFailure)
|
|
describe "Breakdown" $ do
|
|
fuzzBoth @Breakdown.ProductApi "handles products" Breakdown.productServer R.defaultConfig
|
|
(`shouldSatisfy` serverFailure)
|
|
fuzzBoth @Breakdown.SumApi "handles sums" Breakdown.sumServer R.defaultConfig
|
|
(`shouldSatisfy` serverFailure)
|
|
describe "flattening" $
|
|
fuzzBoth @Nested.FlatApi "can handle nested apis" Nested.server R.defaultConfig {R.coverageThreshold = 0.99}
|
|
(`shouldSatisfy` isNothing)
|
|
|
|
serverFailure :: Maybe R.Report -> Bool
|
|
serverFailure = \case
|
|
Just R.Report {..} ->
|
|
let R.RoboservantException {..} = rsException
|
|
in failureReason /= R.NoPossibleMoves
|
|
_ -> False
|
|
|
|
deriving via (R.Atom Foo.Foo) instance R.Breakdown Foo.Foo
|
|
|
|
deriving via (R.Atom Foo.Foo) instance R.BuildFrom Foo.Foo
|
|
|
|
deriving via (R.Atom Headers.Foo) instance R.Breakdown Headers.Foo
|
|
|
|
deriving via (R.Atom Headers.Foo) instance R.BuildFrom Headers.Foo
|
|
|
|
deriving via (R.Atom Seeded.Seed) instance R.Breakdown Seeded.Seed
|
|
|
|
deriving via (R.Atom Seeded.Seed) instance R.BuildFrom Seeded.Seed
|
|
|
|
deriving via (R.Atom Void) instance R.BuildFrom Void
|
|
|
|
deriving via (R.Atom Post.FooPost) instance R.Breakdown Post.FooPost
|
|
deriving via (R.Atom Post.FooPost) instance R.BuildFrom Post.FooPost
|
|
|
|
deriving via (R.Atom Put.Foo) instance R.Breakdown Put.Foo
|
|
deriving via (R.Atom Put.Foo) instance R.BuildFrom Put.Foo
|
|
|
|
|
|
|
|
deriving via (R.Compound Breakdown.Foo) instance R.Breakdown Breakdown.Foo
|
|
|
|
deriving via (R.Compound Product.Foo) instance R.BuildFrom Product.Foo
|
|
|
|
deriving via (R.Compound Breakdown.SomeSum) instance R.Breakdown Breakdown.SomeSum
|
|
|
|
-- | `shouldFail` allows you to assert that a given `Spec` should contain at least one failing test.
|
|
-- this is often useful when testing tests.
|
|
shouldFail :: SpecWith a -> SpecWith a
|
|
shouldFail =
|
|
mapSpecItem_
|
|
( \i ->
|
|
i
|
|
{ itemExample = \p a cb -> do
|
|
r <- itemExample i p a cb
|
|
pure
|
|
r
|
|
{ resultStatus = case resultStatus r of
|
|
Success -> Failure Nothing (Reason "Unexpected success")
|
|
Failure _ _ -> Success
|
|
x -> x
|
|
}
|
|
}
|
|
)
|