graphql 1.8.0.pre11 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/templates/schema.erb +1 -1
  3. data/lib/graphql/function.rb +2 -0
  4. data/lib/graphql/railtie.rb +1 -1
  5. data/lib/graphql/schema.rb +1 -0
  6. data/lib/graphql/schema/argument.rb +3 -2
  7. data/lib/graphql/schema/build_from_definition.rb +1 -1
  8. data/lib/graphql/schema/field.rb +96 -49
  9. data/lib/graphql/schema/interface.rb +21 -3
  10. data/lib/graphql/schema/list.rb +4 -0
  11. data/lib/graphql/schema/member/accepts_definition.rb +2 -2
  12. data/lib/graphql/schema/member/base_dsl_methods.rb +4 -0
  13. data/lib/graphql/schema/member/build_type.rb +4 -2
  14. data/lib/graphql/schema/member/has_fields.rb +1 -8
  15. data/lib/graphql/schema/mutation.rb +19 -88
  16. data/lib/graphql/schema/non_null.rb +4 -0
  17. data/lib/graphql/schema/object.rb +1 -1
  18. data/lib/graphql/schema/relay_classic_mutation.rb +14 -15
  19. data/lib/graphql/schema/resolver.rb +122 -0
  20. data/lib/graphql/subscriptions/instrumentation.rb +5 -1
  21. data/lib/graphql/subscriptions/serialize.rb +2 -0
  22. data/lib/graphql/tracing/new_relic_tracing.rb +26 -0
  23. data/lib/graphql/version.rb +1 -1
  24. data/readme.md +1 -1
  25. data/spec/generators/graphql/install_generator_spec.rb +1 -1
  26. data/spec/graphql/relay/mutation_spec.rb +5 -3
  27. data/spec/graphql/schema/build_from_definition_spec.rb +1 -1
  28. data/spec/graphql/schema/field_spec.rb +7 -24
  29. data/spec/graphql/schema/interface_spec.rb +25 -0
  30. data/spec/graphql/schema/member/accepts_definition_spec.rb +22 -0
  31. data/spec/graphql/schema/member/build_type_spec.rb +17 -0
  32. data/spec/graphql/schema/mutation_spec.rb +15 -14
  33. data/spec/graphql/schema/resolver_spec.rb +131 -0
  34. data/spec/graphql/subscriptions_spec.rb +267 -205
  35. data/spec/graphql/tracing/new_relic_tracing_spec.rb +47 -0
  36. data/spec/support/jazz.rb +6 -1
  37. data/spec/support/new_relic.rb +24 -0
  38. data/spec/support/star_trek/schema.rb +2 -2
  39. data/spec/support/star_wars/schema.rb +1 -2
  40. metadata +13 -4
@@ -16,7 +16,7 @@ describe GraphQL::Schema::Mutation do
16
16
 
17
17
  describe "definition" do
18
18
  it "passes along description" do
19
- assert_equal "Register a new musical instrument in the database", mutation.graphql_field.description
19
+ assert_equal "Register a new musical instrument in the database", mutation.field_options[:description]
20
20
  assert_equal "Autogenerated return type of AddInstrument", mutation.payload_type.description
21
21
  end
22
22
  end
@@ -29,15 +29,9 @@ describe GraphQL::Schema::Mutation do
29
29
  end
30
30
  end
31
31
 
32
- describe ".field" do
33
- it "returns a GraphQL::Field instance, for backwards compat" do
34
- field = mutation.field
35
- assert_instance_of GraphQL::Field, field
36
- assert_equal "addInstrument", field.name
37
- end
38
-
32
+ describe "a derived field" do
39
33
  it "has a reference to the mutation" do
40
- f = mutation.field
34
+ f = GraphQL::Schema::Field.from_options(name: "x", **mutation.field_options)
41
35
  assert_equal mutation, f.mutation
42
36
 
43
37
  # Make sure it's also present in the schema
@@ -52,6 +46,13 @@ describe GraphQL::Schema::Mutation do
52
46
  end
53
47
  end
54
48
 
49
+ describe ".field" do
50
+ it "raises a nice error when called without args" do
51
+ err = assert_raises(ArgumentError) { mutation.field }
52
+ assert_includes err.message, "Use `mutation: Jazz::AddInstrument` to attach this mutation instead."
53
+ end
54
+ end
55
+
55
56
  describe ".object_class" do
56
57
  it "can override & inherit the parent class" do
57
58
  obj_class = Class.new(GraphQL::Schema::Object)
@@ -121,9 +122,9 @@ describe GraphQL::Schema::Mutation do
121
122
  graphql_name "Thing3"
