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