Skip to content

Commit 2b64b65

Browse files
committed
Tests that use variables
1 parent fdeaf22 commit 2b64b65

File tree

4 files changed

+110
-6
lines changed

4 files changed

+110
-6
lines changed

src/GraphQL/API/Enum.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ instance forall conName p b sa sb f.
100100

101101
-- | For each enum type we need 1) a list of all possible values 2) a
102102
-- way to serialise and 3) deserialise.
103+
--
104+
-- TODO: Update this comment to explain what a GraphQLEnum is, why you might
105+
-- want an instance, and any laws that apply to method relations.
103106
class GraphQLEnum a where
107+
-- TODO: Document each of these methods.
104108
enumValues :: [Either NameError Name]
105109
default enumValues :: (Generic a, GenericEnumValues (Rep a)) => [Either NameError Name]
106110
enumValues = genericEnumValues @(Rep a)

src/GraphQL/Value.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ module GraphQL.Value
3232
-- * Names
3333
, Name(..)
3434
, NameError(..)
35+
, makeName
3536
-- * Objects
3637
, Object
3738
, Object'(..)
@@ -54,7 +55,7 @@ import qualified Data.Map as Map
5455
import Test.QuickCheck (Arbitrary(..), Gen, oneof, listOf, sized)
5556

5657
import GraphQL.Internal.Arbitrary (arbitraryText)
57-
import GraphQL.Internal.Name (Name(..), NameError(..))
58+
import GraphQL.Internal.Name (Name(..), NameError(..), makeName)
5859
import GraphQL.Internal.Syntax.AST (Variable)
5960
import qualified GraphQL.Internal.Syntax.AST as AST
6061
import GraphQL.Internal.OrderedMap (OrderedMap)

tests/EndToEndTests.hs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ module EndToEndTests (tests) where
88

99
import Protolude
1010