122
123
  end
123
124
 
124
- assert default_mutation_class.graphql_field.instance_variable_get("@return_type_null")
125
- assert nullable_mutation_class.graphql_field.instance_variable_get("@return_type_null")
126
- refute non_nullable_mutation_class.graphql_field.instance_variable_get("@return_type_null")
125
+ assert default_mutation_class.field_options[:null]
126
+ assert nullable_mutation_class.field_options[:null]
127
+ refute non_nullable_mutation_class.field_options[:null]
127
128
  end
128
129
 
129
130
  it "should inherit and override in subclasses" do
@@ -140,8 +141,8 @@ describe GraphQL::Schema::Mutation do
140
141
  null(true)
141
142
  end
142
143
 
143
- assert_equal false, inheriting_mutation.graphql_field.instance_variable_get("@return_type_null")
144
- assert override_mutation.graphql_field.instance_variable_get("@return_type_null")
144
+ assert_equal false, inheriting_mutation.field_options[:null]
145
+ assert override_mutation.field_options[:null]
145
146
  end
146
147
  end
147
148
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+ require "spec_helper"
3
+
4
+ describe GraphQL::Schema::Resolver do
5
+ module ResolverTest
6
+ class BaseResolver < GraphQL::Schema::Resolver
7
+ end
8
+
9
+ class Resolver1 < BaseResolver
10
+ argument :value, Integer, required: false
11
+ type [Integer, null: true], null: false
12
+
13
+ def initialize(object:, context:)
14
+ super
15
+ if defined?(@value)
16
+ raise "The instance should start fresh"
17
+ end
18
+ @value = [100]
19
+ end
20
+
21
+ def resolve(value: nil)
22
+ @value << value
23
+ @value
24
+ end
25
+ end
26
+
27
+ class Resolver2 < Resolver1
28
+ argument :extra_value, Integer, required: true
29
+
30
+ def resolve(extra_value:, **_rest)
31
+ value = super(_rest)
32
+ value << extra_value
33
+ value
34
+ end
35
+ end
36
+
37
+ class Resolver3 < Resolver1
38
+ end
39
+
40
+ class Resolver4 < BaseResolver
41
+ type Integer, null: false
42
+
43
+ extras [:ast_node]
44
+ def resolve(ast_node:)
45
+ object.value + ast_node.name.size
46
+ end
47
+ end
48
+
49
+ class Resolver5 < Resolver4
50
+ end
51
+
52
+ class Query < GraphQL::Schema::Object
53
+ class CustomField < GraphQL::Schema::Field
54
+ def resolve_field(*args)
55
+ value = super
56
+ if @name == "resolver3"
57
+ value << -1
58
+ end
59
+ value
60
+ end
61
+ end
62
+
63
+ field_class(CustomField)
64
+
65
+ field :resolver_1, resolver: Resolver1
66
+ field :resolver_2, resolver: Resolver2
67
+ field :resolver_3, resolver: Resolver3
68
+ field :resolver_3_again, resolver: Resolver3, description: "field desc"
69
+ field :resolver_4, "Positional description", resolver: Resolver4
70
+ field :resolver_5, resolver: Resolver5
71
+ end
72
+
73
+ class Schema < GraphQL::Schema
74
+ query(Query)
75
+ end
76
+ end
77
+
78
+ it "gets initialized for each resolution" do
79
+ # State isn't shared between calls:
80
+ res = ResolverTest::Schema.execute " { r1: resolver1(value: 1) r2: resolver1 }"
81
+ assert_equal [100, 1], res["data"]["r1"]
82
+ assert_equal [100, nil], res["data"]["r2"]
83
+ end
84
+
85
+ it "inherits type and arguments" do
86
+ res = ResolverTest::Schema.execute " { r1: resolver2(value: 1, extraValue: 2) r2: resolver2(extraValue: 3) }"
87
+ assert_equal [100, 1, 2], res["data"]["r1"]
88
+ assert_equal [100, nil, 3], res["data"]["r2"]
89
+ end
90
+
91
+ it "uses the object's field_class" do
92
+ res = ResolverTest::Schema.execute " { r1: resolver3(value: 1) r2: resolver3 }"
93
+ assert_equal [100, 1, -1], res["data"]["r1"]
94
+ assert_equal [100, nil, -1], res["data"]["r2"]
95
+ end
96
+
97
+ describe "resolve method" do
98
+ it "has access to the application object" do
99
+ res = ResolverTest::Schema.execute " { resolver4 } ", root_value: OpenStruct.new(value: 4)
100
+ assert_equal 13, res["data"]["resolver4"]
101
+ end
102
+
103
+ it "gets extras" do
104
+ res = ResolverTest::Schema.execute " { resolver4 } ", root_value: OpenStruct.new(value: 0)
105
+ assert_equal 9, res["data"]["resolver4"]
106
+ end
107
+ end
108
+
109
+ describe "extras" do
110
+ it "is inherited" do
111
+ res = ResolverTest::Schema.execute " { resolver4 resolver5 } ", root_value: OpenStruct.new(value: 0)
112
+ assert_equal 9, res["data"]["resolver4"]
113
+ assert_equal 9, res["data"]["resolver5"]
114
+ end
115
+ end
116
+
117
+ describe "when applied to a field" do
118
+ it "gets the field's description" do
119
+ assert_nil ResolverTest::Schema.find("Query.resolver3").description
120
+ assert_equal "field desc", ResolverTest::Schema.find("Query.resolver3Again").description
121
+ assert_equal "Positional description", ResolverTest::Schema.find("Query.resolver4").description
122
+ end
123
+
124
+ it "gets the field's name" do
125
+ # Matching name:
126
+ assert ResolverTest::Schema.find("Query.resolver3")
127
+ # Mismatched name:
128
+ assert ResolverTest::Schema.find("Query.resolver3Again")
129
+ end
130
+ end
131
+ end
@@ -75,7 +75,7 @@ class InMemoryBackend
75
75
  end
