graphql 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/base_type.rb +6 -2
  3. data/lib/graphql/definition_helpers.rb +0 -5
  4. data/lib/graphql/definition_helpers/defined_by_config.rb +106 -102
  5. data/lib/graphql/definition_helpers/non_null_with_bang.rb +13 -9
  6. data/lib/graphql/definition_helpers/string_named_hash.rb +19 -15
  7. data/lib/graphql/definition_helpers/type_definer.rb +25 -21
  8. data/lib/graphql/enum_type.rb +8 -2
  9. data/lib/graphql/float_type.rb +1 -1
  10. data/lib/graphql/input_object_type.rb +27 -6
  11. data/lib/graphql/interface_type.rb +10 -0
  12. data/lib/graphql/introspection/fields_field.rb +2 -2
  13. data/lib/graphql/list_type.rb +12 -2
  14. data/lib/graphql/non_null_type.rb +11 -1
  15. data/lib/graphql/object_type.rb +19 -0
  16. data/lib/graphql/query.rb +2 -14
  17. data/lib/graphql/query/input_validation_result.rb +23 -0
  18. data/lib/graphql/query/literal_input.rb +3 -1
  19. data/lib/graphql/query/serial_execution/field_resolution.rb +3 -1
  20. data/lib/graphql/query/serial_execution/selection_resolution.rb +21 -15
  21. data/lib/graphql/query/variable_validation_error.rb +18 -0
  22. data/lib/graphql/query/variables.rb +4 -8
  23. data/lib/graphql/scalar_type.rb +10 -4
  24. data/lib/graphql/schema.rb +4 -2
  25. data/lib/graphql/schema/printer.rb +12 -3
  26. data/lib/graphql/schema/type_reducer.rb +4 -3
  27. data/lib/graphql/static_validation.rb +1 -0
  28. data/lib/graphql/static_validation/all_rules.rb +1 -0
  29. data/lib/graphql/static_validation/rules/document_does_not_exceed_max_depth.rb +79 -0
  30. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  31. data/lib/graphql/static_validation/validation_context.rb +63 -0
  32. data/lib/graphql/static_validation/validator.rb +1 -52
  33. data/lib/graphql/version.rb +1 -1
  34. data/readme.md +21 -22
  35. data/spec/graphql/enum_type_spec.rb +8 -0
  36. data/spec/graphql/input_object_type_spec.rb +101 -3
  37. data/spec/graphql/introspection/schema_type_spec.rb +6 -6
  38. data/spec/graphql/introspection/type_type_spec.rb +6 -6
  39. data/spec/graphql/language/transform_spec.rb +9 -5
  40. data/spec/graphql/list_type_spec.rb +23 -0
  41. data/spec/graphql/object_type_spec.rb +11 -4
  42. data/spec/graphql/query/executor_spec.rb +34 -5
  43. data/spec/graphql/query_spec.rb +22 -3
  44. data/spec/graphql/scalar_type_spec.rb +28 -0
  45. data/spec/graphql/schema/type_reducer_spec.rb +2 -2
  46. data/spec/graphql/static_validation/complexity_validator_spec.rb +15 -0
  47. data/spec/graphql/static_validation/rules/document_does_not_exceed_max_depth_spec.rb +93 -0
  48. data/spec/support/dairy_app.rb +2 -2
  49. data/spec/support/minimum_input_object.rb +8 -5
  50. metadata +10 -4
  51. data/spec/graphql/static_validation/complexity_validator.rb +0 -15
@@ -19,7 +19,7 @@ class GraphQL::StaticValidation::FieldsAreDefinedOnType
19
19
  return GraphQL::Language::Visitor::SKIP
20
20
  end
21
21
 
22
- field = parent_type.fields[ast_field.name]
22
+ field = parent_type.get_field(ast_field.name)
23
23
  if field.nil?
24
24
  errors << message("Field '#{ast_field.name}' doesn't exist on type '#{parent_type.name}'", parent)
25
25
  return GraphQL::Language::Visitor::SKIP
