graphql 1.9.6 → 1.9.7
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 +5 -5
- data/lib/generators/graphql/install_generator.rb +2 -1
- data/lib/generators/graphql/templates/base_field.erb +7 -0
- data/lib/graphql/analysis/ast/visitor.rb +5 -2
- data/lib/graphql/execution/multiplex.rb +5 -1
- data/lib/graphql/query.rb +6 -1
- data/lib/graphql/relay/array_connection.rb +1 -1
- data/lib/graphql/schema.rb +15 -1
- data/lib/graphql/schema/argument.rb +5 -1
- data/lib/graphql/schema/input_object.rb +21 -12
- data/lib/graphql/schema/introspection_system.rb +6 -1
- data/lib/graphql/schema/member/has_arguments.rb +4 -2
- data/lib/graphql/schema/resolver.rb +8 -3
- data/lib/graphql/schema/subscription.rb +22 -0
- data/lib/graphql/schema/timeout.rb +109 -0
- data/lib/graphql/types/relay/base_edge.rb +0 -3
- data/lib/graphql/upgrader/member.rb +148 -111
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/fixtures/upgrader/mutation.original.rb +28 -0
- data/spec/fixtures/upgrader/mutation.transformed.rb +28 -0
- data/spec/graphql/analysis/ast_spec.rb +27 -0
- data/spec/graphql/execution/instrumentation_spec.rb +34 -6
- data/spec/graphql/execution/multiplex_spec.rb +11 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +6 -1
- data/spec/graphql/schema/input_object_spec.rb +56 -7
- data/spec/graphql/schema/introspection_system_spec.rb +24 -0
- data/spec/graphql/schema/subscription_spec.rb +65 -0
- data/spec/graphql/schema/timeout_spec.rb +206 -0
- data/spec/integration/mongoid/star_trek/schema.rb +1 -2
- data/spec/integration/rails/graphql/input_object_spec.rb +19 -0
- data/spec/integration/rails/graphql/relay/array_connection_spec.rb +47 -28
- data/spec/integration/rails/graphql/schema_spec.rb +18 -0
- data/spec/integration/tmp/app/graphql/types/date_type.rb +14 -0
- data/spec/integration/tmp/dummy/Gemfile +50 -0
- data/spec/integration/tmp/dummy/README.md +24 -0
- data/spec/integration/tmp/dummy/Rakefile +6 -0
- data/spec/integration/tmp/dummy/app/assets/config/manifest.js +3 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/application.js +16 -0
- data/spec/integration/tmp/dummy/app/assets/javascripts/cable.js +13 -0
- data/spec/integration/tmp/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/channel.rb +5 -0
- data/spec/integration/tmp/dummy/app/channels/application_cable/connection.rb +5 -0
- data/spec/integration/tmp/dummy/app/controllers/application_controller.rb +4 -0
- data/spec/integration/tmp/dummy/app/controllers/graphql_controller.rb +44 -0
- data/spec/integration/tmp/dummy/app/helpers/application_helper.rb +3 -0
- data/spec/integration/tmp/dummy/app/jobs/application_job.rb +3 -0
- data/spec/integration/tmp/dummy/app/mailers/application_mailer.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/dummy_schema.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/mutations/update_name.rb +15 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_enum.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_input_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_interface.rb +6 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_object.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_scalar.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/base_union.rb +5 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/mutation_type.rb +12 -0
- data/spec/integration/tmp/dummy/app/mydirectory/types/query_type.rb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/integration/tmp/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/integration/tmp/dummy/bin/bundle +3 -0
- data/spec/integration/tmp/dummy/bin/rails +4 -0
- data/spec/integration/tmp/dummy/bin/rake +4 -0
- data/spec/integration/tmp/dummy/bin/setup +34 -0
- data/spec/integration/tmp/dummy/bin/update +29 -0
- data/spec/integration/tmp/dummy/config.ru +5 -0
- data/spec/integration/tmp/dummy/config/application.rb +26 -0
- data/spec/integration/tmp/dummy/config/boot.rb +4 -0
- data/spec/integration/tmp/dummy/config/cable.yml +9 -0
- data/spec/integration/tmp/dummy/config/environment.rb +6 -0
- data/spec/integration/tmp/dummy/config/environments/development.rb +52 -0
- data/spec/integration/tmp/dummy/config/environments/production.rb +84 -0
- data/spec/integration/tmp/dummy/config/environments/test.rb +43 -0
- data/spec/integration/tmp/dummy/config/initializers/application_controller_renderer.rb +9 -0
- data/spec/integration/tmp/dummy/config/initializers/assets.rb +12 -0
- data/spec/integration/tmp/dummy/config/initializers/backtrace_silencers.rb +8 -0
- data/spec/integration/tmp/dummy/config/initializers/cookies_serializer.rb +6 -0
- data/spec/integration/tmp/dummy/config/initializers/filter_parameter_logging.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/inflections.rb +17 -0
- data/spec/integration/tmp/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/integration/tmp/dummy/config/initializers/new_framework_defaults.rb +24 -0
- data/spec/integration/tmp/dummy/config/initializers/session_store.rb +4 -0
- data/spec/integration/tmp/dummy/config/initializers/wrap_parameters.rb +10 -0
- data/spec/integration/tmp/dummy/config/locales/en.yml +23 -0
- data/spec/integration/tmp/dummy/config/puma.rb +48 -0
- data/spec/integration/tmp/dummy/config/routes.rb +9 -0
- data/spec/integration/tmp/dummy/config/secrets.yml +22 -0
- data/spec/integration/tmp/dummy/db/seeds.rb +8 -0
- data/spec/integration/tmp/dummy/log/test.log +0 -0
- data/spec/integration/tmp/dummy/public/404.html +67 -0
- data/spec/integration/tmp/dummy/public/422.html +67 -0
- data/spec/integration/tmp/dummy/public/500.html +66 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/integration/tmp/dummy/public/apple-touch-icon.png +0 -0
- data/spec/integration/tmp/dummy/public/favicon.ico +0 -0
- data/spec/integration/tmp/dummy/public/robots.txt +5 -0
- data/spec/integration/tmp/dummy/test/test_helper.rb +8 -0
- data/spec/support/jazz.rb +6 -0
- data/spec/support/star_wars/schema.rb +1 -2
- metadata +171 -6
- data/spec/integration/tmp/app/graphql/types/bird_type.rb +0 -7
|
@@ -62,23 +62,45 @@ describe GraphQL::Schema do
|
|
|
62
62
|
end
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
+
# This is how you might add queries from a persisted query backend
|
|
66
|
+
|
|
67
|
+
class QueryStringInstrumenter
|
|
68
|
+
def before_query(query)
|
|
69
|
+
if query.context[:extra_query_string] && query.query_string.nil?
|
|
70
|
+
query.query_string = query.context[:extra_query_string]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def after_query(query)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
65
78
|
let(:query_type) {
|
|
66
|
-
GraphQL::
|
|
67
|
-
|
|
68
|
-
field :int,
|
|
69
|
-
argument :value,
|
|
70
|
-
|
|
79
|
+
Class.new(GraphQL::Schema::Object) do
|
|
80
|
+
graphql_name "Query"
|
|
81
|
+
field :int, Integer, null: true do
|
|
82
|
+
argument :value, Integer, required: false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def int(value:)
|
|
86
|
+
value
|
|
71
87
|
end
|
|
72
88
|
end
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
let(:schema) {
|
|
76
92
|
spec = self
|
|
77
|
-
GraphQL::Schema
|
|
93
|
+
Class.new(GraphQL::Schema) do
|
|
78
94
|
query(spec.query_type)
|
|
79
95
|
instrument(:query, FirstInstrumenter.new)
|
|
80
96
|
instrument(:query, SecondInstrumenter.new)
|
|
81
97
|
instrument(:query, ExecutionErrorInstrumenter.new)
|
|
98
|
+
instrument(:query, QueryStringInstrumenter.new)
|
|
99
|
+
|
|
100
|
+
if TESTING_INTERPRETER
|
|
101
|
+
use GraphQL::Analysis::AST
|
|
102
|
+
use GraphQL::Execution::Interpreter
|
|
103
|
+
end
|
|
82
104
|
end
|
|
83
105
|
}
|
|
84
106
|
|
|
@@ -114,6 +136,12 @@ describe GraphQL::Schema do
|
|
|
114
136
|
assert_equal "Raised from instrumenter before_query", res["errors"].first["message"]
|
|
115
137
|
refute res.key?("data"), "The query doesn't run"
|
|
116
138
|
end
|
|
139
|
+
|
|
140
|
+
it "can assign a query string there" do
|
|
141
|
+
context = { extra_query_string: "{ __typename }"}
|
|
142
|
+
res = schema.execute(nil, context: context)
|
|
143
|
+
assert_equal "Query", res["data"]["__typename"]
|
|
144
|
+
end
|
|
117
145
|
end
|
|
118
146
|
|
|
119
147
|
describe "within a multiplex" do
|
|
@@ -134,6 +134,17 @@ describe GraphQL::Execution::Multiplex do
|
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
describe "max_complexity" do
|
|
138
|
+
it "can successfully calculate complexity" do
|
|
139
|
+
message = "Query has complexity of 11, which exceeds max complexity of 10"
|
|
140
|
+
results = multiplex(queries, max_complexity: 10)
|
|
141
|
+
|
|
142
|
+
results.each do |res|
|
|
143
|
+
assert_equal message, res["errors"][0]["message"]
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
137
148
|
describe "after_query when errors are raised" do
|
|
138
149
|
class InspectQueryInstrumentation
|
|
139
150
|
class << self
|
|
@@ -125,7 +125,12 @@ describe GraphQL::InternalRepresentation::Rewrite do
|
|
|
125
125
|
nut_selections = plant_selection.typed_children[schema.types["Nut"]]
|
|
126
126
|
# `... on Tree`, `... on Nut`, and `NutFields`, but not `... on Fruit { ... on Tree }`
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
if RUBY_VERSION < "2.5"
|
|
129
|
+
# Ruby 2.5 changed how hash key collisions worked a little bit.
|
|
130
|
+
# This test is "broken", but honestly, it doesn't matter.
|
|
131
|
+
# Apparently nobody uses `IrepNode#ast_nodes`, and that's for the best.
|
|
132
|
+
assert_equal 3, nut_selections["leafType"].ast_nodes.size
|
|
133
|
+
end
|
|
129
134
|
|
|
130
135
|
# Multi-level merging when including fragments:
|
|
131
136
|
habitats_selections = nut_selections["habitats"].typed_children[schema.types["Habitat"]]
|
|
@@ -40,7 +40,7 @@ describe GraphQL::Schema::InputObject do
|
|
|
40
40
|
ensemble_class = Class.new(subclass) do
|
|
41
41
|
argument :ensemble_id, GraphQL::Types::ID, required: false, loads: Jazz::Ensemble
|
|
42
42
|
end
|
|
43
|
-
|
|
43
|
+
|
|
44
44
|
assert_equal 3, subclass.arguments.size
|
|
45
45
|
assert_equal ["arg1", "arg2", "arg3"], subclass.arguments.keys
|
|
46
46
|
assert_equal ["String!", "Int!", "Int!"], subclass.arguments.values.map { |a| a.type.to_type_signature }
|
|
@@ -87,8 +87,32 @@ describe GraphQL::Schema::InputObject do
|
|
|
87
87
|
end
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
+
class Mutation < GraphQL::Schema::Object
|
|
91
|
+
class TouchInstrument < GraphQL::Schema::Mutation
|
|
92
|
+
class InstrumentInput < GraphQL::Schema::InputObject
|
|
93
|
+
argument :instrument_id, ID, required: true, loads: Jazz::InstrumentType
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
argument :input_obj, InstrumentInput, required: true
|
|
97
|
+
field :instrument_name_method, String, null: false
|
|
98
|
+
field :instrument_name_key, String, null: false
|
|
99
|
+
|
|
100
|
+
def resolve(input_obj:)
|
|
101
|
+
# Make sure both kinds of access work the same:
|
|
102
|
+
{
|
|
103
|
+
instrument_name_method: input_obj.instrument.name,
|
|
104
|
+
instrument_name_key: input_obj[:instrument].name,
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
field :touch_instrument, mutation: TouchInstrument
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
|
|
90
113
|
class Schema < GraphQL::Schema
|
|
91
114
|
query(Query)
|
|
115
|
+
mutation(Mutation)
|
|
92
116
|
if TESTING_INTERPRETER
|
|
93
117
|
use GraphQL::Execution::Interpreter
|
|
94
118
|
end
|
|
@@ -109,7 +133,7 @@ describe GraphQL::Schema::InputObject do
|
|
|
109
133
|
GRAPHQL
|
|
110
134
|
|
|
111
135
|
res = InputObjectPrepareTest::Schema.execute(query_str, context: { multiply_by: 3 })
|
|
112
|
-
expected_obj = [{ a: 1, b2: 2, c: 9, d2: 12, e2: 30, instrument: "
|
|
136
|
+
expected_obj = [{ a: 1, b2: 2, c: 9, d2: 12, e2: 30, instrument: Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION") }.inspect, "Drum Kit"]
|
|
113
137
|
assert_equal expected_obj, res["data"]["inputs"]
|
|
114
138
|
end
|
|
115
139
|
|
|
@@ -127,6 +151,21 @@ describe GraphQL::Schema::InputObject do
|
|
|
127
151
|
assert_equal("boom!", res["errors"][0]["extensions"]["problems"][0]["explanation"])
|
|
128
152
|
assert_equal(input, res["errors"][0]["extensions"]["value"])
|
|
129
153
|
end
|
|
154
|
+
|
|
155
|
+
it "loads input object arguments" do
|
|
156
|
+
query_str = <<-GRAPHQL
|
|
157
|
+
mutation {
|
|
158
|
+
touchInstrument(inputObj: { instrumentId: "Instrument/Drum Kit" }) {
|
|
159
|
+
instrumentNameMethod
|
|
160
|
+
instrumentNameKey
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
GRAPHQL
|
|
164
|
+
|
|
165
|
+
res = InputObjectPrepareTest::Schema.execute(query_str)
|
|
166
|
+
assert_equal "Drum Kit", res["data"]["touchInstrument"]["instrumentNameMethod"]
|
|
167
|
+
assert_equal "Drum Kit", res["data"]["touchInstrument"]["instrumentNameKey"]
|
|
168
|
+
end
|
|
130
169
|
end
|
|
131
170
|
|
|
132
171
|
describe "loading application object(s)" do
|
|
@@ -238,7 +277,7 @@ describe GraphQL::Schema::InputObject do
|
|
|
238
277
|
end
|
|
239
278
|
end
|
|
240
279
|
|
|
241
|
-
describe
|
|
280
|
+
describe 'hash conversion behavior' do
|
|
242
281
|
module InputObjectToHTest
|
|
243
282
|
class TestInput1 < GraphQL::Schema::InputObject
|
|
244
283
|
graphql_name "TestInput1"
|
|
@@ -258,16 +297,26 @@ describe GraphQL::Schema::InputObject do
|
|
|
258
297
|
TestInput2.to_graphql
|
|
259
298
|
end
|
|
260
299
|
|
|
261
|
-
|
|
300
|
+
before do
|
|
262
301
|
arg_values = {a: 1, b: 2, c: { d: 3, e: 4, instrumentId: "Instrument/Drum Kit"}}
|
|
263
302
|
|
|
264
|
-
input_object = InputObjectToHTest::TestInput2.new(
|
|
303
|
+
@input_object = InputObjectToHTest::TestInput2.new(
|
|
265
304
|
arg_values,
|
|
266
|
-
context:
|
|
305
|
+
context: OpenStruct.new(schema: Jazz::Schema),
|
|
267
306
|
defaults_used: Set.new
|
|
268
307
|
)
|
|
308
|
+
end
|
|
269
309
|
|
|
270
|
-
|
|
310
|
+
describe "#to_h" do
|
|
311
|
+
it "returns a symbolized, aliased, ruby keyword style hash" do
|
|
312
|
+
assert_equal({ a: 1, b: 2, input_object: { d: 3, e: 4, instrument: Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION") } }, @input_object.to_h)
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
describe "#to_hash" do
|
|
317
|
+
it "returns the same results as #to_h (aliased)" do
|
|
318
|
+
assert_equal(@input_object.to_h, @input_object.to_hash)
|
|
319
|
+
end
|
|
271
320
|
end
|
|
272
321
|
end
|
|
273
322
|
|
|
@@ -53,4 +53,28 @@ describe GraphQL::Schema::IntrospectionSystem do
|
|
|
53
53
|
assert_equal [], ensembles_field["args"]
|
|
54
54
|
end
|
|
55
55
|
end
|
|
56
|
+
|
|
57
|
+
describe "#disable_introspection_entry_points" do
|
|
58
|
+
let(:schema) { Jazz::Schema }
|
|
59
|
+
|
|
60
|
+
it "allows entry point introspection by default" do
|
|
61
|
+
res = schema.execute("{ __schema { types { name } } }")
|
|
62
|
+
assert res
|
|
63
|
+
|
|
64
|
+
types = res["data"]["__schema"]["types"]
|
|
65
|
+
refute_empty types
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe "when entry points introspection is disabled" do
|
|
69
|
+
let(:schema) { Jazz::SchemaWithoutIntrospection }
|
|
70
|
+
|
|
71
|
+
it "returns error" do
|
|
72
|
+
res = schema.execute("{ __schema { types { name } } }")
|
|
73
|
+
assert res
|
|
74
|
+
|
|
75
|
+
assert_nil res["data"]
|
|
76
|
+
assert_equal ["Field '__schema' doesn't exist on type 'Query'"], res["errors"].map { |e| e["message"] }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
56
80
|
end
|
|
@@ -68,6 +68,12 @@ describe GraphQL::Schema::Subscription do
|
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
|
|
71
|
+
class DirectTootWasTooted < BaseSubscription
|
|
72
|
+
subscription_scope :viewer
|
|
73
|
+
field :toot, Toot, null: false
|
|
74
|
+
field :user, User, null: false
|
|
75
|
+
end
|
|
76
|
+
|
|
71
77
|
# Test initial response, which returns all users
|
|
72
78
|
class UsersJoined < BaseSubscription
|
|
73
79
|
class UsersJoinedManualPayload < GraphQL::Schema::Object
|
|
@@ -97,6 +103,7 @@ describe GraphQL::Schema::Subscription do
|
|
|
97
103
|
class Subscription < GraphQL::Schema::Object
|
|
98
104
|
extend GraphQL::Subscriptions::SubscriptionRoot
|
|
99
105
|
field :toot_was_tooted, subscription: TootWasTooted
|
|
106
|
+
field :direct_toot_was_tooted, subscription: DirectTootWasTooted
|
|
100
107
|
field :users_joined, subscription: UsersJoined
|
|
101
108
|
field :new_users_joined, subscription: NewUsersJoined
|
|
102
109
|
end
|
|
@@ -437,4 +444,62 @@ describe GraphQL::Schema::Subscription do
|
|
|
437
444
|
assert_equal 1, in_memory_subscription_count
|
|
438
445
|
end
|
|
439
446
|
end
|
|
447
|
+
|
|
448
|
+
describe "`subscription_scope` method" do
|
|
449
|
+
it "provdes a subscription scope that is recognized in the schema" do
|
|
450
|
+
scoped_subscription = SubscriptionFieldSchema::get_field("Subscription", "directTootWasTooted")
|
|
451
|
+
|
|
452
|
+
assert_equal :viewer, scoped_subscription.subscription_scope
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
it "provides a subscription scope that is used in execution" do
|
|
456
|
+
res = exec_query <<-GRAPHQL, context: { viewer: :me }
|
|
457
|
+
subscription {
|
|
458
|
+
directTootWasTooted {
|
|
459
|
+
toot { body }
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
GRAPHQL
|
|
463
|
+
assert_equal 1, in_memory_subscription_count
|
|
464
|
+
|
|
465
|
+
# Only the subscription with scope :me should be in the mailbox
|
|
466
|
+
obj = OpenStruct.new(toot: { body: "Hello from matz!" }, user: SubscriptionFieldSchema::USERS["matz"])
|
|
467
|
+
SubscriptionFieldSchema.subscriptions.trigger(:direct_toot_was_tooted, {}, obj, scope: :me)
|
|
468
|
+
SubscriptionFieldSchema.subscriptions.trigger(:direct_toot_was_tooted, {}, obj, scope: :not_me)
|
|
469
|
+
mailbox = res.context[:subscription_mailbox]
|
|
470
|
+
|
|
471
|
+
assert_equal 1, mailbox.length
|
|
472
|
+
|
|
473
|
+
expected_response = {
|
|
474
|
+
"data" => {
|
|
475
|
+
"directTootWasTooted" => {
|
|
476
|
+
"toot" => {
|
|
477
|
+
"body" => "Hello from matz!"
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
assert_equal expected_response, mailbox.first
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
it "allows for proper inheritance of the class's configuration in subclasses" do
|
|
487
|
+
# Make a subclass without an explicit configuration
|
|
488
|
+
class DirectTootSubclass < SubscriptionFieldSchema::DirectTootWasTooted
|
|
489
|
+
end
|
|
490
|
+
# Then check if the field options got the inherited value
|
|
491
|
+
direct_toot_options = DirectTootSubclass.field_options
|
|
492
|
+
assert_equal :viewer, direct_toot_options[:subscription_scope]
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
it "allows for setting the subscription scope value to nil" do
|
|
496
|
+
class PrivateSubscription < SubscriptionFieldSchema::BaseSubscription
|
|
497
|
+
subscription_scope :private
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
PrivateSubscription.subscription_scope nil
|
|
501
|
+
|
|
502
|
+
assert_nil PrivateSubscription.subscription_scope
|
|
503
|
+
end
|
|
504
|
+
end
|
|
440
505
|
end
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "spec_helper"
|
|
3
|
+
|
|
4
|
+
describe GraphQL::Schema::Timeout do
|
|
5
|
+
let(:max_seconds) { 1 }
|
|
6
|
+
let(:timeout_class) { GraphQL::Schema::Timeout }
|
|
7
|
+
let(:timeout_schema) {
|
|
8
|
+
nested_sleep_type = Class.new(GraphQL::Schema::Object) do
|
|
9
|
+
graphql_name "NestedSleep"
|
|
10
|
+
|
|
11
|
+
field :seconds, Float, null: true
|
|
12
|
+
|
|
13
|
+
def seconds
|
|
14
|
+
object
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
field :nested_sleep, GraphQL::Schema::LateBoundType.new(graphql_name), null: true do
|
|
18
|
+
argument :seconds, Float, required: true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def nested_sleep(seconds:)
|
|
22
|
+
sleep(seconds)
|
|
23
|
+
seconds
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
query_type = Class.new(GraphQL::Schema::Object) do
|
|
28
|
+
graphql_name "Query"
|
|
29
|
+
|
|
30
|
+
field :sleep_for, Float, null: true do
|
|
31
|
+
argument :seconds, Float, required: true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def sleep_for(seconds:)
|
|
35
|
+
sleep(seconds)
|
|
36
|
+
seconds
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
field :nested_sleep, nested_sleep_type, null: true do
|
|
40
|
+
argument :seconds, Float, required: true
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def nested_sleep(seconds:)
|
|
44
|
+
sleep(seconds)
|
|
45
|
+
seconds
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
schema = Class.new(GraphQL::Schema) do
|
|
50
|
+
query query_type
|
|
51
|
+
if TESTING_INTERPRETER
|
|
52
|
+
use GraphQL::Execution::Interpreter
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
schema.use timeout_class, max_seconds: max_seconds
|
|
56
|
+
schema
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let(:result) { timeout_schema.execute(query_string) }
|
|
60
|
+
|
|
61
|
+
describe "timeout part-way through" do
|
|
62
|
+
let(:query_string) {%|
|
|
63
|
+
{
|
|
64
|
+
a: sleepFor(seconds: 0.4)
|
|
65
|
+
b: sleepFor(seconds: 0.4)
|
|
66
|
+
c: sleepFor(seconds: 0.4)
|
|
67
|
+
d: sleepFor(seconds: 0.4)
|
|
68
|
+
e: sleepFor(seconds: 0.4)
|
|
69
|
+
}
|
|
70
|
+
|}
|
|
71
|
+
it "returns a partial response and error messages" do
|
|
72
|
+
expected_data = {
|
|
73
|
+
"a"=>0.4,
|
|
74
|
+
"b"=>0.4,
|
|
75
|
+
"c"=>0.4,
|
|
76
|
+
"d"=>nil,
|
|
77
|
+
"e"=>nil,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
expected_errors = [
|
|
81
|
+
{
|
|
82
|
+
"message"=>"Timeout on Query.sleepFor",
|
|
83
|
+
"locations"=>[{"line"=>6, "column"=>9}],
|
|
84
|
+
"path"=>["d"]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"message"=>"Timeout on Query.sleepFor",
|
|
88
|
+
"locations"=>[{"line"=>7, "column"=>9}],
|
|
89
|
+
"path"=>["e"]
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
assert_equal expected_data, result["data"]
|
|
93
|
+
assert_equal expected_errors, result["errors"]
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
describe "timeout in nested fields" do
|
|
98
|
+
let(:query_string) {%|
|
|
99
|
+
{
|
|
100
|
+
a: nestedSleep(seconds: 0.3) {
|
|
101
|
+
seconds
|
|
102
|
+
b: nestedSleep(seconds: 0.3) {
|
|
103
|
+
seconds
|
|
104
|
+
c: nestedSleep(seconds: 0.3) {
|
|
105
|
+
seconds
|
|
106
|
+
d: nestedSleep(seconds: 0.4) {
|
|
107
|
+
seconds
|
|
108
|
+
e: nestedSleep(seconds: 0.4) {
|
|
109
|
+
seconds
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|}
|
|
117
|
+
|
|
118
|
+
it "returns a partial response and error messages" do
|
|
119
|
+
expected_data = {
|
|
120
|
+
"a" => {
|
|
121
|
+
"seconds" => 0.3,
|
|
122
|
+
"b" => {
|
|
123
|
+
"seconds" => 0.3,
|
|
124
|
+
"c" => {
|
|
125
|
+
"seconds"=>0.3,
|
|
126
|
+
"d" => {
|
|
127
|
+
"seconds"=>nil,
|
|
128
|
+
"e"=>nil
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
expected_errors = [
|
|
135
|
+
{
|
|
136
|
+
"message"=>"Timeout on NestedSleep.seconds",
|
|
137
|
+
"locations"=>[{"line"=>10, "column"=>15}],
|
|
138
|
+
"path"=>["a", "b", "c", "d", "seconds"]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"message"=>"Timeout on NestedSleep.nestedSleep",
|
|
142
|
+
"locations"=>[{"line"=>11, "column"=>15}],
|
|
143
|
+
"path"=>["a", "b", "c", "d", "e"]
|
|
144
|
+
},
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
assert_equal expected_data, result["data"]
|
|
148
|
+
assert_equal expected_errors, result["errors"]
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
describe "long-running fields" do
|
|
153
|
+
let(:query_string) {%|
|
|
154
|
+
{
|
|
155
|
+
a: sleepFor(seconds: 0.2)
|
|
156
|
+
b: sleepFor(seconds: 0.2)
|
|
157
|
+
c: sleepFor(seconds: 0.8)
|
|
158
|
+
d: sleepFor(seconds: 0.1)
|
|
159
|
+
}
|
|
160
|
+
|}
|
|
161
|
+
it "doesn't terminate long-running field execution" do
|
|
162
|
+
expected_data = {
|
|
163
|
+
"a"=>0.2,
|
|
164
|
+
"b"=>0.2,
|
|
165
|
+
"c"=>0.8,
|
|
166
|
+
"d"=>nil,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
expected_errors = [
|
|
170
|
+
{
|
|
171
|
+
"message"=>"Timeout on Query.sleepFor",
|
|
172
|
+
"locations"=>[{"line"=>6, "column"=>9}],
|
|
173
|
+
"path"=>["d"]
|
|
174
|
+
},
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
assert_equal expected_data, result["data"]
|
|
178
|
+
assert_equal expected_errors, result["errors"]
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
describe "with a custom block" do
|
|
183
|
+
let(:timeout_class) do
|
|
184
|
+
Class.new(GraphQL::Schema::Timeout) do
|
|
185
|
+
def handle_timeout(err, query)
|
|
186
|
+
raise("Query timed out after 2s: #{err.message}")
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
let(:query_string) {%|
|
|
192
|
+
{
|
|
193
|
+
a: sleepFor(seconds: 0.4)
|
|
194
|
+
b: sleepFor(seconds: 0.4)
|
|
195
|
+
c: sleepFor(seconds: 0.4)
|
|
196
|
+
d: sleepFor(seconds: 0.4)
|
|
197
|
+
e: sleepFor(seconds: 0.4)
|
|
198
|
+
}
|
|
199
|
+
|}
|
|
200
|
+
|
|
201
|
+
it "calls the block" do
|
|
202
|
+
err = assert_raises(RuntimeError) { result }
|
|
203
|
+
assert_equal "Query timed out after 2s: Timeout on Query.sleepFor", err.message
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|