76
76
  end
77
77
  # Just a random stateful object for tracking what happens:
78
- class Payload
78
+ class SubscriptionPayload
79
79
  attr_reader :str
80
80
 
81
81
  def initialize
@@ -87,7 +87,65 @@ class InMemoryBackend
87
87
  @counter += 1
88
88
  end
89
89
  end
90
+ end
91
+
92
+ class ClassBasedInMemoryBackend < InMemoryBackend
93
+ class Payload < GraphQL::Schema::Object
94
+ field :str, String, null: false
95
+ field :int, Integer, null: false
96
+ end
97
+
98
+ class PayloadType < GraphQL::Schema::Enum
99
+ graphql_name "PayloadType"
100
+ # Arbitrary "kinds" of payloads which may be
101
+ # subscribed to separately
102
+ value "ONE"
103
+ value "TWO"
104
+ end
105
+
106
+ class StreamInput < GraphQL::Schema::InputObject
107
+ argument :user_id, ID, required: true
108
+ argument :type, PayloadType, required: false, default_value: "ONE"
109
+ end
110
+
111
+ class Subscription < GraphQL::Schema::Object
112
+ field :payload, Payload, null: false do
113
+ argument :id, ID, required: true
114
+ end
115
+
116
+ def payload(id:)
117
+ object
118
+ end
119
+
120
+ field :event, Payload, null: true do
121
+ argument :stream, StreamInput, required: false
122
+ end
123
+
124
+ def event(stream: nil)
125
+ object
126
+ end
127
+
128
+ field :my_event, Payload, null: true, subscription_scope: :me do
129
+ argument :type, PayloadType, required: false
130
+ end
131
+
132
+ def my_event(type: nil)
133
+ object
134
+ end
135
+ end
136
+
137
+ class Query < GraphQL::Schema::Object
138
+ field :dummy, Integer, null: true
139
+ end
140
+
141
+ class Schema < GraphQL::Schema
142
+ query(Query)
143
+ subscription(Subscription)
144
+ use InMemoryBackend::Subscriptions, extra: 123
145
+ end
146
+ end
90
147
 
148
+ class FromDefinitionInMemoryBackend < InMemoryBackend
91
149
  SchemaDefinition = <<-GRAPHQL
92
150
  type Subscription {
93
151
  payload(id: ID!): Payload!
@@ -126,7 +184,7 @@ class InMemoryBackend
126
184
  }
127
185
  Schema = GraphQL::Schema.from_definition(SchemaDefinition, default_resolve: Resolvers).redefine do
128
186
  use InMemoryBackend::Subscriptions,
129
- extra: 123
187
+ extra: 123
130
188
  end
131
189
 
132
190
  # TODO don't hack this (no way to add metadata from IDL parser right now)
@@ -162,260 +220,264 @@ describe GraphQL::Subscriptions do
162
220
  schema.subscriptions.reset
163
221
  end
164
222
 
165
- let(:root_object) {
166
- OpenStruct.new(
167
- payload: InMemoryBackend::Payload.new,
168
- )
169
- }
223
+ [ClassBasedInMemoryBackend, FromDefinitionInMemoryBackend].each do |in_memory_backend_class|
224
+ describe "using #{in_memory_backend_class}" do
225
+ let(:root_object) {
226
+ OpenStruct.new(
227
+ payload: in_memory_backend_class::SubscriptionPayload.new,
228
+ )
229
+ }
170
230
 
