graphql 1.8.6 → 1.8.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/graphql/mutation_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/base_enum.erb +3 -1
  4. data/lib/generators/graphql/templates/base_input_object.erb +3 -1
  5. data/lib/generators/graphql/templates/base_interface.erb +4 -2
  6. data/lib/generators/graphql/templates/base_object.erb +3 -1
  7. data/lib/generators/graphql/templates/base_union.erb +3 -1
  8. data/lib/generators/graphql/templates/enum.erb +5 -3
  9. data/lib/generators/graphql/templates/interface.erb +6 -4
  10. data/lib/generators/graphql/templates/loader.erb +14 -12
  11. data/lib/generators/graphql/templates/mutation.erb +9 -6
  12. data/lib/generators/graphql/templates/mutation_type.erb +8 -6
  13. data/lib/generators/graphql/templates/object.erb +6 -4
  14. data/lib/generators/graphql/templates/query_type.erb +13 -11
  15. data/lib/generators/graphql/templates/union.erb +5 -3
  16. data/lib/graphql/argument.rb +19 -2
  17. data/lib/graphql/base_type.rb +1 -1
  18. data/lib/graphql/define/instance_definable.rb +2 -0
  19. data/lib/graphql/enum_type.rb +1 -0
  20. data/lib/graphql/execution/instrumentation.rb +28 -20
  21. data/lib/graphql/field.rb +3 -3
  22. data/lib/graphql/input_object_type.rb +2 -1
  23. data/lib/graphql/query.rb +1 -9
  24. data/lib/graphql/query/variables.rb +1 -18
  25. data/lib/graphql/relay.rb +0 -1
  26. data/lib/graphql/relay/mongo_relation_connection.rb +10 -0
  27. data/lib/graphql/relay/mutation.rb +2 -1
  28. data/lib/graphql/schema.rb +5 -4
  29. data/lib/graphql/schema/base_64_bp.rb +25 -0
  30. data/lib/graphql/schema/base_64_encoder.rb +5 -1
  31. data/lib/graphql/schema/field.rb +52 -22
  32. data/lib/graphql/schema/interface.rb +2 -0
  33. data/lib/graphql/schema/list.rb +3 -15
  34. data/lib/graphql/schema/member.rb +8 -4
  35. data/lib/graphql/schema/member/base_dsl_methods.rb +12 -6
  36. data/lib/graphql/schema/member/has_arguments.rb +7 -0
  37. data/lib/graphql/schema/member/relay_shortcuts.rb +47 -0
  38. data/lib/graphql/schema/member/scoped.rb +21 -0
  39. data/lib/graphql/schema/non_null.rb +8 -17
  40. data/lib/graphql/schema/resolver.rb +99 -76
  41. data/lib/graphql/schema/unique_within_type.rb +4 -1
  42. data/lib/graphql/schema/wrapper.rb +29 -0
  43. data/lib/graphql/static_validation/validation_context.rb +1 -3
  44. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -1
  45. data/lib/graphql/type_kinds.rb +32 -8
  46. data/lib/graphql/types/relay/base_connection.rb +17 -3
  47. data/lib/graphql/types/relay/base_edge.rb +12 -0
  48. data/lib/graphql/version.rb +1 -1
  49. data/spec/generators/graphql/enum_generator_spec.rb +8 -6
  50. data/spec/generators/graphql/install_generator_spec.rb +24 -20
  51. data/spec/generators/graphql/interface_generator_spec.rb +6 -4
  52. data/spec/generators/graphql/loader_generator_spec.rb +30 -26
  53. data/spec/generators/graphql/mutation_generator_spec.rb +18 -13
  54. data/spec/generators/graphql/object_generator_spec.rb +12 -6
  55. data/spec/generators/graphql/union_generator_spec.rb +10 -4
  56. data/spec/graphql/authorization_spec.rb +55 -14
  57. data/spec/graphql/input_object_type_spec.rb +11 -0
  58. data/spec/graphql/language/generation_spec.rb +8 -6
  59. data/spec/graphql/language/nodes_spec.rb +8 -6
  60. data/spec/graphql/relay/connection_instrumentation_spec.rb +1 -1
  61. data/spec/graphql/relay/mongo_relation_connection_spec.rb +54 -0
  62. data/spec/graphql/schema/field_spec.rb +9 -0
  63. data/spec/graphql/schema/list_spec.rb +46 -0
  64. data/spec/graphql/schema/member/scoped_spec.rb +161 -0
  65. data/spec/graphql/schema/non_null_spec.rb +46 -0
  66. data/spec/graphql/schema/object_spec.rb +15 -0
  67. data/spec/graphql/schema/resolver_spec.rb +165 -25
  68. data/spec/graphql/subscriptions/serialize_spec.rb +0 -22
  69. data/spec/graphql/subscriptions_spec.rb +19 -31
  70. data/spec/support/global_id.rb +23 -0
  71. data/spec/support/lazy_helpers.rb +1 -1
  72. data/spec/support/star_trek/data.rb +19 -2
  73. data/spec/support/star_trek/schema.rb +37 -22
  74. data/spec/support/star_wars/schema.rb +24 -17
  75. metadata +220 -208
