{-# 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