171
- let(:schema) { InMemoryBackend::Schema }
172
- let(:implementation) { schema.subscriptions }
173
- let(:deliveries) { implementation.deliveries }
174
- describe "pushing updates" do
175
- it "sends updated data" do
176
- query_str = <<-GRAPHQL
231
+ let(:schema) { in_memory_backend_class::Schema }
232
+ let(:implementation) { schema.subscriptions }
233
+ let(:deliveries) { implementation.deliveries }
234
+ describe "pushing updates" do
235
+ it "sends updated data" do
236
+ query_str = <<-GRAPHQL
177
237
  subscription ($id: ID!){
178
238
  firstPayload: payload(id: $id) { str, int }
179
239
  otherPayload: payload(id: "900") { int }
180
240
  }
181
- GRAPHQL
182
-
183
- # Initial subscriptions
184
- res_1 = schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "100" }, root_value: root_object)
185
- res_2 = schema.execute(query_str, context: { socket: "2" }, variables: { "id" => "200" }, root_value: root_object)
186
-
187
- # Initial response is nil, no broadcasts yet
188
- assert_equal(nil, res_1["data"])
189
- assert_equal(nil, res_2["data"])
190
- assert_equal [], deliveries["1"]
191
- assert_equal [], deliveries["2"]
192
-
193
- # Application stuff happens.
194
- # The application signals graphql via `subscriptions.trigger`:
195
- schema.subscriptions.trigger(:payload, {"id" => "100"}, root_object.payload)
196
- schema.subscriptions.trigger("payload", {"id" => "200"}, root_object.payload)
197
- # Symobls are OK too
198
- schema.subscriptions.trigger(:payload, {:id => "100"}, root_object.payload)
199
- schema.subscriptions.trigger("payload", {"id" => "300"}, nil)
200
-
201
- # Let's see what GraphQL sent over the wire:
202
- assert_equal({"str" => "Update", "int" => 1}, deliveries["1"][0]["data"]["firstPayload"])
203
- assert_equal({"str" => "Update", "int" => 2}, deliveries["2"][0]["data"]["firstPayload"])
204
- assert_equal({"str" => "Update", "int" => 3}, deliveries["1"][1]["data"]["firstPayload"])
205
- end
206
- end
241
+ GRAPHQL
242
+
243
+ # Initial subscriptions
244
+ res_1 = schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "100" }, root_value: root_object)
245
+ res_2 = schema.execute(query_str, context: { socket: "2" }, variables: { "id" => "200" }, root_value: root_object)
246
+
247
+ # Initial response is nil, no broadcasts yet
248
+ assert_equal(nil, res_1["data"])
249
+ assert_equal(nil, res_2["data"])
250
+ assert_equal [], deliveries["1"]
251
+ assert_equal [], deliveries["2"]
252
+
253
+ # Application stuff happens.
254
+ # The application signals graphql via `subscriptions.trigger`:
255
+ schema.subscriptions.trigger(:payload, {"id" => "100"}, root_object.payload)
256
+ schema.subscriptions.trigger("payload", {"id" => "200"}, root_object.payload)
257
+ # Symobls are OK too
258
+ schema.subscriptions.trigger(:payload, {:id => "100"}, root_object.payload)
259
+ schema.subscriptions.trigger("payload", {"id" => "300"}, nil)
260
+
261
+ # Let's see what GraphQL sent over the wire:
262
+ assert_equal({"str" => "Update", "int" => 1}, deliveries["1"][0]["data"]["firstPayload"])
263
+ assert_equal({"str" => "Update", "int" => 2}, deliveries["2"][0]["data"]["firstPayload"])
264
+ assert_equal({"str" => "Update", "int" => 3}, deliveries["1"][1]["data"]["firstPayload"])
265
+ end
266
+ end
207
267
 
208
- describe "subscribing" do
209
- it "doesn't call the subscriptions for invalid queries" do
210
- query_str = <<-GRAPHQL
268
+ describe "subscribing" do
269
+ it "doesn't call the subscriptions for invalid queries" do
270
+ query_str = <<-GRAPHQL
211
271
  subscription ($id: ID){
212
272
  payload(id: $id) { str, int }
213
273
  }
214
- GRAPHQL
274
+ GRAPHQL
215
275
 