@@ -77,7 +77,7 @@ describe GraphQL::Relay::ConnectionInstrumentation do
77
77
  # Before the object is wrapped in a connection, the instrumentation sees `Array`
78
78
  assert_equal ["StarWars::FactionRecord", "Array", "GraphQL::Relay::ArrayConnection"], ctx[:before_built_ins]
79
79
  # After the object is wrapped in a connection, it sees the connection object
80
- assert_equal ["StarWars::Faction", "GraphQL::Relay::ArrayConnection", "GraphQL::Types::Relay::PageInfo"], ctx[:after_built_ins]
80
+ assert_equal ["StarWars::Faction", "StarWars::ShipConnectionWithParentType", "GraphQL::Types::Relay::PageInfo"], ctx[:after_built_ins]
81
81
  end
82
82
  end
83
83
  end
@@ -18,6 +18,15 @@ describe GraphQL::Relay::MongoRelationConnection do
18
18
  ships.map { |e| e["node"]["name"] }
19
19
  end
20
20
 
21
+ def get_residents(ship)
22
+ ship["residents"]["edges"].map { |e| e["node"]["name"] }
23
+ end
24
+
25
+ def get_ships_residents(result)
26
+ ships = result["data"]["federation"]["bases"]["edges"]
27
+ Hash[ships.map { |e| [e["node"]["name"], get_residents(e["node"])] }]
28
+ end
29
+
21
30
  def get_page_info(result)
22
31
  result["data"]["federation"]["bases"]["pageInfo"]
23
32
  end
@@ -482,4 +491,49 @@ describe GraphQL::Relay::MongoRelationConnection do
482
491
  connection = GraphQL::Relay::BaseConnection.connection_for_nodes(relation)
483
492
  assert_equal GraphQL::Relay::MongoRelationConnection, connection
484
493
  end
