graphql-api-0.4.0: tests/ASTSpec.hs
{-# LANGUAGE QuasiQuotes #-}
-- | Tests for AST, including parser and encoder.
module ASTSpec (spec) where
import Protolude
import Data.Attoparsec.Text (parseOnly)
import Text.RawString.QQ (r)
import Test.Hspec.QuickCheck (prop)
import Test.QuickCheck (arbitrary, forAll, resize)
import Test.Hspec
import GraphQL.Value (String(..))
import GraphQL.Internal.Name (Name)
import qualified GraphQL.Internal.Syntax.AST as AST
import qualified GraphQL.Internal.Syntax.Parser as Parser
import qualified GraphQL.Internal.Syntax.Encoder as Encoder
kitchenSink :: Text
kitchenSink = "query queryName($foo:ComplexType,$site:Site=MOBILE){whoever123is:node(id:[123,456]){id,... on User@defer{field2{id,alias:field1(first:10,after:$foo)@include(if:$foo){id,...frag}}}}}mutation likeStory{like(story:123)@defer{story{id}}}fragment frag on Friend{foo(size:$size,bar:$b,obj:{key:\"value\"})}\n"
dog :: Name
dog = "dog"
someName :: Name
someName = "name"
spec :: Spec
spec = describe "AST" $ do
describe "Parser and encoder" $ do
it "roundtrips on minified documents" $ do
let actual = Encoder.queryDocument <$> parseOnly Parser.queryDocument kitchenSink
actual `shouldBe` Right kitchenSink
describe "parsing numbers" $ do
it "works for some integers" $ do
parseOnly Parser.value "1" `shouldBe` Right (AST.ValueInt 1)
prop "works for all integers" $ do
\x -> parseOnly Parser.value (show x) == Right (AST.ValueInt x)
it "works for some floats" $ do
parseOnly Parser.value "1.5" `shouldBe` Right (AST.ValueFloat 1.5)
it "treats floats as floats even if they end with .0" $ do
parseOnly Parser.value "0.0" `shouldBe` Right (AST.ValueFloat 0.0)
prop "works for floats" $ do
\x -> parseOnly Parser.value (show x) == Right (AST.ValueFloat x)
describe "strings" $ do
prop "works for all strings" $ do
\(String x) ->
let input = AST.ValueString (AST.StringValue x)
output = Encoder.value input in
parseOnly Parser.value output == Right input
it "handles unusual strings" $ do
let input = AST.ValueString (AST.StringValue "\fh\244")
let output = Encoder.value input
-- \f is \u000c
output `shouldBe` "\"\\u000ch\244\""
parseOnly Parser.value output `shouldBe` Right input
describe "parsing values" $ do
prop "works for all literal values" $ do
forAll (resize 3 arbitrary) $ \x -> parseOnly Parser.value (Encoder.value x) `shouldBe` Right x
it "parses ununusual objects" $ do
let input = AST.ValueObject
(AST.ObjectValue
[ AST.ObjectField "s"
(AST.ValueString (AST.StringValue "\224\225v^6{FPDk\DC3\a")),
AST.ObjectField "Hsr" (AST.ValueInt 0)
])
let output = Encoder.value input
parseOnly Parser.value output `shouldBe` Right input
it "parses lists of floats" $ do
let input = AST.ValueList
(AST.ListValue
[ AST.ValueFloat 1.5
, AST.ValueFloat 1.5
])
let output = Encoder.value input
output `shouldBe` "[1.5,1.5]"
parseOnly Parser.value output `shouldBe` Right input
describe "Parser" $ do
it "parses shorthand syntax documents" $ do
let query = [r|{
dog {
name
}
}|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = AST.QueryDocument
[ AST.DefinitionOperation
(AST.AnonymousQuery
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
])
])
]
parsed `shouldBe` expected
it "parses anonymous query documents" $ do
let query = [r|query {
dog {
name
}
}|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing [] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
])
]))
]
parsed `shouldBe` expected
it "errors on missing selection set" $ do
let query = [r|query {
dog {
}
}|]
let Left parsed = parseOnly Parser.queryDocument query
-- this is not very explicit
parsed `shouldBe` "query document error! > definition error!: string"
it "parses invalid documents" $ do
let query = [r|{
dog {
name
}
}
query getName {
dog {
owner {
name
}
}
}|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = AST.QueryDocument
[ AST.DefinitionOperation
(AST.AnonymousQuery
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
])
])
, AST.DefinitionOperation
(AST.Query
(AST.Node (pure "getName") [] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "owner" [] []
[ AST.SelectionField (AST.Field Nothing someName [] [] [])
])
])
]))
]
parsed `shouldBe` expected
it "includes variable definitions" $ do
let query = [r|
query houseTrainedQuery($atOtherHomes: Boolean = true) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node (pure "houseTrainedQuery")
[ 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"))
] [] [])
])
]))
]
parsed `shouldBe` expected
it "parses anonymous query with variables" $ do
let query = [r|
query ($atOtherHomes: Boolean = true) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = 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"))
] [] [])
])
]))
]
parsed `shouldBe` expected
it "parses anonymous query with variable annotation" $ do
let query = [r|
query ($atOtherHomes: [Home!]) {
dog {
isHousetrained(atOtherHomes: $atOtherHomes)
}
}
|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = AST.QueryDocument
[ AST.DefinitionOperation
(AST.Query
(AST.Node Nothing
[ AST.VariableDefinition
(AST.Variable "atOtherHomes")
(AST.TypeList
(AST.ListType
(AST.TypeNonNull
(AST.NonNullTypeNamed (AST.NamedType "Home"))
)
)
)
Nothing
] []
[ AST.SelectionField
(AST.Field Nothing dog [] []
[ AST.SelectionField
(AST.Field Nothing "isHousetrained"
[ AST.Argument "atOtherHomes"
(AST.ValueVariable (AST.Variable "atOtherHomes"))
] [] [])
])
]))
]
parsed `shouldBe` expected
it "parses anonymous query with inline argument (List, Object, Enum, String, Number)" $ do
-- keys are not quoted for inline objects
let query = [r|
query {
dog {
isHousetrained(atOtherHomes: [{testKey: 123, anotherKey: "string"}])
}
}
|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = 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")))
]))
]))
] [] [])
])
]))
]
parsed `shouldBe` expected
it "parses anonymous query with fragment" $ do
-- keys are not quoted for inline objects
let query = [r|
fragment dogTest on Dog {
name
}
query {
dog {
...dogTest
}
}
|]
let Right parsed = parseOnly Parser.queryDocument query
let expected = 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" [])
])
])))
]
parsed `shouldBe` expected