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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/templates/schema.erb +1 -1
- data/lib/graphql/function.rb +2 -0
- data/lib/graphql/railtie.rb +1 -1
- data/lib/graphql/schema.rb +1 -0
- data/lib/graphql/schema/argument.rb +3 -2
- data/lib/graphql/schema/build_from_definition.rb +1 -1
- data/lib/graphql/schema/field.rb +96 -49
- data/lib/graphql/schema/interface.rb +21 -3
- data/lib/graphql/schema/list.rb +4 -0
- data/lib/graphql/schema/member/accepts_definition.rb +2 -2
- data/lib/graphql/schema/member/base_dsl_methods.rb +4 -0
- data/lib/graphql/schema/member/build_type.rb +4 -2
- data/lib/graphql/schema/member/has_fields.rb +1 -8
- data/lib/graphql/schema/mutation.rb +19 -88
- data/lib/graphql/schema/non_null.rb +4 -0
- data/lib/graphql/schema/object.rb +1 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +14 -15
- data/lib/graphql/schema/resolver.rb +122 -0
- data/lib/graphql/subscriptions/instrumentation.rb +5 -1
- data/lib/graphql/subscriptions/serialize.rb +2 -0
- data/lib/graphql/tracing/new_relic_tracing.rb +26 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/generators/graphql/install_generator_spec.rb +1 -1
- data/spec/graphql/relay/mutation_spec.rb +5 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +1 -1
- data/spec/graphql/schema/field_spec.rb +7 -24
- data/spec/graphql/schema/interface_spec.rb +25 -0
- data/spec/graphql/schema/member/accepts_definition_spec.rb +22 -0
- data/spec/graphql/schema/member/build_type_spec.rb +17 -0
- data/spec/graphql/schema/mutation_spec.rb +15 -14
- data/spec/graphql/schema/resolver_spec.rb +131 -0
- data/spec/graphql/subscriptions_spec.rb +267 -205
- data/spec/graphql/tracing/new_relic_tracing_spec.rb +47 -0
- data/spec/support/jazz.rb +6 -1
- data/spec/support/new_relic.rb +24 -0
- data/spec/support/star_trek/schema.rb +2 -2
- data/spec/support/star_wars/schema.rb +1 -2
- 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.
|
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 "
|
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.
|
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.
|
125
|
-
assert nullable_mutation_class.
|
126
|
-
refute non_nullable_mutation_class.
|
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.
|
144
|
-
assert override_mutation.
|
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
|
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
|
-
|
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
|
-
|
166
|
-
|
167
|
-
|
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
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
274
|
+
GRAPHQL
|
215
275
|
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
288
|
+
GRAPHQL
|
229
289
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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
|
-
|
236
|
-
|
295
|
+
it "pushes errors" do
|
296
|
+
query_str = <<-GRAPHQL
|
237
297
|
subscription ($id: ID!){
|
238
298
|
payload(id: $id) { str, int }
|
239
299
|
}
|
240
|
-
|
300
|
+
GRAPHQL
|
241
301
|
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
-
|
250
|
-
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
-
|
288
|
-
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
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
|
-
|
312
|
-
|
313
|
-
|
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
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
402
|
+
describe "errors" do
|
403
|
+
class ErrorPayload
|
404
|
+
def int
|
405
|
+
raise "Boom!"
|
406
|
+
end
|
352
407
|
|
353
|
-
|
354
|
-
|
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
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
369
|
-
|
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
|
-
|
433
|
+
GRAPHQL
|
374
434
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
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
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
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
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
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
|
-
|
402
|
-
|
403
|
-
|
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
|
-
|
406
|
-
|
407
|
-
|
408
|
-
end
|
461
|
+
assert_includes err.message, "trigger: nonsense_field"
|
462
|
+
assert_includes err.message, "Subscription.nonsenseField"
|
463
|
+
end
|
409
464
|
|
410
|
-
|
411
|
-
|
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
|
-
|
414
|
-
|
415
|
-
|
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
|
-
|
418
|
-
|
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
|