216
- res = schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "100" }, root_value: root_object)
217
- assert_equal true, res.key?("errors")
218
- assert_equal 0, implementation.size
219
- end
220
- end
276
+ res = schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "100" }, root_value: root_object)
277
+ assert_equal true, res.key?("errors")
278
+ assert_equal 0, implementation.size
279
+ end
280
+ end
221
281
 
222
- describe "trigger" do
223
- it "uses the provided queue" do
224
- query_str = <<-GRAPHQL
282
+ describe "trigger" do
283
+ it "uses the provided queue" do
284
+ query_str = <<-GRAPHQL
225
285
  subscription ($id: ID!){
226
286
  payload(id: $id) { str, int }
227
287
  }
228
- GRAPHQL
288
+ GRAPHQL
229
289
 
230
- schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "8" }, root_value: root_object)
231
- schema.subscriptions.trigger("payload", { "id" => "8"}, root_object.payload)
232
- assert_equal ["1"], implementation.pushes
233
- end
290
+ schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "8" }, root_value: root_object)
291
+ schema.subscriptions.trigger("payload", { "id" => "8"}, root_object.payload)
292
+ assert_equal ["1"], implementation.pushes
293
+ end
234
294
 
235
- it "pushes errors" do
236
- query_str = <<-GRAPHQL
295
+ it "pushes errors" do
296
+ query_str = <<-GRAPHQL
237
297
  subscription ($id: ID!){
238
298
  payload(id: $id) { str, int }
239
299
  }
240
- GRAPHQL
300
+ GRAPHQL
241
301
 
242
- schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "8" }, root_value: root_object)
243
- schema.subscriptions.trigger("payload", { "id" => "8"}, OpenStruct.new(str: nil, int: nil))
244
- delivery = deliveries["1"].first
245
- assert_nil delivery.fetch("data")
246
- assert_equal 1, delivery["errors"].length
247
- end
302
+ schema.execute(query_str, context: { socket: "1" }, variables: { "id" => "8" }, root_value: root_object)
303
+ schema.subscriptions.trigger("payload", { "id" => "8"}, OpenStruct.new(str: nil, int: nil))
304
+ delivery = deliveries["1"].first
305
+ assert_nil delivery.fetch("data")
306
+ assert_equal 1, delivery["errors"].length
307
+ end
248
308
 
249
- it "coerces args" do
250
- query_str = <<-GRAPHQL
309
+ it "coerces args" do
310
+ query_str = <<-GRAPHQL
251
311
  subscription($type: PayloadType) {
252
312
  e1: event(stream: { userId: "3", type: $type }) { int }
253
313
  }