@@ -0,0 +1,63 @@
1
+ module GraphQL
2
+ module StaticValidation
3
+ # The validation context gets passed to each validator.
4
+ #
5
+ # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Visitor#visit} is called in {Validator#validate})
6
+ #
7
+ # It provides access to the schema & fragments which validators may read from.
8
+ #
9
+ # It holds a list of errors which each validator may add to.
10
+ #
11
+ # It also provides limited access to the {TypeStack} instance,
12
+ # which tracks state as you climb in and out of different fields.
13
+ class ValidationContext
14
+ attr_reader :schema, :document, :errors, :visitor, :fragments, :operations
15
+ def initialize(schema, document)
16
+ @schema = schema
17
+ @document = document
18
+ @fragments = {}
19
+ @operations = {}
20
+
21
+ document.definitions.each do |definition|
22
+ case definition
23
+ when GraphQL::Language::Nodes::FragmentDefinition
24
+ @fragments[definition.name] = definition
25
+ when GraphQL::Language::Nodes::OperationDefinition
26
+ @operations[definition.name] = definition
27
+ end
28
+ end
29
+
30
+ @errors = []
31
+ @visitor = GraphQL::Language::Visitor.new
32
+ @type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
33
+ end
34
+
35
+ def object_types
36
+ @type_stack.object_types
37
+ end
38
+
39
+ # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
40
+ def field_definition
41
+ @type_stack.field_definitions.last
42
+ end
43
+
44
+ # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
45
+ def directive_definition
46
+ @type_stack.directive_definitions.last
47
+ end
48
+
49
+ # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
50
+ def argument_definition
51
+ # Don't get the _last_ one because that's the current one.
52
+ # Get the second-to-last one, which is the parent of the current one.
53
+ @type_stack.argument_definitions[-2]
54
+ end
55
+
56
+ # Don't try to validate dynamic fields
57
+ # since they aren't defined by the type system
58
+ def skip_field?(field_name)
59
+ GraphQL::Schema::DYNAMIC_FIELDS.include?(field_name)
60
+ end
61
+ end
62
+ end
63
+ end
@@ -19,62 +19,11 @@ class GraphQL::StaticValidation::Validator
19
19
  # @param document [GraphQL::Language::Nodes::Document]
20
20
  # @return [Array<Hash>]
21
21
  def validate(document)
22
- context = Context.new(@schema, document)
22
+ context = GraphQL::StaticValidation::ValidationContext.new(@schema, document)
23
23
  @rules.each do |rules|
24
24
  rules.new.validate(context)
25
25
  end
26
26
  context.visitor.visit(document)
27
27
  context.errors.map(&:to_h)
28
28
  end
29
-
30
- # The validation context gets passed to each validator.
31
- #
32
- # It exposes a {GraphQL::Language::Visitor} where validators may add hooks. ({Visitor#visit} is called in {Validator#validate})
33
- #
34
- # It provides access to the schema & fragments which validators may read from.
35
- #
36
- # It holds a list of errors which each validator may add to.
37
- #
38
- # It also provides limited access to the {TypeStack} instance,
39
- # which tracks state as you climb in and out of different fields.
40
- class Context
41
- attr_reader :schema, :document, :errors, :visitor, :fragments
42
- def initialize(schema, document)
43
- @schema = schema
44
- @document = document
45
- @fragments = document.definitions.each_with_object({}) do |part, memo|
46
- part.is_a?(GraphQL::Language::Nodes::FragmentDefinition) && memo[part.name] = part
47
- end
48
- @errors = []
49
- @visitor = GraphQL::Language::Visitor.new
50
- @type_stack = GraphQL::StaticValidation::TypeStack.new(schema, visitor)
51
- end
52
-
53
- def object_types
54
- @type_stack.object_types
55
- end
56
-
57
- # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
58
- def field_definition
59
- @type_stack.field_definitions.last
60
- end
61
-
62
- # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
63
- def directive_definition
64
- @type_stack.directive_definitions.last
65
- end
66
-
67
- # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
68
- def argument_definition
69
- # Don't get the _last_ one because that's the current one.
70
- # Get the second-to-last one, which is the parent of the current one.
71
- @type_stack.argument_definitions[-2]
72
- end
73
-
74
- # Don't try to validate dynamic fields
75
- # since they aren't defined by the type system
76
- def skip_field?(field_name)
77
- GraphQL::Schema::DYNAMIC_FIELDS.include?(field_name)
78
- end
79
- end
80
29
  end