11-
import Data.Aeson (toJSON, object, (.=))
12-
import GraphQL (interpretAnonymousQuery)
11+
import Data.Aeson (Value(Null), toJSON, object, (.=))
12+
import qualified Data.Map as Map
13+
import GraphQL (compileQuery, executeQuery, interpretAnonymousQuery, interpretQuery)
1314
import GraphQL.API (Object, Field)
15+
import GraphQL.Internal.Syntax.AST (Variable(..))
1416
import GraphQL.Resolver ((:<>)(..), Handler)
17+
import GraphQL.Value (makeName)
1518
import GraphQL.Value.ToValue (ToValue(..))
1619
import Test.Tasty (TestTree)
1720
import Test.Tasty.Hspec (testSpec, describe, it, shouldBe)
@@ -196,3 +199,93 @@ tests = testSpec "End-to-end tests" $ do
196199
]
197200
]
198201
toJSON (toValue response) `shouldBe` expected
202+
describe "interpretQuery" $ do
203+
it "Handles the simplest named query" $ do
204+
let root = pure (viewServerDog mortgage)
205+
let query = [r|query myQuery {
206+
dog {
207+
name
208+
}
209+
}
210+
|]
211+
response <- interpretQuery @QueryRoot root query Nothing mempty
212+
let expected =
213+
object
214+
[ "data" .= object
215+
[ "dog" .= object
216+
[ "name" .= ("Mortgage" :: Text)
217+
]
218+
]
219+
]
220+
toJSON (toValue response) `shouldBe` expected
221+
it "Allows calling query by name" $ do
222+
let root = pure (viewServerDog mortgage)
223+
let query = [r|query myQuery {
224+
dog {
225+
name
226+
}
227+
}
228+
|]
229+
let Right name = makeName "myQuery"
230+
response <- interpretQuery @QueryRoot root query (Just name) mempty
231+
let expected =
232+
object
233+
[ "data" .= object
234+
[ "dog" .= object
235+
[ "name" .= ("Mortgage" :: Text)
236+
]
237+
]
238+
]
239+
toJSON (toValue response) `shouldBe` expected
240+
describe "Handles variables" $ do
241+
let root = pure (viewServerDog mortgage)
242+
let Right query =
243+
compileQuery [r|query myQuery($whichCommand: DogCommand) {
244+
dog {
245+
name
246+
doesKnowCommand(dogCommand: $whichCommand)
247+
}
248+
}
249+
|]
250+
it "Errors when no variables provided" $ do
251+
response <- executeQuery @QueryRoot root query Nothing mempty
252+
let expected =
253+
object
254+
[ "data" .= object
255+
[ "dog" .= object
256+
[ "name" .= ("Mortgage" :: Text)
257+
, "doesKnowCommand" .= Null
258+
]
259+
]
260+
, "errors" .=
261+
[
262+
object
263+
-- TODO: This error message is pretty bad. We should define
264+
-- a typeclass for client-friendly "Show" (separate from
265+
-- actual Show which remains extremely useful for debugging)
266+
-- and use that when including values in error messages.
267+
[ "message" .= ("Could not coerce Name {unName = \"dogCommand\"} to valid value: ValueScalar' ConstNull not an enum: [Right (Name {unName = \"Sit\"}),Right (Name {unName = \"Down\"}),Right (Name {unName = \"Heel\"})]" :: Text)
268+
]
269+
]
270+
]
271+
toJSON (toValue response) `shouldBe` expected
272+
it "Substitutes variables when they are provided" $ do
273+
-- TODO: This is a crummy way to make a variable map. jml doesn't want
274+
-- to come up with a new API in this PR, but probably we should have a
275+
-- very simple function to turn a JSON value / object into the
276+
-- variable map that we desire. Alternatively, we should have APIs
277+
-- like Aeson does.
278+
let Right varName = makeName "whichCommand"
279+
let vars = Map.singleton (Variable varName) (toValue Sit)
280+
response <- executeQuery @QueryRoot root query Nothing vars
281+
let expected =
282+
object
283+
[ "data" .= object
284+
[ "dog" .= object
285+
[ "name" .= ("Mortgage" :: Text)
286+
, "doesKnowCommand" .= False
287+
]
288+
]
289+
]
290+
toJSON (toValue response) `shouldBe` expected
291+

tests/ExampleSchema.hs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{-# LANGUAGE DataKinds #-}
22
{-# LANGUAGE DeriveGeneric #-}
3+
{-# LANGUAGE PatternSynonyms #-}
34
{-# LANGUAGE TypeOperators #-}
45
{-# LANGUAGE ViewPatterns #-}
56

@@ -60,13 +61,13 @@
6061
-- Haskell as we go.
6162

6263
module ExampleSchema
63-
( DogCommand
64+
( DogCommand(..)
6465
, Dog
6566
, Sentient
6667
, Pet
6768
, Alien
6869
, Human
69-
, CatCommand
70+
, CatCommand(..)
7071
, Cat
7172
, CatOrDog
7273
, DogOrHuman
@@ -88,7 +89,8 @@ import GraphQL.API
8889
-- XXX: This really shouldn't be part of Resolver, since whether or not a
8990
-- thing has a default is part of the API / Schema definition.
9091
import GraphQL.Resolver (Defaultable(..))
91-
import GraphQL.Value (unName)
92+
import GraphQL.Value (pattern ValueEnum, unName)
93+
import GraphQL.Value.ToValue (ToValue(..))
9294

9395
-- | A command that can be given to a 'Dog'.
9496
--
@@ -107,6 +109,10 @@ data DogCommand = Sit | Down | Heel deriving (Show, Eq, Ord, Generic)
107109

108110
instance GraphQLEnum DogCommand
109111

112+
-- TODO: Probably shouldn't have to do this for enums.
113+
instance ToValue DogCommand where
114+
toValue = ValueEnum . enumToValue
115+
110116
-- | A dog.
111117
--
112118
-- This is an example of a GraphQL \"object\".

0 commit comments

Comments
 (0)