schemas-0.2.0: test/SchemasSpec.hs
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
module SchemasSpec where
import Control.Exception
import qualified Data.Aeson as A
import Data.Either
import Data.Maybe
import Generators
import Person
import Person2
import Person3
import Schemas
import System.Timeout
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import Text.Show.Functions ()
main :: IO ()
main = hspec spec
spec :: Spec
spec = do
describe "encoding" $ do
prop "is the inverse of decoding" $ \(sc :: Schema) ->
decode (encode sc) == Right sc
describe "versions" $ do
prop "eliminates AllOf" $ \sc -> all (not . hasAllOf) (versions sc)
describe "finite" $ do
it "is reflexive (in absence of OneOf)" $ forAll (sized genSchema `suchThat` (not . hasOneOf)) $ \sc ->
sc `shouldBeSubtypeOf` sc
it "always produces a supertype (in absence of OneOf)" $
forAll (sized genSchema `suchThat` (not . hasOneOf)) $ \sc ->
forAll arbitrary $ \(SmallNatural size) ->
all (\sc -> isRight $ isSubtypeOf primValidators sc (finite size sc)) (versions sc)
describe "isSubtypeOf" $ do
it "subtypes can add fields" $ do
Record [makeField "a" prim True, makeField "def" prim True]
`shouldBeSubtypeOf` Record [makeField "def" prim True]
Record [makeField "a" prim False, makeField "def" prim True]
`shouldBeSubtypeOf` Record [makeField "def" prim True]
it "subtypes cannot turn a Required makeField into Optional" $ do
Record [makeField "a" prim False]
`shouldNotBeSubtypeOf` Record [makeField "a" prim True]
it "subtypes can turn an Optional makeField into Required" $ do
Record [makeField "a" prim True]
`shouldBeSubtypeOf` Record [makeField "a" prim False]
it "subtypes can relax the type of a field" $ do
Record [makeField "a" (Array prim) True]
`shouldBeSubtypeOf` Record [makeField "a" prim True]
it "subtypes cannot remove Required fields" $ do
Record [makeField "def" prim True] `shouldNotBeSubtypeOf` Record
[makeField "def" prim True, makeField "a" prim True]
it "subtypes can remove Optional fields" $ do
Record [makeField "def" prim True] `shouldBeSubtypeOf` Record
[makeField "def" prim True, makeField "a" prim (False)]
it "subtypes can remove enum choices" $ do
Enum ["def"] `shouldBeSubtypeOf` Enum ["A", "def"]
it "subtypes cannot add enum choices" $ do
Enum ["A", "def"] `shouldNotBeSubtypeOf` Enum ["def"]
it "subtypes can remove constructors" $ do
Union [constructor' "B" Empty]
`shouldBeSubtypeOf` Union [constructor' "A" Empty, constructor' "B" Empty]
it "subtypes cannot add constructors" $ do
Union [constructor' "A" prim, constructor' "B" Empty]
`shouldNotBeSubtypeOf` Union [constructor' "A" (prim)]
it "subtypes can expand an array" $ do
Array prim `shouldBeSubtypeOf` prim
it "subtypes cannot drop an array" $ do
prim `shouldNotBeSubtypeOf` Array prim
describe "examples" $ do
describe "Schemas" $ do
prop "finite(schema @Schema) is a supertype of (schema @Schema)" $ \(SmallNatural n) ->
theSchema @Schema `shouldBeSubtypeOf` finite n (theSchema @Schema)
describe "Person" $ do
it "decode is the inverse of encode (applicative)" $ do
decode (encode pepe) `shouldBe` Right pepe
describe "Person2" $ do
it "Person2 < Person" $ do
theSchema @Person2 `shouldBeSubtypeOf` theSchema @Person
it "pepe2 `as` Person" $ do
let encoder = encodeTo (theSchema @Person)
encoder `shouldSatisfy` isJust
decode (fromJust encoder pepe2) `shouldBe` Right pepe
it "pepe `as` Person2" $ do
let decoder = decodeFrom (theSchema @Person)
decoder `shouldSatisfy` isJust
fromJust decoder (encode pepe) `shouldBe` Right pepe2
it "Person < Person2" $ do
theSchema @Person `shouldBeSubtypeOf` theSchema @Person2
describe "Person3" $ do
it "finiteEncode works as expected" $ shouldLoop $ evaluate $ A.encode
(finiteEncode 4 laura3)
shouldBeSubtypeOf :: Schema -> Schema -> Expectation
shouldBeSubtypeOf a b = case isSubtypeOf primValidators a b of
Right _ -> pure ()
_ -> expectationFailure $ show a <> " should be a subtype of " <> show b
shouldNotBeSubtypeOf :: Schema -> Schema -> Expectation
shouldNotBeSubtypeOf a b = case isSubtypeOf primValidators a b of
Right _ -> expectationFailure $ show a <> " should not be a subtype of " <> show b
_ -> pure ()
shouldLoop :: (Show a, Eq a) => IO a -> Expectation
shouldLoop act = timeout 1000000 act `shouldReturn` Nothing
shouldNotLoop :: (Show a, Eq a) => IO a -> Expectation
shouldNotLoop act = timeout 1000000 act `shouldNotReturn` Nothing
makeField :: a -> Schema -> Bool -> (a, Field)
makeField n t isReq = (n, Field t isReq)
constructor' :: a -> b -> (a, b)
constructor' n t = (n, t)
prim = Prim "A"
primValidators = validatorsFor @(Schema, Double, Int, Bool)