graphql 1.8.0.pre11 → 1.8.0

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