@@ -1,3 +1,3 @@
1
1
  module GraphQL
2
- VERSION = "0.11.0"
2
+ VERSION = "0.11.1"
3
3
  end
data/readme.md CHANGED
@@ -58,7 +58,10 @@ QueryType = GraphQL::ObjectType.define do
58
58
  end
59
59
 
60
60
  # Then create your schema
61
- Schema = GraphQL::Schema.new(query: QueryType)
61
+ Schema = GraphQL::Schema.new(
62
+ query: QueryType,
63
+ max_depth: 8,
64
+ )
62
65
  ```
63
66
 
64
67
  See also:
@@ -108,24 +111,6 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
108
111
  3. http://mgiroux.me/2015/getting-started-with-rails-graphql-relay/
109
112
  4. http://mgiroux.me/2015/uploading-files-using-relay-with-rails/
110
113
 
111
- ## To Do
112
-
113
- - Code clean-up
114
- - Raise if you try to configure an attribute which doesn't suit the type (ie, if you try to define `resolve` on an ObjectType, it should somehow raise)
115
- - make `DefinitionHelpers` more friendly for extension
116
- - Interface's possible types should be a property of the schema, not the interface
117
- - Type lookup should be by type name (to support reloaded constants in Rails code)
118
- - Add a complexity validator (reject queries if they're too big)
119
- - Add a custom dump for Relay (it expects default value strings to be double-quoted)
120
- - Make variable validation provide a specific, useful message
121
- - Add docs for shared behaviors & DRY code
122
- - After releasing the next version, use it for [graphql-libgraphqlparser](https://github.com/rmosolgo/graphql-libgraphqlparser-ruby)
123
- - Big ideas:
124
- - Revamp the fixture Schema to be more useful (better names, more extensible)
125
- - __Subscriptions__
126
- - This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
127
- - For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
128
-
129
114
  ## Goals
130
115
 
131
116
  - Implement the GraphQL spec & support a Relay front end
@@ -145,8 +130,22 @@ https://medium.com/@gauravtiwari/graphql-and-relay-on-rails-first-relay-powered-
145
130
  - [`graphql-batch`](https://github.com/shopify/graphql-batch), a batched query execution strategy
146
131
  - [`graphql-parallel`](https://github.com/rmosolgo/graphql-parallel), an asynchronous query execution strategy
147
132
  - [Example Relay support](https://github.com/rmosolgo/graphql-relay-ruby) in Ruby
133
+ - [`graphql-libgraphqlparser`](https://github.com/rmosolgo/graphql-libgraphqlparser), bindings to [libgraphqlparser](https://github.com/graphql/libgraphqlparser), a C-level parser.
148
134
 
149
- ## P.S.
135
+ ## To Do
150
136
 
151
- - Thanks to @sgwilym for the great logo!
152
- - Definition API heavily inspired by @seanchas's [implementation of GraphQL](https://github.com/seanchas/graphql)
137
+ - Code clean-up
138
+ - Raise if you try to configure an attribute which doesn't suit the type (ie, if you try to define `resolve` on an ObjectType, it should somehow raise)
139
+ - make `DefinitionHelpers` more friendly for extension
140
+ - Interface's possible types should be a property of the schema, not the interface
141
+ - Type lookup should be by type name (to support reloaded constants in Rails code)
142
+ - Depth validator should be aware of fragments
143
+ - Add a complexity validator (reject queries if they're too big)
144
+ - Add a custom dump for Relay (it expects default value strings to be double-quoted)
145
+ - Add docs for shared behaviors & DRY code
146
+ - Optimize the pure-Ruby parser (hand-write, RACC?!)
147
+ - Big ideas:
148
+ - Revamp the fixture Schema to be more useful (better names, more extensible)
149
+ - __Subscriptions__
150
+ - This is a good chance to make an `Operation` abstraction of which `query`, `mutation` and `subscription` are members
151
+ - For a subscription, `graphql` would send an outbound message to the system (allow the host application to manage its own subscriptions via Pusher, ActionCable, whatever)
@@ -16,4 +16,12 @@ describe GraphQL::EnumType do
16
16
  it 'has value description' do
17
17
  assert_equal("Animal with horns", enum.values['GOAT'].description)
18
18
  end
19
+
20
+ describe 'validate_input with bad input' do
21
+ let(:result) { DairyAnimalEnum.validate_input('bad enum') }
22
+
23
+ it 'returns an invalid result' do
24
+ assert(!result.valid?)
25
+ end
26
+ end
19
27
  end
@@ -12,8 +12,106 @@ describe GraphQL::InputObjectType do
12
12
 
13
13
  describe "input validation" do
14
14
  it "Accepts anything that yields key-value pairs to #all?" do
15
- values_obj = MinimumInputObject.new
16
- assert DairyProductInputType.valid_non_null_input?(values_obj)
15
+ values_obj = MinimumInputObject.new({"source" => "COW", "fatContent" => 0.4})
16
+ assert DairyProductInputType.valid_input?(values_obj)
17
+ end
18
+
19
+ describe 'validate_input with non-enumerable input' do
20
+ it "returns a valid result for MinimumInputObject" do
21
+ result = DairyProductInputType.validate_input(MinimumInputObject.new({"source" => "COW", "fatContent" => 0.4}))
22
+ assert(result.valid?)
23
+ end
24
+
25
+ it "returns an invalid result for MinimumInvalidInputObject" do
26
+ invalid_input = MinimumInputObject.new({"source" => "KOALA", "fatContent" => 0.4})
27
+ result = DairyProductInputType.validate_input(invalid_input)
28
+ assert(!result.valid?)
29
+ end
30
+ end
31
+
32
+ describe 'validate_input with enumerable input' do
33
+ describe 'with good input' do
34
+ let(:input) do
35
+ {
36
+ 'source' => 'COW',
37
+ 'fatContent' => 0.4
38
+ }
39
+ end
40
+ let(:result) { DairyProductInputType.validate_input(input) }
41
+
42
+ it 'returns a valid result' do
43
+ assert(result.valid?)
44
+ end
45
+ end
46
+
47
+ describe 'with bad enum and float' do
48
+ let(:result) { DairyProductInputType.validate_input('source' => 'KOALA', 'fatContent' => 'bad_num') }
49
+
50
+ it 'returns an invalid result' do
51
+ assert(!result.valid?)
52
+ end
53
+
54
+ it 'has problems with correct paths' do
55
+ paths = result.problems.map { |p| p['path'] }
56
+ assert(paths.include?(['source']))
57
+ assert(paths.include?(['fatContent']))
58
+ end
59
+
60
+ it 'has correct problem explanation' do
61
+ expected = DairyAnimalEnum.validate_input('KOALA').problems[0]['explanation']
62
+
63
+ source_problem = result.problems.detect { |p| p['path'] == ['source'] }
64
+ actual = source_problem['explanation']
65
+
66
+ assert_equal(expected, actual)
67
+ end
68
+ end
69
+
70
+ describe 'with extra argument' do
71
+ let(:result) { DairyProductInputType.validate_input('source' => 'COW', 'fatContent' => 0.4, 'isDelicious' => false) }
72
+
73
+ it 'returns an invalid result' do
74
+ assert(!result.valid?)
75
+ end
76
+
77
+ it 'has problem with correct path' do
78
+ paths = result.problems.map { |p| p['path'] }
79
+ assert_equal(paths, [['isDelicious']])
80
+ end
81
+
82
+ it 'has correct problem explanation' do
83
+ assert(result.problems[0]['explanation'].include?('Field is not defined'))
84
+ end
85
+ end
86
+
87
+ describe 'list with one invalid element' do
88
+ let(:list_type) { GraphQL::ListType.new(of_type: DairyProductInputType) }
89
+ let(:result) do
90
+ list_type.validate_input([
91
+ { 'source' => 'COW', 'fatContent' => 0.4 },
92
+ { 'source' => 'KOALA', 'fatContent' => 0.4 }
93
+ ])
94
+ end
95
+
96
+ it 'returns an invalid result' do
97
+ assert(!result.valid?)
98
+ end
99
+
100
+ it 'has one problem' do
101
+ assert_equal(result.problems.length, 1)
102
+ end
103
+
104
+ it 'has problem with correct path' do
105
+ path = result.problems[0]['path']
106
+ assert_equal(path, [1, 'source'])
107
+ end
108
+
109
+ it 'has problem with correct explanation' do
110
+ expected = DairyAnimalEnum.validate_input('KOALA').problems[0]['explanation']
111
+ actual = result.problems[0]['explanation']
112
+ assert_equal(expected, actual)
113
+ end
114
+ end
17
115
  end
18
116
  end
19
117
 
@@ -22,7 +120,7 @@ describe GraphQL::InputObjectType do
22
120
  let(:result) { DummySchema.execute(query_string, variables: variables) }
23
121
 
24
122
  describe "list inputs" do
25
- let(:variables) { {"search" => [MinimumInputObject.new]} }
123
+ let(:variables) { {"search" => [MinimumInputObject.new({"source" => "COW", "fatContent" => 0.4})]} }
26
124
  let(:query_string) {%|
27
125
  query getCheeses($search: [DairyProductInput]!){
28
126
  sheep: searchDairy(product: [{source: SHEEP, fatContent: 0.1}]) {
@@ -19,15 +19,15 @@ describe GraphQL::Introspection::SchemaType do
19
19
  "queryType"=>{
20
20
  "fields"=>[
21
21
  {"name"=>"cheese"},
22
- {"name"=>"milk"},
23
- {"name"=>"dairy"},
24
- {"name"=>"fromSource"},
25
- {"name"=>"favoriteEdible"},
26
22
  {"name"=>"cow"},
27
- {"name"=>"searchDairy"},
23
+ {"name"=>"dairy"},
28
24
  {"name"=>"error"},
29
25
  {"name"=>"executionError"},
30
- {"name"=>"maybeNull"}
26
+ {"name"=>"favoriteEdible"},
27
+ {"name"=>"fromSource"},
28
+ {"name"=>"maybeNull"},
29
+ {"name"=>"milk"},
30
+ {"name"=>"searchDairy"},
31
31
  ]
32
32
  },
33
33
  "mutationType"=> {
@@ -12,11 +12,11 @@ describe GraphQL::Introspection::TypeType do
12
12
  |}
13
13
  let(:result) { DummySchema.execute(query_string, context: {}, variables: {"cheeseId" => 2}) }
14
14
  let(:cheese_fields) {[
15
- {"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}},
16
15
  {"name"=>"flavor", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}},
16
+ {"name"=>"id", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "Int"}}},
17
17
  {"name"=>"origin", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "String"}}},
18
- {"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}},
19
18
  {"name"=>"similarCheese", "isDeprecated"=>false, "type"=>{"name"=>"Cheese", "ofType"=>nil}},
19
+ {"name"=>"source", "isDeprecated" => false, "type" => { "name" => "Non-Null", "ofType" => { "name" => "DairyAnimal"}}},
20
20
  ]}
21
21
 
22
22
  let(:dairy_animals) {[
@@ -37,11 +37,11 @@ describe GraphQL::Introspection::TypeType do
37
37
  {"name"=>"AnimalProduct"}
38
38
  ],
39
39
  "fields"=>[
40
- {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}},
41
- {"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}},
42
- {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}},
43
40
  {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}},
44
41
  {"type"=>{"name"=>"List", "ofType"=>{"name"=>"String"}}},
42
+ {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"ID"}}},
43
+ {"type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"String"}}},
44
+ {"type"=>{"name"=>"DairyAnimal", "ofType"=>nil}},
45
45
  ]
46
46
  },
47
47
  "dairyAnimal"=>{
@@ -75,7 +75,7 @@ describe GraphQL::Introspection::TypeType do
75
75
  |}
76
76
  let(:deprecated_fields) { {"name"=>"fatContent", "isDeprecated"=>true, "type"=>{"name"=>"Non-Null", "ofType"=>{"name"=>"Float"}}} }
77
77
  it 'can expose deprecated fields' do
78
- new_cheese_fields = cheese_fields + [deprecated_fields]
78
+ new_cheese_fields = [deprecated_fields] + cheese_fields
79
79
  expected = { "data" => {
80
80
  "cheeseType" => {
81
81
  "name"=> "Cheese",
@@ -113,17 +113,21 @@ describe GraphQL::Language::Transform do
113
113
 
114
114
  it 'transforms input objects' do
115
115
  res_one_pair = get_result(%q|{one: 1}|, parse: :value_input_object)
116
- res_two_pair = get_result(%q|{first: "Apple", second: "Banana"}|, parse: :value_input_object)
116
+ res_many_pair = get_result(%q|{first: "Apple", second: "Banana", third: ORANGE}|, parse: :value_input_object)
117
117
  res_empty = get_result(%q|{}|, parse: :value_input_object)
118
118
  res_empty_space = get_result(%q|{ }|, parse: :value_input_object)
119
119
 
120
120
  assert_equal('one', res_one_pair.arguments[0].name)
121
121
  assert_equal(1 , res_one_pair.arguments[0].value)
122
122
 
123
- assert_equal('first' , res_two_pair.arguments[0].name)
124
- assert_equal('Apple' , res_two_pair.arguments[0].value)
125
- assert_equal('second', res_two_pair.arguments[1].name)
126
- assert_equal('Banana', res_two_pair.arguments[1].value)
123
+ assert_equal("first" , res_many_pair.arguments[0].name)
124
+ assert_equal("Apple" , res_many_pair.arguments[0].value)
125
+ assert_equal("second", res_many_pair.arguments[1].name)
126
+ assert_equal("Banana", res_many_pair.arguments[1].value)
127
+
128
+ assert(res_many_pair.arguments[2].value.is_a?(GraphQL::Language::Nodes::Enum))
129
+ assert_equal("third", res_many_pair.arguments[2].name)
130
+ assert_equal("ORANGE", res_many_pair.arguments[2].value.name)
127
131
 
128
132
  assert_equal([], res_empty.arguments)
129
133
  assert_equal([], res_empty_space.arguments)
@@ -6,4 +6,27 @@ describe GraphQL::ListType do
6
6
  it 'coerces elements in the list' do
7
7
  assert_equal([1.0, 2.0, 3.0].inspect, float_list.coerce_input([1, 2, 3]).inspect)
8
8
  end
9
+
10
+ describe 'validate_input with bad input' do
11
+ let(:bad_num) { 'bad_num' }
12
+ let(:result) { float_list.validate_input([bad_num, 2.0, 3.0]) }
13
+
14
+ it 'returns an invalid result' do
15
+ assert(!result.valid?)
16
+ end
17
+
18
+ it 'has one problem' do
19
+ assert_equal(result.problems.length, 1)
20
+ end
21
+
22
+ it 'has path [0]' do
23
+ assert_equal(result.problems[0]['path'], [0])
24
+ end
25
+
26
+ it 'has the correct explanation' do
27
+ expected = GraphQL::FLOAT_TYPE.validate_input(bad_num).problems[0]['explanation']
28
+ actual = result.problems[0]['explanation']
29
+ assert_equal(actual, expected)
30
+ end
31
+ end
9
32
  end