Skip to content

Commit 1a3b86c

Browse files
committed
Clarify more behaviors with subscription and prepare
1 parent 6697554 commit 1a3b86c

File tree

3 files changed

+81
-40
lines changed

3 files changed

+81
-40
lines changed

lib/graphql/subscriptions.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,10 @@ def normalize_arguments(event_name, arg_owner, args)
228228
# match query arguments.
229229
arg_owner.arguments.each do |name, arg_defn|
230230
if arg_defn.default_value? && !normalized_args.key?(arg_defn.name)
231-
normalized_args[arg_defn.name] = arg_defn.default_value
231+
default_value = arg_defn.default_value
232+
# We don't have an underlying "object" here, so it can't call methods.
233+
# This is broken.
234+
normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: GraphQL::Query::NullContext)
232235
end
233236
end
234237

lib/graphql/subscriptions/subscription_root.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,13 @@ def after_resolve(value:, context:, object:, arguments:, **rest)
3838
elsif (events = context.namespace(:subscriptions)[:events])
3939
# This is the first execution, so gather an Event
4040
# for the backend to register:
41-
events << Subscriptions::Event.new(
41+
event = Subscriptions::Event.new(
4242
name: field.name,
4343
arguments: arguments,
4444
context: context,
4545
field: field,
4646
)
47+
events << event
4748
value
4849
elsif context.query.subscription_topic == Subscriptions::Event.serialize(
4950
field.name,

spec/graphql/subscriptions_spec.rb

Lines changed: 75 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,7 @@ class PayloadType < GraphQL::Schema::Enum
109109

110110
class StreamInput < GraphQL::Schema::InputObject
111111
argument :user_id, ID, required: true, camelize: false
112-
argument :payload_type, PayloadType, required: false, default_value: "ONE", prepare: :downcase
113-
114-
def downcase(value)
115-
value ? value.downcase : value
116-
end
112+
argument :payload_type, PayloadType, required: false, default_value: "ONE", prepare: ->(e, ctx) { e ? e.downcase : e }
117113
end
118114

119115
class EventSubscription < GraphQL::Schema::Subscription
@@ -174,7 +170,7 @@ class FromDefinitionInMemoryBackend < InMemoryBackend
174170
type Subscription {
175171
payload(id: ID!): Payload!
176172
event(stream: StreamInput): Payload
177-
eventSubscription(userId: ID, payloadType: PayloadType): EventSubscriptionPayload
173+
eventSubscription(userId: ID, payloadType: PayloadType = ONE): EventSubscriptionPayload
178174
myEvent(payloadType: PayloadType): Payload
179175
failedEvent(id: ID!): Payload!
180176
}
@@ -409,14 +405,23 @@ def str
409405
# Subscribe with explicit null
410406
schema.execute(query_str, context: { socket: "4" }, variables: { "type" => nil }, root_value: root_object)
411407

408+
# The class-based schema has a "prepare" behavior, so it expects these downcased values in `.trigger`
409+
if schema == ClassBasedInMemoryBackend::Schema
410+
one = "one"
411+
two = "two"
412+
else
413+
one = "ONE"
414+
two = "TWO"
415+
end
416+
412417
# Trigger the subscription with coerceable args, different orders:
413-
schema.subscriptions.trigger("event", { "stream" => {"user_id" => 3, "payloadType" => "ONE"} }, OpenStruct.new(str: "", int: 1))
414-
schema.subscriptions.trigger("event", { "stream" => {"payloadType" => "ONE", "user_id" => "3"} }, OpenStruct.new(str: "", int: 2))
418+
schema.subscriptions.trigger("event", { "stream" => {"user_id" => 3, "payloadType" => one} }, OpenStruct.new(str: "", int: 1))
419+
schema.subscriptions.trigger("event", { "stream" => {"payloadType" => one, "user_id" => "3"} }, OpenStruct.new(str: "", int: 2))
415420
# This is a non-trigger
416-
schema.subscriptions.trigger("event", { "stream" => {"user_id" => "3", "payloadType" => "TWO"} }, OpenStruct.new(str: "", int: 3))
421+
schema.subscriptions.trigger("event", { "stream" => {"user_id" => "3", "payloadType" => two} }, OpenStruct.new(str: "", int: 3))
417422
# These get default value of ONE (underscored / symbols are ok)
418423
schema.subscriptions.trigger("event", { stream: { user_id: "3"} }, OpenStruct.new(str: "", int: 4))
419-
# Trigger with null updates subscriptionss to null
424+
# Trigger with null updates subscriptions to null
420425
schema.subscriptions.trigger("event", { "stream" => {"user_id" => 3, "payloadType" => nil} }, OpenStruct.new(str: "", int: 5))
421426

422427
assert_equal [1,2,4], deliveries["1"].map { |d| d["data"]["e1"]["int"] }
@@ -499,29 +504,49 @@ def str
499504
eventSubscription(userId: "4", payloadType: ONE) { payload { int } }
500505
}
501506
GRAPHQL
507+
508+
query_str_3 = <<-GRAPHQL
509+
subscription {
510+
eventSubscription(userId: "4") { payload { int } }
511+
}
512+
GRAPHQL
502513
# Value from variable
503514
schema.execute(query_str, context: { socket: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
504515
# Default value for variable
505516
schema.execute(query_str, context: { socket: "1" }, root_value: root_object)
506517
# Query string literal value
507518
schema.execute(query_str_2, context: { socket: "1" }, root_value: root_object)
519+
# Schema default value
520+
schema.execute(query_str_3, context: { socket: "1" }, root_value: root_object)
508521

509522
# There's no way to add `prepare:` when using SDL, so only the Ruby-defined schema has it
510-
expected_keys = if schema == ClassBasedInMemoryBackend::Schema && TESTING_INTERPRETER
511-
# Unfortunately, on the non-interpreter runtime, `prepare:` was _not_ applied here.
512-
[
513-
":eventSubscription:payloadType:one:userId:3",
514-
":eventSubscription:payloadType:one:userId:4",
515-
":eventSubscription:payloadType:two:userId:3",
516-
]
523+
expected_sub_count = if schema == ClassBasedInMemoryBackend::Schema
524+
if TESTING_INTERPRETER
525+
{
526+
":eventSubscription:payloadType:one:userId:3" => 1,
527+
":eventSubscription:payloadType:one:userId:4" => 2,
528+
":eventSubscription:payloadType:two:userId:3" => 1,
529+
}
530+
else
531+
# Unfortunately, on the non-interpreter runtime, `prepare:` was _not_ applied here,
532+
{
533+
":eventSubscription:payloadType:ONE:userId:3" => 1,
534+
":eventSubscription:payloadType:ONE:userId:4" => 2,
535+
":eventSubscription:payloadType:TWO:userId:3" => 1,
536+
}
537+
end
517538
else
518-
[
519-
":eventSubscription:payloadType:ONE:userId:3",
520-
":eventSubscription:payloadType:ONE:userId:4",
521-
":eventSubscription:payloadType:TWO:userId:3",
522-
]
539+
{
540+
":eventSubscription:payloadType:ONE:userId:3" => 1,
541+
":eventSubscription:payloadType:ONE:userId:4" => 2,
542+
":eventSubscription:payloadType:TWO:userId:3" => 1,
543+
}
544+
end
545+
count_by_topic = Hash.new(0)
546+
active_subscriptions.each do |k, v|
547+
count_by_topic[k] += v.size
523548
end
524-
assert_equal expected_keys, active_subscriptions.keys.sort
549+
assert_equal expected_sub_count, count_by_topic
525550
end
526551

527552
it "doesn't apply for plain fields" do
@@ -536,29 +561,41 @@ def str
536561
event(stream: { user_id: "4", payloadType: ONE}) { int }
537562
}
538563
GRAPHQL
564+
565+
query_str_3 = <<-GRAPHQL
566+
subscription {
567+
event(stream: { user_id: "4" }) { int }
568+
}
569+
GRAPHQL
539570
# Value from variable
540571
schema.execute(query_str, context: { socket: "1" }, variables: { "type" => "ONE" }, root_value: root_object)
541572
# Default value for variable
542573
schema.execute(query_str, context: { socket: "1" }, root_value: root_object)
543574
# Query string literal value
544575
schema.execute(query_str_2, context: { socket: "1" }, root_value: root_object)
576+
# Schema default value
577+
schema.execute(query_str_3, context: { socket: "1" }, root_value: root_object)
545578

546579

547580
# There's no way to add `prepare:` when using SDL, so only the Ruby-defined schema has it
548-
expected_keys = if schema == ClassBasedInMemoryBackend::Schema
549-
[
550-
":event:stream:payloadType:one:user_id:3",
551-
":event:stream:payloadType:one:user_id:4",
552-
":event:stream:payloadType:two:user_id:3",
553-
]
581+
expected_sub_count = if schema == ClassBasedInMemoryBackend::Schema
582+
{
583+
":event:stream:payloadType:one:user_id:3" => 1,
584+
":event:stream:payloadType:two:user_id:3" => 1,
585+
":event:stream:payloadType:one:user_id:4" => 2,
586+
}
554587
else
555-
[
556-
":event:stream:payloadType:ONE:user_id:3",
557-
":event:stream:payloadType:ONE:user_id:4",
558-
":event:stream:payloadType:TWO:user_id:3",
559-
]
588+
{
589+
":event:stream:payloadType:ONE:user_id:3" => 1,
590+
":event:stream:payloadType:TWO:user_id:3" => 1,
591+
":event:stream:payloadType:ONE:user_id:4" => 2,
592+
}
593+
end
594+
count_by_topic = Hash.new(0)
595+
active_subscriptions.each do |k, v|
596+
count_by_topic[k] += v.size
560597
end
561-
assert_equal expected_keys, active_subscriptions.keys.sort
598+
assert_equal expected_sub_count, count_by_topic
562599
end
563600
end
564601

@@ -592,9 +629,9 @@ def str
592629

593630
it "sends query errors to the subscriptions" do
594631
query_str = <<-GRAPHQL
595-
subscription($type: PayloadType) {
596-
myEvent(payloadType: $type) { str }
597-
}
632+
subscription($type: PayloadType) {
633+
myEvent(payloadType: $type) { str }
634+
}
598635
GRAPHQL
599636

600637
schema.execute(query_str, context: { socket: "1", me: "1" }, variables: { "type" => "ONE" }, root_value: root_object)

0 commit comments

Comments
 (0)