494
+
495
+ describe "relations" do
496
+ let(:query_string) {%|
497
+ query getShips {
498
+ federation {
499
+ bases {
500
+ ... basesConnection
501
+ }
502
+ }
503
+ }
504
+
505
+ fragment basesConnection on BasesConnectionWithTotalCount {
506
+ edges {
507
+ cursor
508
+ node {
509
+ name
510
+ residents {
511
+ edges {
512
+ node {
513
+ name
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ |}
521
+
522
+ it "Mongoid::Association::Referenced::HasMany::Targets::Enumerable" do
523
+ result = star_trek_query(query_string)
524
+ assert_equal get_ships_residents(result), {
525
+ "Deep Space Station K-7" => [
526
+ "Shir th'Talias",
527
+ "Lurry",
528
+ "Mackenzie Calhoun"
529
+ ],
530
+ "Regula I" => [
531
+ "V. Madison",
532
+ "D. March",
533
+ "C. Marcus"
534
+ ],
535
+ "Deep Space Nine" => []
536
+ }
537
+ end
538
+ end
485
539
  end
@@ -11,6 +11,15 @@ describe GraphQL::Schema::Field do
11
11
  assert_equal :ok, arg_defn.metadata[:custom]
12
12
  end
13
13
 
14
+ it "can add argument directly with add_argument" do
15
+ argument = Jazz::Query.fields["instruments"].arguments["family"]
16
+
17
+ field.add_argument(argument)
18
+
19
+ assert_equal "family", field.arguments["family"].name
20
+ assert_equal Jazz::Family, field.arguments["family"].type
21
+ end
22
+
14
23
  it "attaches itself to its graphql_definition as type_class" do
15
24
  assert_equal field, field.graphql_definition.metadata[:type_class]
16
25
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe GraphQL::Schema::List do
6
+ let(:of_type) { Jazz::Musician }
7
+ let(:list_type) { GraphQL::Schema::List.new(of_type) }
8
+
9
+ it "returns list? to be true" do
10
+ assert list_type.list?
11
+ end
12
+
13
+ it "returns non_null? to be false" do
14
+ refute list_type.non_null?
15
+ end
16
+
17
+ it "returns kind to be GraphQL::TypeKinds::LIST" do
18
+ assert_equal GraphQL::TypeKinds::LIST, list_type.kind
19
+ end
20
+
21
+ it "returns correct type signature" do
22
+ assert_equal "[Musician]", list_type.to_type_signature
23
+ end
24
+
25
+ describe "comparison operator" do
26
+ it "will return false if list types 'of_type' are different" do
27
+ new_of_type = Jazz::InspectableKey
28
+ new_list_type = GraphQL::Schema::List.new(new_of_type)
29
+
30
+ refute_equal list_type, new_list_type
31
+ end
32
+
33
+ it "will return true if list types 'of_type' are the same" do
34
+ new_of_type = Jazz::Musician
35
+ new_list_type = GraphQL::Schema::List.new(new_of_type)
36
+
37
+ assert_equal list_type, new_list_type
38
+ end
39
+ end
40
+
41
+ describe "to_graphql" do
42
+ it "will return a list type" do
43
+ assert_kind_of GraphQL::ListType, list_type.to_graphql
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Schema::Member::Scoped do
5
+ class ScopeSchema < GraphQL::Schema
6
+ class BaseObject < GraphQL::Schema::Object
7
+ end
8
+
9
+ class Item < BaseObject
10
+ def self.scope_items(items, context)
11
+ if context[:french]
12
+ items.select { |i| i.name == "Trombone" }
13
+ elsif context[:english]
14
+ items.select { |i| i.name == "Paperclip" }
15
+ else
16
+ []
17
+ end
18
+ end
19
+
20
+ field :name, String, null: false
21
+ end
22
+
23
+ class FrenchItem < Item
24
+ def self.scope_items(items, context)
25
+ super(items, {french: true})
26
+ end
27
+ end
28
+
29
+ class Equipment < BaseObject
30
+ field :designation, String, null: false, method: :name
31
+ end
32
+
33
+ class BaseUnion < GraphQL::Schema::Union
34
+ end
35
+
36
+ class Thing < BaseUnion
37
+ def self.scope_items(items, context)
38
+ l = context.fetch(:first_letter)
39
+ items.select { |i| i.name.start_with?(l) }
40
+ end
41
+
42
+ possible_types Item, Equipment
43
+
44
+ def self.resolve_type(item, ctx)
45
+ if item.name == "Turbine"
46
+ Equipment
47
+ else
48
+ Item
49
+ end
50
+ end
51
+ end
52
+
53
+ class Query < BaseObject
54
+ field :items, [Item], null: false
55
+ field :unscoped_items, [Item], null: false,
56
+ scope: false,
57
+ method: :items
58
+ field :french_items, [FrenchItem], null: false,
59
+ method: :items
60
+ field :items_connection, Item.connection_type, null: false,
61
+ method: :items
62
+
63
+ def items
64
+ [
65
+ OpenStruct.new(name: "Trombone"),
66
+ OpenStruct.new(name: "Paperclip"),
67
+ ]
68
+ end
69
+
70
+ field :things, [Thing], null: false
71
+ def things
72
+ items + [OpenStruct.new(name: "Turbine")]
73
+ end
74
+ end
75
+
76
+ query(Query)
77
+ end
78
+
79
+ describe ".scope_items(items, ctx)" do
80
+ def get_item_names_with_context(ctx, field_name: "items")
81
+ query_str = "
82
+ {
83
+ #{field_name} {
84
+ name
85
+ }
86
+ }
87
+ "
88
+ res = ScopeSchema.execute(query_str, context: ctx)
89
+ res["data"][field_name].map { |i| i["name"] }
90
+ end
91
+
92
+ it "applies to lists when scope: true" do
93
+ assert_equal [], get_item_names_with_context({})
94
+ assert_equal ["Trombone"], get_item_names_with_context({french: true})
95
+ assert_equal ["Paperclip"], get_item_names_with_context({english: true})
96
+ end
97
+
98
+ it "is bypassed when scope: false" do
99
+ assert_equal ["Trombone", "Paperclip"], get_item_names_with_context({}, field_name: "unscopedItems")
100
+ end
101
+
102
+ it "is inherited" do
103
+ assert_equal ["Trombone"], get_item_names_with_context({}, field_name: "frenchItems")
104
+ end
105
+
106
+ it "is called for connection fields" do
107
+ query_str = "
108
+ {
109
+ itemsConnection {
110
+ edges {
111
+ node {
112
+ name
113
+ }
114
+ }
115
+ }
116
+ }
117
+ "
118
+ res = ScopeSchema.execute(query_str, context: {english: true})
119
+ names = res["data"]["itemsConnection"]["edges"].map { |e| e["node"]["name"] }
120
+ assert_equal ["Paperclip"], names
121
+ end
122
+
123
+ it "is called for abstract types" do
124
+ query_str = "
125
+ {
126
+ things {
127
+ ... on Item {
128
+ name
129
+ }
130
+ ... on Equipment {
131
+ designation
132
+ }
133
+ }
134
+ }
135
+ "
136
+ res = ScopeSchema.execute(query_str, context: {first_letter: "T"})
137
+ things = res["data"]["things"]
138
+ assert_equal [{ "name" => "Trombone" }, {"designation" => "Turbine"}], things
139
+ end
140
+ end
141
+
142
+ describe "Schema::Field.scoped?" do
143
+ it "prefers the override value" do
144
+ assert_equal false, ScopeSchema::Query.fields["unscopedItems"].scoped?
145
+ end
146
+
147
+ it "defaults to true for lists" do
148
+ assert_equal true, ScopeSchema::Query.fields["items"].type.list?
149
+ assert_equal true, ScopeSchema::Query.fields["items"].scoped?
150
+ end
151
+
152
+ it "defaults to true for connections" do
153
+ assert_equal true, ScopeSchema::Query.fields["itemsConnection"].connection?
154
+ assert_equal true, ScopeSchema::Query.fields["itemsConnection"].scoped?
155
+ end
156
+
157
+ it "defaults to false for others" do
158
+ assert_equal false, ScopeSchema::Item.fields["name"].scoped?
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ describe GraphQL::Schema::NonNull do
6
+ let(:of_type) { Jazz::Musician }
7
+ let(:non_null_type) { GraphQL::Schema::NonNull.new(of_type) }
8
+
9
+ it "returns list? to be false" do
10
+ refute non_null_type.list?
11
+ end
12
+
13
+ it "returns non_null? to be true" do
14
+ assert non_null_type.non_null?
15
+ end
16
+
17
+ it "returns kind to be GraphQL::TypeKinds::NON_NULL" do
18
+ assert_equal GraphQL::TypeKinds::NON_NULL, non_null_type.kind
19
+ end
20
+
21
+ it "returns correct type signature" do
22
+ assert_equal "Musician!", non_null_type.to_type_signature
23
+ end
24
+
25
+ describe "comparison operator" do
26
+ it "will return false if list types 'of_type' are different" do
27
+ new_of_type = Jazz::InspectableKey
28
+ new_non_null_type = GraphQL::Schema::NonNull.new(new_of_type)
29
+
30
+ refute_equal non_null_type, new_non_null_type
31
+ end
32
+
33
+ it "will return true if list types 'of_type' are the same" do
34
+ new_of_type = Jazz::Musician
35
+ new_non_null_type = GraphQL::Schema::NonNull.new(new_of_type)
36
+
37
+ assert_equal non_null_type, new_non_null_type
38
+ end
39
+ end
40
+
41
+ describe "to_graphql" do
42
+ it "will return a non null type" do
43
+ assert_kind_of GraphQL::NonNullType, non_null_type.to_graphql
44
+ end
45
+ end
46
+ end
@@ -4,6 +4,7 @@ require "spec_helper"
4
4
  describe GraphQL::Schema::Object do
5
5
  describe "class attributes" do
6
6
  let(:object_class) { Jazz::Ensemble }
7
+
7
8
  it "tells type data" do
8
9
  assert_equal "Ensemble", object_class.graphql_name
9
10
  assert_equal "A group of musicians playing together", object_class.description
@@ -52,6 +53,20 @@ describe GraphQL::Schema::Object do
52
53
  anonymous_class.graphql_name
53
54
  end
54
55
  end
56
+
57
+ class OverrideNameObject < GraphQL::Schema::Object
58
+ class << self
59
+ def default_graphql_name
60
+ "Override"
61
+ end
62
+ end
63
+ end
64
+
65
+ it "can override the default graphql_name" do
66
+ override_name_object = OverrideNameObject
67
+
68
+ assert_equal "Override", override_name_object.graphql_name
69
+ end
55
70
  end
56
71
 
57
72
  describe "implementing interfaces" do
@@ -76,7 +76,7 @@ describe GraphQL::Schema::Resolver do
76
76
 
77
77
  class PrepResolver1 < BaseResolver
78
78
  argument :int, Integer, required: true
79
-
79
+ undef_method :load_int
80
80
  def load_int(i)
81
81
  i * 10
82
82
  end
@@ -127,37 +127,122 @@ describe GraphQL::Schema::Resolver do
127
127
  class PrepResolver5 < PrepResolver1
128
128
  type Integer, null: true
129
129
 
130
- def before_prepare(int:)
130
+ def ready?(int:)
131
131
  check_for_magic_number(int)
132
132
  end
133
133
  end
134
134
 
135
135
  class PrepResolver6 < PrepResolver5
136
- def before_prepare(**args)
136
+ def ready?(**args)
137
137
  LazyBlock.new {
138
138
  super
139
139
  }
140
140
  end
141
141
  end
142
142
 
143
- class PrepResolver7 < PrepResolver1
143
+ class PrepResolver7 < GraphQL::Schema::Mutation
144
+ argument :int, Integer, required: true
145
+ field :errors, [String], null: true
146
+ field :int, Integer, null: true
147
+
148
+ def ready?(int:)
149
+ if int == 13
150
+ return false, { errors: ["Bad number!"] }
151
+ else
152
+ true
153
+ end
154
+ end
155
+
156
+ def resolve(int:)
157
+ { int: int }
158
+ end
159
+ end
160
+
161
+ module HasValue
162
+ include GraphQL::Schema::Interface
163
+ field :value, Integer, null: false
164
+ def self.resolve_type(obj, ctx)
165
+ if obj.is_a?(Integer)
166
+ IntegerWrapper
167
+ else
168
+ raise "Unexpected: #{obj.inspect}"
169
+ end
170
+ end
171
+ end
172
+
173
+ class IntegerWrapper < GraphQL::Schema::Object
174
+ implements HasValue
175
+ field :value, Integer, null: false, method: :object
176
+ end
177
+
178
+ class PrepResolver9 < BaseResolver
179
+ argument :int_id, ID, required: true, loads: HasValue
180
+ # Make sure the lazy object is resolved properly:
181
+ type HasValue, null: false
182
+ def object_from_id(type, id, ctx)
183
+ # Make sure a lazy object is handled appropriately
184
+ LazyBlock.new {
185
+ # Make sure that the right type ends up here
186
+ id.to_i + type.graphql_name.length
187
+ }
188
+ end
189
+
190
+ def resolve(int:)
191
+ int * 3
192
+ end
193
+ end
194
+
195
+ class PrepResolver10 < BaseResolver
196
+ argument :int1, Integer, required: true
197
+ argument :int2, Integer, required: true
144
198
  type Integer, null: true
199
+ def authorized?(int1:, int2:)
200
+ if int1 + int2 > context[:max_int]
201
+ raise GraphQL::ExecutionError, "Inputs too big"
202
+ elsif context[:min_int] && (int1 + int2 < context[:min_int])
203
+ false
204
+ else
205
+ true
206
+ end
207
+ end
145
208
 
146
- def load_int(int)
147
- int
209
+ def resolve(int1:, int2:)
210
+ int1 + int2
148
211
  end
212
+ end
149
213
 
150
- def validate_int(int)
151
- check_for_magic_number(int)
214
+ class PrepResolver11 < PrepResolver10
215
+ def authorized?(int1:, int2:)
216
+ LazyBlock.new { super(int1: int1 * 2, int2: int2) }
217
+ end
218
+ end
219
+
220
+ class PrepResolver12 < GraphQL::Schema::Mutation
221
+ argument :int1, Integer, required: true
222
+ argument :int2, Integer, required: true
223
+ field :error_messages, [String], null: true
224
+ field :value, Integer, null: true
225
+ def authorized?(int1:, int2:)
226
+ if int1 + int2 > context[:max_int]
227
+ return false, { error_messages: ["Inputs must be less than #{context[:max_int]} (but you provided #{int1 + int2})"] }
228
+ else
229
+ true
230
+ end
231
+ end
232
+
233
+ def resolve(int1:, int2:)
234
+ { value: int1 + int2 }
152
235
  end
153
236
  end
154
237
 
155
- class PrepResolver8 < PrepResolver7
156
- def validate_int(int)
157
- LazyBlock.new { super }
238
+ class PrepResolver13 < PrepResolver12
239
+ def authorized?(int1:, int2:)
240
+ # Increment the numbers so we can be sure they're passing through here
241
+ LazyBlock.new { super(int1: int1 + 1, int2: int2 + 1) }
158
242
  end
159
243
  end
160
244
 
245
+
161
246
  class Query < GraphQL::Schema::Object
162
247
  class CustomField < GraphQL::Schema::Field
163
248
  def resolve_field(*args)
@@ -188,12 +273,17 @@ describe GraphQL::Schema::Resolver do
188
273
  field :prep_resolver_5, resolver: PrepResolver5
189
274
  field :prep_resolver_6, resolver: PrepResolver6
190
275
  field :prep_resolver_7, resolver: PrepResolver7
191
- field :prep_resolver_8, resolver: PrepResolver8
276
+ field :prep_resolver_9, resolver: PrepResolver9
277
+ field :prep_resolver_10, resolver: PrepResolver10
278
+ field :prep_resolver_11, resolver: PrepResolver11
279
+ field :prep_resolver_12, resolver: PrepResolver12
280
+ field :prep_resolver_13, resolver: PrepResolver13
192
281
  end
193
282
 
194
283
  class Schema < GraphQL::Schema
195
284
  query(Query)
196
285
  lazy_resolve LazyBlock, :value
286
+ orphan_types IntegerWrapper
197
287
  end
198
288
  end
199
289
 
@@ -280,17 +370,25 @@ describe GraphQL::Schema::Resolver do
280
370
  refute res.key?("errors"), "#{description}: silent auth failure (no top-level error)"
281
371
  end
282
372
 
283
- describe "before_prepare" do
373
+ describe "ready?" do
284
374
  it "can raise errors" do
285
375
  res = exec_query("{ int: prepResolver5(int: 5) }")
286
376
  assert_equal 50, res["data"]["int"]
287
- add_error_assertions("prepResolver5", "before_prepare")
377
+ add_error_assertions("prepResolver5", "ready?")
288
378
  end
289
379
 
290
380
  it "can raise errors in lazy sync" do
291
381
  res = exec_query("{ int: prepResolver6(int: 5) }")
292
382
  assert_equal 50, res["data"]["int"]
293
- add_error_assertions("prepResolver6", "lazy before_prepare")
383
+ add_error_assertions("prepResolver6", "lazy ready?")
384
+ end
385
+
386
+ it "can return false and data" do
387
+ res = exec_query("{ int: prepResolver7(int: 13) { errors int } }")
388
+ assert_equal ["Bad number!"], res["data"]["int"]["errors"]
389
+
390
+ res = exec_query("{ int: prepResolver7(int: 213) { errors int } }")
391
+ assert_equal 213, res["data"]["int"]["int"]
294
392
  end
295
393
  end
296
394
 
@@ -319,17 +417,59 @@ describe GraphQL::Schema::Resolver do
319
417
  end
320
418
 
321
419
  describe "validating arguments" do
322
- test_cases = {
323
- "eager" => "prepResolver7",
324
- "lazy" => "prepResolver8",
325
- }
326
-
327
- test_cases.each do |mode, field_name|
328
- it "supports raising #{mode} errors" do
329
- res = exec_query("{ validatedInt: #{field_name}(int: 5) }")
330
- assert_equal 5, res["data"]["validatedInt"]
331
- add_error_assertions(field_name, "#{mode} validation")
420
+ describe ".authorized?" do
421
+ it "can raise an error to halt" do
422
+ res = exec_query("{ prepResolver10(int1: 5, int2: 6) }", context: { max_int: 9 })
423
+ assert_equal ["Inputs too big"], res["errors"].map { |e| e["message"] }
424
+
425
+ res = exec_query("{ prepResolver10(int1: 5, int2: 6) }", context: { max_int: 90 })
426
+ assert_equal 11, res["data"]["prepResolver10"]
427
+ end
428
+
429
+ it "can return a lazy object" do
430
+ # This is too big because it's modified in the overridden authorized? hook:
431
+ res = exec_query("{ prepResolver11(int1: 3, int2: 5) }", context: { max_int: 9 })
432
+ assert_equal ["Inputs too big"], res["errors"].map { |e| e["message"] }
433
+
434
+ res = exec_query("{ prepResolver11(int1: 3, int2: 5) }", context: { max_int: 90 })
435
+ assert_equal 8, res["data"]["prepResolver11"]
436
+ end
437
+
438
+ it "can return data early" do
439
+ res = exec_query("{ prepResolver12(int1: 9, int2: 5) { errorMessages } }", context: { max_int: 9 })
440
+ assert_equal ["Inputs must be less than 9 (but you provided 14)"], res["data"]["prepResolver12"]["errorMessages"]
441
+ # This works
442
+ res = exec_query("{ prepResolver12(int1: 2, int2: 5) { value } }", context: { max_int: 9 })
443
+ assert_equal 7, res["data"]["prepResolver12"]["value"]
332
444
  end
445
+
446
+ it "can return data early in a promise" do
447
+ # This is too big because it's modified in the overridden authorized? hook:
448
+ res = exec_query("{ prepResolver13(int1: 4, int2: 4) { errorMessages } }", context: { max_int: 9 })
449
+ assert_equal ["Inputs must be less than 9 (but you provided 10)"], res["data"]["prepResolver13"]["errorMessages"]
450
+ # This works
451
+ res = exec_query("{ prepResolver13(int1: 2, int2: 5) { value } }", context: { max_int: 9 })
452
+ assert_equal 7, res["data"]["prepResolver13"]["value"]
453
+ end
454
+
455
+ it "can return false to halt" do
456
+ str = <<-GRAPHQL
457
+ {
458
+ prepResolver10(int1: 5, int2: 10)
459
+ prepResolver11(int1: 3, int2: 5)
460
+ }
461
+ GRAPHQL
462
+ res = exec_query(str, context: { max_int: 100, min_int: 20 })
463
+ assert_equal({ "prepResolver10" => nil, "prepResolver11" => nil }, res["data"])
464
+ end
465
+ end
466
+ end
467
+
468
+ describe "Loading inputs" do
469
+ it "calls object_from_id" do
470
+ res = exec_query('{ prepResolver9(intId: "5") { value } }')
471
+ # (5 + 8) * 3
472
+ assert_equal 39, res["data"]["prepResolver9"]["value"]
333
473
  end
334
474
  end
335
475
  end