254
- GRAPHQL
255
-
256
- # Subscribe with explicit `TYPE`
257
- schema.execute(query_str, context: { socket: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
258
- # Subscribe with default `TYPE`
259
- schema.execute(query_str, context: { socket: "2" }, root_value: root_object)
260
- # Subscribe with non-matching `TYPE`
261
- schema.execute(query_str, context: { socket: "3" }, variables: { "type" => "TWO" }, root_value: root_object)
262
- # Subscribe with explicit null
263
- schema.execute(query_str, context: { socket: "4" }, variables: { "type" => nil }, root_value: root_object)
264
-
265
- # Trigger the subscription with coerceable args, different orders:
266
- schema.subscriptions.trigger("event", { "stream" => {"userId" => 3, "type" => "ONE"} }, OpenStruct.new(str: "", int: 1))
267
- schema.subscriptions.trigger("event", { "stream" => {"type" => "ONE", "userId" => "3"} }, OpenStruct.new(str: "", int: 2))
268
- # This is a non-trigger
269
- schema.subscriptions.trigger("event", { "stream" => {"userId" => "3", "type" => "TWO"} }, OpenStruct.new(str: "", int: 3))
270
- # These get default value of ONE (underscored / symbols are ok)
271
- schema.subscriptions.trigger("event", { stream: { user_id: "3"} }, OpenStruct.new(str: "", int: 4))
272
- # Trigger with null updates subscriptionss to null
273
- schema.subscriptions.trigger("event", { "stream" => {"userId" => 3, "type" => nil} }, OpenStruct.new(str: "", int: 5))
274
-
275
- assert_equal [1,2,4], deliveries["1"].map { |d| d["data"]["e1"]["int"] }
276
-
277
- # Same as socket_1
278
- assert_equal [1,2,4], deliveries["2"].map { |d| d["data"]["e1"]["int"] }
279
-
280
- # Received the "non-trigger"
281
- assert_equal [3], deliveries["3"].map { |d| d["data"]["e1"]["int"] }
282
-
283
- # Received the trigger with null
284
- assert_equal [5], deliveries["4"].map { |d| d["data"]["e1"]["int"] }
285
- end
314
+ GRAPHQL
315
+
316
+ # Subscribe with explicit `TYPE`
317
+ schema.execute(query_str, context: { socket: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
318
+ # Subscribe with default `TYPE`
319
+ schema.execute(query_str, context: { socket: "2" }, root_value: root_object)
320
+ # Subscribe with non-matching `TYPE`
321
+ schema.execute(query_str, context: { socket: "3" }, variables: { "type" => "TWO" }, root_value: root_object)
322
+ # Subscribe with explicit null
323
+ schema.execute(query_str, context: { socket: "4" }, variables: { "type" => nil }, root_value: root_object)
324
+
325
+ # Trigger the subscription with coerceable args, different orders:
326
+ schema.subscriptions.trigger("event", { "stream" => {"userId" => 3, "type" => "ONE"} }, OpenStruct.new(str: "", int: 1))
327
+ schema.subscriptions.trigger("event", { "stream" => {"type" => "ONE", "userId" => "3"} }, OpenStruct.new(str: "", int: 2))
328
+ # This is a non-trigger
329
+ schema.subscriptions.trigger("event", { "stream" => {"userId" => "3", "type" => "TWO"} }, OpenStruct.new(str: "", int: 3))
330
+ # These get default value of ONE (underscored / symbols are ok)
331
+ schema.subscriptions.trigger("event", { stream: { user_id: "3"} }, OpenStruct.new(str: "", int: 4))
332
+ # Trigger with null updates subscriptionss to null
333
+ schema.subscriptions.trigger("event", { "stream" => {"userId" => 3, "type" => nil} }, OpenStruct.new(str: "", int: 5))
334
+
335
+ assert_equal [1,2,4], deliveries["1"].map { |d| d["data"]["e1"]["int"] }
336
+
337
+ # Same as socket_1
338
+ assert_equal [1,2,4], deliveries["2"].map { |d| d["data"]["e1"]["int"] }
339
+
340
+ # Received the "non-trigger"
341
+ assert_equal [3], deliveries["3"].map { |d| d["data"]["e1"]["int"] }
342
+
343
+ # Received the trigger with null
344
+ assert_equal [5], deliveries["4"].map { |d| d["data"]["e1"]["int"] }
345
+ end
286
346
 
287
- it "allows context-scoped subscriptions" do
288
- query_str = <<-GRAPHQL
347
+ it "allows context-scoped subscriptions" do
348
+ query_str = <<-GRAPHQL
289
349
  subscription($type: PayloadType) {
290
350
  myEvent(type: $type) { int }
291
351
  }
292
- GRAPHQL
293
-
294
- # Subscriptions for user 1
295
- schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
296
- schema.execute(query_str, context: { socket: "2", me: "1" }, variables: { "type" => "TWO" }, root_value: root_object)
297
- # Subscription for user 2
298
- schema.execute(query_str, context: { socket: "3", me: "2" }, variables: { "type" => "ONE" }, root_value: root_object)
299
-
300
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 1), scope: "1")
301
- schema.subscriptions.trigger("myEvent", { "type" => "TWO" }, OpenStruct.new(str: "", int: 2), scope: "1")
302
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 3), scope: "2")
303
-
304
- # Delivered to user 1
305
- assert_equal [1], deliveries["1"].map { |d| d["data"]["myEvent"]["int"] }
306
- assert_equal [2], deliveries["2"].map { |d| d["data"]["myEvent"]["int"] }
307
- # Delivered to user 2
308
- assert_equal [3], deliveries["3"].map { |d| d["data"]["myEvent"]["int"] }
309
- end
352
+ GRAPHQL
353
+
354
+ # Subscriptions for user 1
355
+ schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
356
+ schema.execute(query_str, context: { socket: "2", me: "1" }, variables: { "type" => "TWO" }, root_value: root_object)
357
+ # Subscription for user 2
358
+ schema.execute(query_str, context: { socket: "3", me: "2" }, variables: { "type" => "ONE" }, root_value: root_object)
359
+
360
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 1), scope: "1")
361
+ schema.subscriptions.trigger("myEvent", { "type" => "TWO" }, OpenStruct.new(str: "", int: 2), scope: "1")
362
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 3), scope: "2")
363
+
364
+ # Delivered to user 1
365
+ assert_equal [1], deliveries["1"].map { |d| d["data"]["myEvent"]["int"] }
366
+ assert_equal [2], deliveries["2"].map { |d| d["data"]["myEvent"]["int"] }
367
+ # Delivered to user 2
368
+ assert_equal [3], deliveries["3"].map { |d| d["data"]["myEvent"]["int"] }
369
+ end
310
370
 
