graphql 1.8.6 → 1.8.7

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 (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