graphql-api-0.4.0: tests/ValidationSpec.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
-- | Tests for query validation.
module ValidationSpec (spec) where
import Protolude
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck ((===))
import Test.Hspec
import qualified Data.Set as Set
import GraphQL.Internal.Name (Name)
import qualified GraphQL.Internal.Syntax.AST as AST
import GraphQL.Internal.Schema (emptySchema, Schema)
import GraphQL.Internal.Validation
( ValidationError(..)
, findDuplicates
, getErrors
, formatErrors
)
me :: Maybe Name
me = pure "me"
someName :: Name
someName = "name"
dog :: Name
dog = "dog"
-- | Schema used for these tests. Since none of them do type-level stuff, we
-- don't need to define it.
schema :: Schema
schema = emptySchema
spec :: Spec
spec = describe "Validation" $ do
describe "getErrors" $ do
it "Treats simple queries as valid" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
( AST.Query
( AST.Node me [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
)
]
getErrors schema doc `shouldBe` []
it "Treats anonymous queries as valid" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing [] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
])
]))
]
getErrors schema doc `shouldBe` []
it "Treats anonymous queries with variables as valid" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[ AST.VariableDefinition
(AST.Variable "atOtherHomes")
(AST.TypeNamed (AST.NamedType "Boolean"))
(Just (AST.ValueBoolean True))
] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[ AST.Argument "atOtherHomes"
(AST.ValueVariable (AST.Variable "atOtherHomes"))
] [] [])
])
]))
]
getErrors schema doc `shouldBe` []
it "Treats anonymous queries with annotated variables as valid ([[Boolean]]!)" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[ AST.VariableDefinition
(AST.Variable "atOtherHomes")
(AST.TypeNonNull (AST.NonNullTypeList (AST.ListType
(AST.TypeList (AST.ListType (AST.TypeNamed (AST.NamedType "Boolean"))))
)))
Nothing
] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[ AST.Argument "atOtherHomes"
(AST.ValueVariable (AST.Variable "atOtherHomes"))
] [] [])
])
]))
]
getErrors schema doc `shouldBe` []
it "Detects duplicate operation names" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
( AST.Query
( AST.Node me [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
)
, AST.DefinitionOperation
( AST.Query
( AST.Node me [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
)
]
getErrors schema doc `shouldBe` [DuplicateOperation me]
it "Detects duplicate anonymous operations" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
( AST.AnonymousQuery
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
, AST.DefinitionOperation
( AST.AnonymousQuery
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
]
let errors = getErrors schema doc
errors `shouldBe` [MixedAnonymousOperations 2 []]
formatErrors errors `shouldBe` ["Multiple anonymous operations defined. Found 2"]
it "Detects mixed operations" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
( AST.AnonymousQuery
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
)
, AST.DefinitionOperation
( AST.Query (AST.Node (pure "houseTrainedQuery") [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
]
))
]
let errors = getErrors schema doc
errors `shouldBe` [MixedAnonymousOperations 1 [Just "houseTrainedQuery"]]
formatErrors errors `shouldBe` ["Document contains both anonymous operations (1) and named operations ([Just (Name {unName = \"houseTrainedQuery\"})])"]
it "Detects non-existing type in variable definition" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[ AST.VariableDefinition
(AST.Variable "atOtherHomes")
(AST.TypeNamed (AST.NamedType "MyNonExistingType"))
(Just (AST.ValueBoolean True))
] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[ AST.Argument "atOtherHomes"
(AST.ValueVariable (AST.Variable "atOtherHomes"))
] [] [])
])
]))
]
getErrors schema doc `shouldBe` [VariableTypeNotFound (AST.Variable "atOtherHomes") "MyNonExistingType"]
it "Detects unused variable definition" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[ AST.VariableDefinition
(AST.Variable "atOtherHomes")
(AST.TypeNamed (AST.NamedType "String"))
(Just (AST.ValueBoolean True))
] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[] [] [])
])
]))
]
getErrors schema doc `shouldBe` [UnusedVariables (Set.fromList [AST.Variable "atOtherHomes"])]
it "Treats anonymous queries with inline arguments as valid" $ do
let doc = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[ AST.Argument "atOtherHomes"
(AST.ValueList (AST.ListValue [
(AST.ValueObject (AST.ObjectValue [
(AST.ObjectField "testKey" (AST.ValueInt 123)),
(AST.ObjectField "anotherKey" (AST.ValueString (AST.StringValue "string")))
]))
]))
] [] [])
])
]))
]
getErrors schema doc `shouldBe` []
it "Detects non-existent fragment type" $ do
let doc = AST.QueryDocument
[(AST.DefinitionFragment (AST.FragmentDefinition "dogTest"
(AST.NamedType "Dog") [] [
AST.SelectionField (AST.Field Nothing "name" [] [] [])
])),
(AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[] []
[AST.SelectionField
(AST.Field Nothing dog [] []
[AST.SelectionFragmentSpread (AST.FragmentSpread "dogTest" [])
])
])))
]
getErrors schema doc `shouldBe` [TypeConditionNotFound "Dog"]
describe "findDuplicates" $ do
prop "returns empty on unique lists" $ do
\xs -> findDuplicates @Int (ordNub xs) === []
prop "finds only duplicates" $ \xs -> do
all (>1) (count xs <$> findDuplicates @Int xs)
prop "finds all duplicates" $ \xs -> do
(sort . findDuplicates @Int) xs === (ordNub . sort . filter ((> 1) . count xs)) xs
-- | Count the number of times 'x' occurs in 'xs'.
count :: Eq a => [a] -> a -> Int
count xs x = (length . filter (== x)) xs