Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
ruby-version: 3.4
bundler-cache: true
- run: bundle exec rake rubocop
system_tests:
Expand All @@ -18,12 +18,12 @@ jobs:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
ruby-version: 3.4
bundler-cache: true
- run: bundle exec rake compile
- uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
ruby-version: 3.4
bundler-cache: true
env:
BUNDLE_GEMFILE: ./spec/dummy/Gemfile
Expand All @@ -43,7 +43,7 @@ jobs:
matrix:
include:
- gemfile: Gemfile
ruby: 3.2
ruby: 3.4
- gemfile: Gemfile
ruby: 2.7 # lowest supported version
- gemfile: gemfiles/rails_7.0.gemfile
Expand All @@ -58,7 +58,7 @@ jobs:
ruby: 3.3
graphql_reject_numbers_followed_by_names: 1
- gemfile: gemfiles/rails_master.gemfile
ruby: 3.3
ruby: 3.4
graphql_reject_numbers_followed_by_names: 1
isolation_level_fiber: 1
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions graphql.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Gem::Specification.new do |s|

s.add_runtime_dependency "base64"
s.add_runtime_dependency "fiber-storage"
s.add_runtime_dependency "logger"

s.add_development_dependency "benchmark-ips"
s.add_development_dependency "concurrent-ruby", "~>1.0"
Expand Down
4 changes: 1 addition & 3 deletions lib/graphql/subscriptions/serialize.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# frozen_string_literal: true
require "set"
require "ostruct"

module GraphQL
class Subscriptions
# Serialization helpers for passing subscription data around.
Expand Down Expand Up @@ -148,7 +146,7 @@ def dump_value(obj)
elsif obj.is_a?(Date) || obj.is_a?(Time)
# DateTime extends Date; for TimeWithZone, call `.utc` first.
{ TIMESTAMP_KEY => [obj.class.name, obj.strftime(TIMESTAMP_FORMAT)] }
elsif obj.is_a?(OpenStruct)
elsif defined?(OpenStruct) && obj.is_a?(OpenStruct)
{ OPEN_STRUCT_KEY => dump_value(obj.to_h) }
elsif defined?(ActiveRecord::Relation) && obj.is_a?(ActiveRecord::Relation)
dump_value(obj.to_a)
Expand Down
31 changes: 21 additions & 10 deletions spec/graphql/backtrace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,13 @@ def execute_multiplex(multiplex:)
]
assert_equal expected_graphql_backtrace, err.graphql_backtrace

hash_inspect = { message: "Boom" }.inspect
# The message includes the GraphQL context
rendered_table = [
'Loc | Field | Object | Arguments | Result',
'3:13 | Thing.raiseField as boomError | :something | {:message=>"Boom"} | #<RuntimeError: This is broken: Boom>',
'2:11 | Query.field1 | "Root" | {} | {}',
'1:9 | query | "Root" | {"msg"=>"Boom"} | {field1: {...}}',
'Loc | Field | Object | ' + "Arguments".ljust(hash_inspect.size) + ' | Result',
'3:13 | Thing.raiseField as boomError | :something | ' + hash_inspect + ' | #<RuntimeError: This is broken: Boom>',
'2:11 | Query.field1 | "Root" | ' + "{}".ljust(hash_inspect.size) + ' | {}',
'1:9 | query | "Root" | ' + {"msg" => "Boom"}.inspect.ljust(hash_inspect.size) + ' | {field1: {...}}',
].join("\n")

assert_includes err.message, "\n" + rendered_table
Expand Down Expand Up @@ -201,14 +202,18 @@ def execute_multiplex(multiplex:)
backtrace_schema.execute("query { nilInspect { raiseField(message: \"pop!\") } }")
}

hash_inspect = {message: "pop!"}.inspect # `=>` on Ruby < 3.4
rendered_table = [
'Loc | Field | Object | Arguments | Result',
'1:22 | Thing.raiseField | | {:message=>"pop!"} | #<RuntimeError: This is broken: pop!>',
'1:9 | Query.nilInspect | nil | {} | {}',
'1:1 | query | nil | {} | {nilInspect: {...}}',
'Loc | Field | Object | ' + "Arguments".ljust(hash_inspect.size) + ' | Result',
'1:22 | Thing.raiseField | | ' + hash_inspect + ' | #<RuntimeError: This is broken: pop!>',
'1:9 | Query.nilInspect | nil | ' + "{}".ljust(hash_inspect.size) + ' | {}',
'1:1 | query | nil | ' + "{}".ljust(hash_inspect.size) + ' | {nilInspect: {...}}',
'',
''
].join("\n")

assert_includes(err.message, rendered_table)
table = err.message.split("GraphQL Backtrace:\n").last
assert_equal rendered_table, table
end