311
- if defined?(GlobalID)
312
- it "allows complex object subscription scopes" do
313
- query_str = <<-GRAPHQL
371
+ if defined?(GlobalID)
372
+ it "allows complex object subscription scopes" do
373
+ query_str = <<-GRAPHQL
314
374
  subscription($type: PayloadType) {
315
375
  myEvent(type: $type) { int }
316
376
  }
317
- GRAPHQL
318
-
319
- # Global ID Backed User
320
- schema.execute(query_str, context: { socket: "1", me: GlobalIDUser.new(1) }, variables: { "type" => "ONE" }, root_value: root_object)
321
- schema.execute(query_str, context: { socket: "2", me: GlobalIDUser.new(1) }, variables: { "type" => "TWO" }, root_value: root_object)
322
- # ToParam Backed User
323
- schema.execute(query_str, context: { socket: "3", me: ToParamUser.new(2) }, variables: { "type" => "ONE" }, root_value: root_object)
324
- # Array of Objects
325
- schema.execute(query_str, context: { socket: "4", me: [GlobalIDUser.new(4), ToParamUser.new(5)] }, variables: { "type" => "ONE" }, root_value: root_object)
326
-
327
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 1), scope: GlobalIDUser.new(1))
328
- schema.subscriptions.trigger("myEvent", { "type" => "TWO" }, OpenStruct.new(str: "", int: 2), scope: GlobalIDUser.new(1))
329
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 3), scope: ToParamUser.new(2))
330
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 4), scope: [GlobalIDUser.new(4), ToParamUser.new(5)])
331
-
332
- # Delivered to GlobalIDUser
333
- assert_equal [1], deliveries["1"].map { |d| d["data"]["myEvent"]["int"] }
334
- assert_equal [2], deliveries["2"].map { |d| d["data"]["myEvent"]["int"] }
335
- # Delivered to ToParamUser
336
- assert_equal [3], deliveries["3"].map { |d| d["data"]["myEvent"]["int"] }
337
- # Delivered to Array of GlobalIDUser and ToParamUser
338
- assert_equal [4], deliveries["4"].map { |d| d["data"]["myEvent"]["int"] }
339
- end
340
- end
341
-
342
- describe "errors" do
343
- class ErrorPayload
344
- def int
345
- raise "Boom!"
377
+ GRAPHQL
378
+
379
+ # Global ID Backed User
380
+ schema.execute(query_str, context: { socket: "1", me: GlobalIDUser.new(1) }, variables: { "type" => "ONE" }, root_value: root_object)
381
+ schema.execute(query_str, context: { socket: "2", me: GlobalIDUser.new(1) }, variables: { "type" => "TWO" }, root_value: root_object)
382
+ # ToParam Backed User
383
+ schema.execute(query_str, context: { socket: "3", me: ToParamUser.new(2) }, variables: { "type" => "ONE" }, root_value: root_object)
384
+ # Array of Objects
385
+ schema.execute(query_str, context: { socket: "4", me: [GlobalIDUser.new(4), ToParamUser.new(5)] }, variables: { "type" => "ONE" }, root_value: root_object)
386
+
387
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 1), scope: GlobalIDUser.new(1))
388
+ schema.subscriptions.trigger("myEvent", { "type" => "TWO" }, OpenStruct.new(str: "", int: 2), scope: GlobalIDUser.new(1))
389
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 3), scope: ToParamUser.new(2))
390
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, OpenStruct.new(str: "", int: 4), scope: [GlobalIDUser.new(4), ToParamUser.new(5)])
391
+
392
+ # Delivered to GlobalIDUser
393
+ assert_equal [1], deliveries["1"].map { |d| d["data"]["myEvent"]["int"] }
394
+ assert_equal [2], deliveries["2"].map { |d| d["data"]["myEvent"]["int"] }
395
+ # Delivered to ToParamUser
396
+ assert_equal [3], deliveries["3"].map { |d| d["data"]["myEvent"]["int"] }
397
+ # Delivered to Array of GlobalIDUser and ToParamUser
398
+ assert_equal [4], deliveries["4"].map { |d| d["data"]["myEvent"]["int"] }
399
+ end
346
400
  end
347
401
 
348
- def str
349
- raise GraphQL::ExecutionError.new("This is handled")
350
- end
351
- end
402
+ describe "errors" do
403
+ class ErrorPayload
404
+ def int
405
+ raise "Boom!"
406
+ end
352
407
 
