graphql 0.11.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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