it "raises original exception instead of a TracedError when error does not occur during resolving" do
Expand All @@ -225,7 +230,13 @@ def execute_multiplex(multiplex:)
# This will get brittle when execution code moves between files
# but I'm not sure how to be sure that the backtrace contains the right stuff!
def assert_backtrace_includes(backtrace, file:, method:)
includes_tag = backtrace.any? { |s| s.include?(file) && s.include?("`" + method) }
includes_tag = if RUBY_VERSION < "3.4"
backtrace.any? { |s| s.include?(file) && s.include?("`" + method) }
elsif method == "block"
backtrace.any? { |s| s.include?(file) && s.include?("'block") }
else
backtrace.any? { |s| s.include?(file) && s.include?("#{method}'") }
end
assert includes_tag, "Backtrace should include #{file} inside method #{method}\n\n#{backtrace.join("\n")}"
end

Expand Down
10 changes: 5 additions & 5 deletions spec/graphql/dataloader/nonblocking_dataloader_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,12 @@ def self.included(child_class)
let(:scheduler_class) { Libev::Scheduler }
include NonblockingDataloaderAssertions
end
end

describe "with evt" do
require "evt"
let(:scheduler_class) { Evt::Scheduler }
include NonblockingDataloaderAssertions
describe "with evt" do
require "evt"
let(:scheduler_class) { Evt::Scheduler }
include NonblockingDataloaderAssertions
end
end
end
end
2 changes: 1 addition & 1 deletion spec/graphql/execution/errors_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def non_nullable_array
ctx = { errors: [] }
res = ErrorsTestSchema.execute "{ f1(a1: 1) }", context: ctx, root_value: :abc
assert_equal({ "data" => { "f1" => nil } }, res)
assert_equal ["f1 broke (ErrorsTestSchema::Query.f1, :abc, {:a1=>1})"], ctx[:errors]
assert_equal ["f1 broke (ErrorsTestSchema::Query.f1, :abc, #{{a1: 1}.inspect})"], ctx[:errors]
end

it "rescues errors from lazy code" do
Expand Down
4 changes: 3 additions & 1 deletion spec/graphql/pagination/connections_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,9 @@ def things2
ConnectionErrorTestSchema.execute("{ things2 { name } }")
end

expected_message = if RUBY_VERSION >= "3.3"
expected_message = if RUBY_VERSION >= "3.4"
"undefined method 'no_such_method' for an instance of ConnectionErrorTestSchema::BadThing"
elsif RUBY_VERSION >= "3.3"
"undefined method `no_such_method' for an instance of ConnectionErrorTestSchema::BadThing"
else
"undefined method `no_such_method' for <BadThing!>"
Expand Down
20 changes: 10 additions & 10 deletions spec/graphql/schema/argument_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ def self.resolve_type(type, obj, ctx)

res = SchemaArgumentTest::Schema.execute(query_str)
# Make sure it's getting the renamed symbol:
assert_equal '{:renamed=>"x", :required_with_default_arg=>1}', res["data"]["field"]
assert_equal({renamed: "x", required_with_default_arg: 1}.inspect, res["data"]["field"])
end
end

Expand All @@ -186,7 +186,7 @@ def self.resolve_type(type, obj, ctx)

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal({ prepared_arg: 15, required_with_default_arg: 1}.inspect, res["data"]["field"])
end

it "calls the method on the provided Proc" do
Expand All @@ -196,7 +196,7 @@ def self.resolve_type(type, obj, ctx)

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_by_proc_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal({prepared_by_proc_arg: 15, required_with_default_arg: 1 }.inspect, res["data"]["field"])
end

it "calls the method on the provided callable object" do
Expand All @@ -206,7 +206,7 @@ def self.resolve_type(type, obj, ctx)

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_by_callable_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal({prepared_by_callable_arg: 15, required_with_default_arg: 1}.inspect, res["data"]["field"])
end

it "handles exceptions raised by prepare" do
Expand All @@ -215,7 +215,7 @@ def self.resolve_type(type, obj, ctx)
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
assert_equal({ 'f1' => '{:arg=>"echo", :required_with_default_arg=>1}', 'f2' => nil }, res['data'])
assert_equal({ 'f1' => {arg: "echo", required_with_default_arg: 1}.inspect, 'f2' => nil }, res['data'])
assert_equal(res['errors'][0]['message'], 'boom!')
assert_equal(res['errors'][0]['path'], ['f2'])
end
Expand All @@ -226,7 +226,7 @@ def self.resolve_type(type, obj, ctx)
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
assert_equal({ 'f1' => '{:arg=>"echo", :required_with_default_arg=>1}', 'f2' => nil }, res['data'])
assert_equal({ 'f1' => {arg: "echo", required_with_default_arg: 1}.inspect, 'f2' => nil }, res['data'])
assert_nil(res['errors'])
end
end
Expand All @@ -238,7 +238,7 @@ def self.resolve_type(type, obj, ctx)
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal '{:required_with_default_arg=>1}', res["data"]["field"]
assert_equal({required_with_default_arg: 1}.inspect, res["data"]["field"])
end