353
- it "lets unhandled errors crash "do
354
- query_str = <<-GRAPHQL
408
+ def str
409
+ raise GraphQL::ExecutionError.new("This is handled")
410
+ end
411
+ end
412
+
413
+ it "lets unhandled errors crash "do
414
+ query_str = <<-GRAPHQL
355
415
  subscription($type: PayloadType) {
356
416
  myEvent(type: $type) { int }
357
417
  }
358
- GRAPHQL
359
-
360
- schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
361
- err = assert_raises(RuntimeError) {
362
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, ErrorPayload.new, scope: "1")
363
- }
364
- assert_equal "Boom!", err.message
365
- end
366
- end
418
+ GRAPHQL
419
+
420
+ schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
421
+ err = assert_raises(RuntimeError) {
422
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, ErrorPayload.new, scope: "1")
423
+ }
424
+ assert_equal "Boom!", err.message
425
+ end
426
+ end
367
427
 
368
- it "sends query errors to the subscriptions" do
369
- query_str = <<-GRAPHQL
428
+ it "sends query errors to the subscriptions" do
429
+ query_str = <<-GRAPHQL
370
430
  subscription($type: PayloadType) {
371
431
  myEvent(type: $type) { str }
372
432
  }
373
- GRAPHQL
433
+ GRAPHQL
374
434
 
375
- schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
376
- schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, ErrorPayload.new, scope: "1")
377
- res = deliveries["1"].first
378
- assert_equal "This is handled", res["errors"][0]["message"]
379
- end
380
- end
381
-
382
- describe "implementation" do
383
- it "is initialized with keywords" do
384
- assert_equal 123, schema.subscriptions.extra
385
- end
386
- end
435
+ schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
436
+ schema.subscriptions.trigger("myEvent", { "type" => "ONE" }, ErrorPayload.new, scope: "1")
437
+ res = deliveries["1"].first
438
+ assert_equal "This is handled", res["errors"][0]["message"]
439
+ end
440
+ end
387
441
 
388
- describe "#build_id" do
389
- it "returns a unique ID string" do
390
- assert_instance_of String, schema.subscriptions.build_id
391
- refute_equal schema.subscriptions.build_id, schema.subscriptions.build_id
392
- end
393
- end
442
+ describe "implementation" do
443
+ it "is initialized with keywords" do
444
+ assert_equal 123, schema.subscriptions.extra
445
+ end
446
+ end
394
447
 
395
- describe ".trigger" do
396
- it "raises when event name is not found" do
397
- err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
398
- schema.subscriptions.trigger(:nonsense_field, {}, nil)
448
+ describe "#build_id" do
449
+ it "returns a unique ID string" do
450
+ assert_instance_of String, schema.subscriptions.build_id
451
+ refute_equal schema.subscriptions.build_id, schema.subscriptions.build_id
452
+ end
399
453
  end
400
454
 
401
- assert_includes err.message, "trigger: nonsense_field"
402
- assert_includes err.message, "Subscription.nonsenseField"
403
- end
455
+ describe ".trigger" do
456
+ it "raises when event name is not found" do
457
+ err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
458
+ schema.subscriptions.trigger(:nonsense_field, {}, nil)
459
+ end
404
460
 
405
- it "raises when argument is not found" do
406
- err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
407
- schema.subscriptions.trigger(:event, { scream: {"userId" => "😱"} }, nil)
408
- end
461
+ assert_includes err.message, "trigger: nonsense_field"
462
+ assert_includes err.message, "Subscription.nonsenseField"
463
+ end
409
464
 
410
- assert_includes err.message, "arguments: scream"
411
- assert_includes err.message, "arguments of Subscription.event"
465
+ it "raises when argument is not found" do
466
+ err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
467
+ schema.subscriptions.trigger(:event, { scream: {"userId" => "😱"} }, nil)
468
+ end
412
469
 
413
- err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
414
- schema.subscriptions.trigger(:event, { stream: { user_id_number: "😱"} }, nil)
415
- end
470
+ assert_includes err.message, "arguments: scream"
471
+ assert_includes err.message, "arguments of Subscription.event"
472
+
473
+ err = assert_raises(GraphQL::Subscriptions::InvalidTriggerError) do
474
+ schema.subscriptions.trigger(:event, { stream: { user_id_number: "😱"} }, nil)
475
+ end
416
476
 
417
- assert_includes err.message, "arguments: user_id_number"
418
- assert_includes err.message, "arguments of StreamInput"
477
+ assert_includes err.message, "arguments: user_id_number"
478
+ assert_includes err.message, "arguments of StreamInput"
479
+ end
480
+ end
419
481
  end
420
482
  end
421
483
  end