it 'uses provided input value' do
Expand All @@ -247,7 +247,7 @@ def self.resolve_type(type, obj, ctx)
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal '{:required_with_default_arg=>2}', res["data"]["field"]
assert_equal({ required_with_default_arg: 2 }.inspect, res["data"]["field"])
end

it 'respects non-null type' do
Expand All @@ -267,14 +267,14 @@ def self.resolve_type(type, obj, ctx)
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal "{:instrument=>#{Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION").inspect}, :required_with_default_arg=>1}", res["data"]["field"]
assert_equal({instrument: Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION"), required_with_default_arg: 1}.inspect, res["data"]["field"])

query_str2 = <<-GRAPHQL
query { field(instrumentIds: ["Instrument/Organ"]) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str2)
assert_equal "{:instruments=>[#{Jazz::Models::Instrument.new("Organ", "KEYS").inspect}], :required_with_default_arg=>1}", res["data"]["field"]
assert_equal({instruments: [Jazz::Models::Instrument.new("Organ", "KEYS")], required_with_default_arg: 1}.inspect, res["data"]["field"])
end

it "returns nil when no ID is given and `required: false`" do
Expand Down
9 changes: 6 additions & 3 deletions spec/graphql/schema/input_object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,8 @@ def self.object_from_id(id, _ctx)
it "authorizes Hashes returned from prepare:" do
query_str = "{ hashInput(input: { k1: 5, k2: 12 }) }"
res = InputObjectPrepareObjectTest::Schema.execute(query_str)
assert_equal "Hash, {:k1=>5, :k2=>12}", res["data"]["hashInput"]
expected_str = { k1: 5, k2: 12 }.inspect
assert_equal "Hash, #{expected_str}", res["data"]["hashInput"]

query_str = "{ hashInput(input: { k1: 500, k2: 12 }) }"
res = InputObjectPrepareObjectTest::Schema.execute(query_str)
Expand Down Expand Up @@ -685,14 +686,16 @@ def self.resolve_type(type, obj, ctx)

it "handles camelized booleans" do
res = Jazz::Schema.execute("query($input: CamelizedBooleanInput!){ inputObjectCamelization(input: $input) }", variables: { input: { camelizedBoolean: false } })
assert_equal "{:camelized_boolean=>false}", res["data"]["inputObjectCamelization"]
expected_res = { camelized_boolean: false }.inspect
assert_equal expected_res, res["data"]["inputObjectCamelization"]
end
end

describe "when used with default_value" do
it "comes as an instance" do
res = Jazz::Schema.execute("{ defaultValueTest }")
assert_equal "Jazz::InspectableInput -> {:string_value=>\"S\"}", res["data"]["defaultValueTest"]
expected_res = { string_value: "S" }.inspect
assert_equal "Jazz::InspectableInput -> #{expected_res}", res["data"]["defaultValueTest"]
end

it "works with empty objects" do
Expand Down
3 changes: 2 additions & 1 deletion spec/graphql/schema/mutation_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,8 @@ def resolve(**inputs)
assert_equal ["thingId", "thingName"], child_mutation.all_argument_definitions.map(&:graphql_name)
assert_equal ["thingId", "thingName"], schema.mutation.fields["child"].all_argument_definitions.map(&:graphql_name)
res = schema.execute("mutation { child(thingName: \"abc\", thingId: \"123\") { inputs } }")
assert_equal "{:thing_id=>\"123\", :thing_name=>\"abc\"}", res["data"]["child"]["inputs"]
expected_result = { thing_id: "123", thing_name: "abc" }.inspect
assert_equal expected_result, res["data"]["child"]["inputs"]
end

describe "flushing dataloader cache" do
Expand Down
11 changes: 11 additions & 0 deletions spec/graphql_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true
require "spec_helper"
require "open3"

describe GraphQL do
it "loads without warnings" do
stderr_and_stdout, _status = Open3.capture2e(%|ruby -Ilib -e "require 'bundler/inline'; gemfile(true, quiet: true) { source('https://rubygems.org'); gem('fiber-storage'); gem('graphql', path: './') }; GraphQL.eager_load!"|)
puts stderr_and_stdout
assert_equal "", stderr_and_stdout
end
end
Loading