graphql 1.8.4 → 1.8.5
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/graphql_controller.erb +10 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +398 -0
- data/lib/graphql/execution/execute.rb +24 -18
- data/lib/graphql/execution/lazy.rb +14 -1
- data/lib/graphql/introspection/type_type.rb +1 -1
- data/lib/graphql/language/lexer.rb +68 -42
- data/lib/graphql/language/lexer.rl +2 -0
- data/lib/graphql/language/nodes.rb +98 -0
- data/lib/graphql/language/parser.rb +1050 -770
- data/lib/graphql/language/parser.y +50 -2
- data/lib/graphql/object_type.rb +4 -0
- data/lib/graphql/query.rb +3 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/schema.rb +10 -2
- data/lib/graphql/schema/input_object.rb +1 -1
- data/lib/graphql/schema/interface.rb +6 -0
- data/lib/graphql/schema/member/has_arguments.rb +1 -0
- data/lib/graphql/schema/member/instrumentation.rb +21 -16
- data/lib/graphql/schema/mutation.rb +1 -1
- data/lib/graphql/schema/object.rb +7 -2
- data/lib/graphql/schema/possible_types.rb +1 -1
- data/lib/graphql/schema/resolver.rb +210 -1
- data/lib/graphql/schema/validation.rb +1 -1
- data/lib/graphql/static_validation/message.rb +5 -1
- data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +9 -0
- data/lib/graphql/type_kinds.rb +9 -6
- data/lib/graphql/types.rb +1 -0
- data/lib/graphql/types/iso_8601_date_time.rb +1 -5
- data/lib/graphql/unauthorized_error.rb +7 -2
- data/lib/graphql/version.rb +1 -1
- data/spec/generators/graphql/install_generator_spec.rb +12 -2
- data/spec/graphql/authorization_spec.rb +80 -1
- data/spec/graphql/directive_spec.rb +42 -0
- data/spec/graphql/execution_error_spec.rb +1 -1
- data/spec/graphql/query_spec.rb +14 -0
- data/spec/graphql/schema/interface_spec.rb +14 -0
- data/spec/graphql/schema/object_spec.rb +41 -1
- data/spec/graphql/schema/relay_classic_mutation_spec.rb +47 -0
- data/spec/graphql/schema/resolver_spec.rb +182 -8
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +6 -5
- data/spec/graphql/static_validation/rules/no_definitions_are_present_spec.rb +34 -0
- data/spec/graphql/static_validation/validator_spec.rb +15 -0
- data/spec/support/jazz.rb +36 -1
- metadata +2 -2
@@ -131,7 +131,7 @@ module GraphQL
|
|
131
131
|
}
|
132
132
|
|
133
133
|
SCHEMA_CAN_RESOLVE_TYPES = ->(schema) {
|
134
|
-
if schema.types.values.any? { |type| type.kind.
|
134
|
+
if schema.types.values.any? { |type| type.kind.abstract? } && schema.resolve_type_proc.nil?
|
135
135
|
"schema contains Interfaces or Unions, so you must define a `resolve_type -> (obj, ctx) { ... }` function"
|
136
136
|
else
|
137
137
|
# :+1:
|
@@ -33,7 +33,11 @@ module GraphQL
|
|
33
33
|
private
|
34
34
|
|
35
35
|
def locations
|
36
|
-
@nodes.map
|
36
|
+
@nodes.map do |node|
|
37
|
+
h = {"line" => node.line, "column" => node.col}
|
38
|
+
h["filename"] = node.filename if node.filename
|
39
|
+
h
|
40
|
+
end
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
@@ -12,6 +12,7 @@ module GraphQL
|
|
12
12
|
}
|
13
13
|
|
14
14
|
visitor = context.visitor
|
15
|
+
|
15
16
|
visitor[GraphQL::Language::Nodes::DirectiveDefinition] << register_node
|
16
17
|
visitor[GraphQL::Language::Nodes::SchemaDefinition] << register_node
|
17
18
|
visitor[GraphQL::Language::Nodes::ScalarTypeDefinition] << register_node
|
@@ -21,6 +22,14 @@ module GraphQL
|
|
21
22
|
visitor[GraphQL::Language::Nodes::UnionTypeDefinition] << register_node
|
22
23
|
visitor[GraphQL::Language::Nodes::EnumTypeDefinition] << register_node
|
23
24
|
|
25
|
+
visitor[GraphQL::Language::Nodes::SchemaExtension] << register_node
|
26
|
+
visitor[GraphQL::Language::Nodes::ScalarTypeExtension] << register_node
|
27
|
+
visitor[GraphQL::Language::Nodes::ObjectTypeExtension] << register_node
|
28
|
+
visitor[GraphQL::Language::Nodes::InputObjectTypeExtension] << register_node
|
29
|
+
visitor[GraphQL::Language::Nodes::InterfaceTypeExtension] << register_node
|
30
|
+
visitor[GraphQL::Language::Nodes::UnionTypeExtension] << register_node
|
31
|
+
visitor[GraphQL::Language::Nodes::EnumTypeExtension] << register_node
|
32
|
+
|
24
33
|
visitor[GraphQL::Language::Nodes::Document].leave << ->(node, _p) {
|
25
34
|
if schema_definition_nodes.any?
|
26
35
|
context.errors << message(%|Query cannot contain schema definitions|, schema_definition_nodes, context: context)
|
data/lib/graphql/type_kinds.rb
CHANGED
@@ -5,18 +5,21 @@ module GraphQL
|
|
5
5
|
# These objects are singletons, eg `GraphQL::TypeKinds::UNION`, `GraphQL::TypeKinds::SCALAR`.
|
6
6
|
class TypeKind
|
7
7
|
attr_reader :name, :description
|
8
|
-
def initialize(name,
|
8
|
+
def initialize(name, abstract: false, fields: false, wraps: false, input: false, description: nil)
|
9
9
|
@name = name
|
10
|
-
@
|
10
|
+
@abstract = abstract
|
11
11
|
@fields = fields
|
12
12
|
@wraps = wraps
|
13
13
|
@input = input
|
14
|
-
@composite = fields? ||
|
14
|
+
@composite = fields? || abstract?
|
15
15
|
@description = description
|
16
16
|
end
|
17
17
|
|
18
18
|
# Does this TypeKind have multiple possible implementors?
|
19
|
-
|
19
|
+
# @deprecated Use `abstract?` instead of `resolves?`.
|
20
|
+
def resolves?; @abstract; end
|
21
|
+
# Is this TypeKind abstract?
|
22
|
+
def abstract?; @abstract; end
|
20
23
|
# Does this TypeKind have queryable fields?
|
21
24
|
def fields?; @fields; end
|
22
25
|
# Does this TypeKind modify another type?
|
@@ -31,8 +34,8 @@ module GraphQL
|
|
31
34
|
TYPE_KINDS = [
|
32
35
|
SCALAR = TypeKind.new("SCALAR", input: true, description: 'Indicates this type is a scalar.'),
|
33
36
|
OBJECT = TypeKind.new("OBJECT", fields: true, description: 'Indicates this type is an object. `fields` and `interfaces` are valid fields.'),
|
34
|
-
INTERFACE = TypeKind.new("INTERFACE",
|
35
|
-
UNION = TypeKind.new("UNION",
|
37
|
+
INTERFACE = TypeKind.new("INTERFACE", abstract: true, fields: true, description: 'Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.'),
|
38
|
+
UNION = TypeKind.new("UNION", abstract: true, description: 'Indicates this type is a union. `possibleTypes` is a valid field.'),
|
36
39
|
ENUM = TypeKind.new("ENUM", input: true, description: 'Indicates this type is an enum. `enumValues` is a valid field.'),
|
37
40
|
INPUT_OBJECT = TypeKind.new("INPUT_OBJECT", input: true, description: 'Indicates this type is an input object. `inputFields` is a valid field.'),
|
38
41
|
LIST = TypeKind.new("LIST", wraps: true, description: 'Indicates this type is a list. `ofType` is a valid field.'),
|
data/lib/graphql/types.rb
CHANGED
@@ -4,11 +4,7 @@ module GraphQL
|
|
4
4
|
# This scalar takes `DateTime`s and transmits them as strings,
|
5
5
|
# using ISO 8601 format.
|
6
6
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# require "graphql/types/iso_8601_date_time"
|
10
|
-
#
|
11
|
-
# Then use it for fields or arguments:
|
7
|
+
# Use it for fields or arguments as follows:
|
12
8
|
#
|
13
9
|
# field :created_at, GraphQL::Types::ISO8601DateTime, null: false
|
14
10
|
#
|
@@ -10,11 +10,16 @@ module GraphQL
|
|
10
10
|
# @return [GraphQL::Query::Context] the context for the current query
|
11
11
|
attr_reader :context
|
12
12
|
|
13
|
-
def initialize(object
|
13
|
+
def initialize(message = nil, object: nil, type: nil, context: nil)
|
14
|
+
if message.nil? && object.nil?
|
15
|
+
raise ArgumentError, "#{self.class.name} requires either a message or keywords"
|
16
|
+
end
|
17
|
+
|
14
18
|
@object = object
|
15
19
|
@type = type
|
16
20
|
@context = context
|
17
|
-
|
21
|
+
message ||= "An instance of #{object.class} failed #{type.name}'s authorization check"
|
22
|
+
super(message)
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
data/lib/graphql/version.rb
CHANGED
@@ -129,7 +129,7 @@ RUBY
|
|
129
129
|
assert_file "app/controllers/graphql_controller.rb", /CustomSchema\.execute/
|
130
130
|
end
|
131
131
|
|
132
|
-
EXPECTED_GRAPHQLS_CONTROLLER = <<-RUBY
|
132
|
+
EXPECTED_GRAPHQLS_CONTROLLER = <<-'RUBY'
|
133
133
|
class GraphqlController < ApplicationController
|
134
134
|
def execute
|
135
135
|
variables = ensure_hash(params[:variables])
|
@@ -141,6 +141,9 @@ class GraphqlController < ApplicationController
|
|
141
141
|
}
|
142
142
|
result = DummySchema.execute(query, variables: variables, context: context, operation_name: operation_name)
|
143
143
|
render json: result
|
144
|
+
rescue => e
|
145
|
+
raise e unless Rails.env.development?
|
146
|
+
handle_error_in_development e
|
144
147
|
end
|
145
148
|
|
146
149
|
private
|
@@ -159,9 +162,16 @@ class GraphqlController < ApplicationController
|
|
159
162
|
when nil
|
160
163
|
{}
|
161
164
|
else
|
162
|
-
raise ArgumentError, "Unexpected parameter:
|
165
|
+
raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
|
163
166
|
end
|
164
167
|
end
|
168
|
+
|
169
|
+
def handle_error_in_development(e)
|
170
|
+
logger.error e.message
|
171
|
+
logger.error e.backtrace.join("\n")
|
172
|
+
|
173
|
+
render json: { error: { message: e.message, backtrace: e.backtrace }, data: {} }, status: 500
|
174
|
+
end
|
165
175
|
end
|
166
176
|
RUBY
|
167
177
|
|
@@ -208,6 +208,26 @@ describe GraphQL::Authorization do
|
|
208
208
|
edge_type(IntegerObjectEdge)
|
209
209
|
end
|
210
210
|
|
211
|
+
# This object responds with `replaced => false`,
|
212
|
+
# but if its replacement value is used, it gives `replaced => true`
|
213
|
+
class Replaceable
|
214
|
+
def replacement
|
215
|
+
{ replaced: true }
|
216
|
+
end
|
217
|
+
|
218
|
+
def replaced
|
219
|
+
false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class ReplacedObject < BaseObject
|
224
|
+
def self.authorized?(obj, ctx)
|
225
|
+
super && !ctx[:replace_me]
|
226
|
+
end
|
227
|
+
|
228
|
+
field :replaced, Boolean, null: false
|
229
|
+
end
|
230
|
+
|
211
231
|
class LandscapeFeature < BaseEnum
|
212
232
|
value "MOUNTAIN"
|
213
233
|
value "STREAM", role: :unauthorized
|
@@ -284,6 +304,12 @@ describe GraphQL::Authorization do
|
|
284
304
|
argument :value, String, required: true
|
285
305
|
end
|
286
306
|
|
307
|
+
field :unauthorized_lazy_list_interface, [UnauthorizedInterface, null: true], null: true
|
308
|
+
|
309
|
+
def unauthorized_lazy_list_interface
|
310
|
+
["z", Box.new(value: Box.new(value: "z2")), "a", Box.new(value: "a")]
|
311
|
+
end
|
312
|
+
|
287
313
|
field :integers, IntegerObjectConnection, null: false
|
288
314
|
|
289
315
|
def integers
|
@@ -295,6 +321,11 @@ describe GraphQL::Authorization do
|
|
295
321
|
def lazy_integers
|
296
322
|
Box.new(value: Box.new(value: [1,2,3]))
|
297
323
|
end
|
324
|
+
|
325
|
+
field :replaced_object, ReplacedObject, null: false
|
326
|
+
def replaced_object
|
327
|
+
Replaceable.new
|
328
|
+
end
|
298
329
|
end
|
299
330
|
|
300
331
|
class DoHiddenStuff < GraphQL::Schema::RelayClassicMutation
|
@@ -303,6 +334,12 @@ describe GraphQL::Authorization do
|
|
303
334
|
end
|
304
335
|
end
|
305
336
|
|
337
|
+
class DoHiddenStuff2 < GraphQL::Schema::Mutation
|
338
|
+
def self.visible?(ctx)
|
339
|
+
super && !ctx[:hidden_mutation]
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
306
343
|
class DoInaccessibleStuff < GraphQL::Schema::RelayClassicMutation
|
307
344
|
def self.accessible?(ctx)
|
308
345
|
super && (ctx[:inaccessible_mutation] ? false : true)
|
@@ -317,6 +354,7 @@ describe GraphQL::Authorization do
|
|
317
354
|
|
318
355
|
class Mutation < BaseObject
|
319
356
|
field :do_hidden_stuff, mutation: DoHiddenStuff
|
357
|
+
field :do_hidden_stuff2, mutation: DoHiddenStuff2
|
320
358
|
field :do_inaccessible_stuff, mutation: DoInaccessibleStuff
|
321
359
|
field :do_unauthorized_stuff, mutation: DoUnauthorizedStuff
|
322
360
|
end
|
@@ -328,7 +366,11 @@ describe GraphQL::Authorization do
|
|
328
366
|
lazy_resolve(Box, :value)
|
329
367
|
|
330
368
|
def self.unauthorized_object(err)
|
331
|
-
|
369
|
+
if err.object.respond_to?(:replacement)
|
370
|
+
err.object.replacement
|
371
|
+
else
|
372
|
+
raise GraphQL::ExecutionError, "Unauthorized #{err.type.graphql_name}: #{err.object}"
|
373
|
+
end
|
332
374
|
end
|
333
375
|
|
334
376
|
# use GraphQL::Backtrace
|
@@ -387,6 +429,17 @@ describe GraphQL::Authorization do
|
|
387
429
|
assert_equal "DoHiddenStuffPayload", visible_introspection_res["data"]["t2"]["name"]
|
388
430
|
end
|
389
431
|
|
432
|
+
it "works with Schema::Mutation" do
|
433
|
+
query = "mutation { doHiddenStuff2 { __typename } }"
|
434
|
+
res = auth_execute(query, context: { hidden_mutation: true })
|
435
|
+
assert_equal ["Field 'doHiddenStuff2' doesn't exist on type 'Mutation'"], res["errors"].map { |e| e["message"] }
|
436
|
+
|
437
|
+
# `#resolve` isn't implemented, so this errors out:
|
438
|
+
assert_raises NotImplementedError do
|
439
|
+
auth_execute(query)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
390
443
|
it "uses the base type for edges and connections" do
|
391
444
|
query = <<-GRAPHQL
|
392
445
|
{
|
@@ -680,5 +733,31 @@ describe GraphQL::Authorization do
|
|
680
733
|
res2 = auth_execute(query, variables: { value: "b"})
|
681
734
|
assert_equal "b", res2["data"]["unauthorizedInterface"]["value"]
|
682
735
|
end
|
736
|
+
|
737
|
+
it "works with lazy values / lists of interfaces" do
|
738
|
+
query = <<-GRAPHQL
|
739
|
+
{
|
740
|
+
unauthorizedLazyListInterface {
|
741
|
+
... on UnauthorizedCheckBox {
|
742
|
+
value
|
743
|
+
}
|
744
|
+
}
|
745
|
+
}
|
746
|
+
GRAPHQL
|
747
|
+
|
748
|
+
res = auth_execute(query)
|
749
|
+
# An error from two, values from the others
|
750
|
+
assert_equal ["Unauthorized UnauthorizedCheckBox: a", "Unauthorized UnauthorizedCheckBox: a"], res["errors"].map { |e| e["message"] }
|
751
|
+
assert_equal [{"value" => "z"}, {"value" => "z2"}, nil, nil], res["data"]["unauthorizedLazyListInterface"]
|
752
|
+
end
|
753
|
+
|
754
|
+
it "replaces objects from the unauthorized_object hook" do
|
755
|
+
query = "{ replacedObject { replaced } }"
|
756
|
+
res = auth_execute(query, context: { replace_me: true })
|
757
|
+
assert_equal true, res["data"]["replacedObject"]["replaced"]
|
758
|
+
|
759
|
+
res = auth_execute(query, context: { replace_me: false })
|
760
|
+
assert_equal false, res["data"]["replacedObject"]["replaced"]
|
761
|
+
end
|
683
762
|
end
|
684
763
|
end
|
@@ -48,6 +48,48 @@ describe GraphQL::Directive do
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
describe "when directive uses argument with default value" do
|
52
|
+
describe "with false" do
|
53
|
+
let(:query_string) { <<-GRAPHQL
|
54
|
+
query($f: Boolean = false) {
|
55
|
+
cheese(id: 1) {
|
56
|
+
dontIncludeFlavor: flavor @include(if: $f)
|
57
|
+
dontSkipFlavor: flavor @skip(if: $f)
|
58
|
+
}
|
59
|
+
}
|
60
|
+
GRAPHQL
|
61
|
+
}
|
62
|
+
|
63
|
+
it "is not included" do
|
64
|
+
assert !result["data"]["cheese"].key?("dontIncludeFlavor")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "is not skipped" do
|
68
|
+
assert result["data"]["cheese"].key?("dontSkipFlavor")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "with true" do
|
73
|
+
let(:query_string) { <<-GRAPHQL
|
74
|
+
query($t: Boolean = true) {
|
75
|
+
cheese(id: 1) {
|
76
|
+
includeFlavor: flavor @include(if: $t)
|
77
|
+
skipFlavor: flavor @skip(if: $t)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
GRAPHQL
|
81
|
+
}
|
82
|
+
|
83
|
+
it "is included" do
|
84
|
+
assert result["data"]["cheese"].key?("includeFlavor")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "is skipped" do
|
88
|
+
assert !result["data"]["cheese"].key?("skipFlavor")
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
51
93
|
it "intercepts fields" do
|
52
94
|
expected = { "data" =>{
|
53
95
|
"cheese" => {
|
data/spec/graphql/query_spec.rb
CHANGED
@@ -638,6 +638,20 @@ describe GraphQL::Query do
|
|
638
638
|
end
|
639
639
|
end
|
640
640
|
|
641
|
+
describe "validating with optional arguments and variables: nil" do
|
642
|
+
it "works" do
|
643
|
+
query_str = <<-GRAPHQL
|
644
|
+
query($expiresAfter: Time) {
|
645
|
+
searchDairy(expiresAfter: $expiresAfter) {
|
646
|
+
__typename
|
647
|
+
}
|
648
|
+
}
|
649
|
+
GRAPHQL
|
650
|
+
query = GraphQL::Query.new(schema, query_str, variables: nil)
|
651
|
+
assert query.valid?
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
641
655
|
describe 'NullValue type arguments' do
|
642
656
|
let(:schema_definition) {
|
643
657
|
<<-GRAPHQL
|
@@ -161,6 +161,12 @@ describe GraphQL::Schema::Interface do
|
|
161
161
|
describe ':DefinitionMethods' do
|
162
162
|
module InterfaceA
|
163
163
|
include GraphQL::Schema::Interface
|
164
|
+
|
165
|
+
definition_methods do
|
166
|
+
def some_method
|
167
|
+
42
|
168
|
+
end
|
169
|
+
end
|
164
170
|
end
|
165
171
|
|
166
172
|
module InterfaceB
|
@@ -171,6 +177,10 @@ describe GraphQL::Schema::Interface do
|
|
171
177
|
include GraphQL::Schema::Interface
|
172
178
|
end
|
173
179
|
|
180
|
+
class ObjectA < GraphQL::Schema::Object
|
181
|
+
implements InterfaceA
|
182
|
+
end
|
183
|
+
|
174
184
|
it "doesn't overwrite them when including multiple interfaces" do
|
175
185
|
def_methods = InterfaceC::DefinitionMethods
|
176
186
|
|
@@ -181,5 +191,9 @@ describe GraphQL::Schema::Interface do
|
|
181
191
|
|
182
192
|
assert_equal(InterfaceC::DefinitionMethods, def_methods)
|
183
193
|
end
|
194
|
+
|
195
|
+
it "extends classes with the defined methods" do
|
196
|
+
assert_equal(ObjectA.some_method, InterfaceA.some_method)
|
197
|
+
end
|
184
198
|
end
|
185
199
|
end
|
@@ -41,6 +41,30 @@ describe GraphQL::Schema::Object do
|
|
41
41
|
assert_equal object_class.description, new_subclass_2.description
|
42
42
|
end
|
43
43
|
|
44
|
+
it "does not inherit singleton methods from base interface when implementing base interface" do
|
45
|
+
object_type = Class.new(GraphQL::Schema::Object)
|
46
|
+
methods = object_type.singleton_methods
|
47
|
+
method_defs = Hash[methods.zip(methods.map{|method| object_type.method(method.to_sym)})]
|
48
|
+
|
49
|
+
object_type.implements(GraphQL::Schema::Interface)
|
50
|
+
new_method_defs = Hash[methods.zip(methods.map{|method| object_type.method(method.to_sym)})]
|
51
|
+
assert_equal method_defs, new_method_defs
|
52
|
+
end
|
53
|
+
|
54
|
+
it "does not inherit singleton methods from base interface when implementing another interface" do
|
55
|
+
object_type = Class.new(GraphQL::Schema::Object)
|
56
|
+
methods = object_type.singleton_methods
|
57
|
+
method_defs = Hash[methods.zip(methods.map{|method| object_type.method(method.to_sym)})]
|
58
|
+
|
59
|
+
module InterfaceType
|
60
|
+
include GraphQL::Schema::Interface
|
61
|
+
end
|
62
|
+
|
63
|
+
object_type.implements(InterfaceType)
|
64
|
+
new_method_defs = Hash[methods.zip(methods.map{|method| object_type.method(method.to_sym)})]
|
65
|
+
assert_equal method_defs, new_method_defs
|
66
|
+
end
|
67
|
+
|
44
68
|
it "should take Ruby name (without Type suffix) as default graphql name" do
|
45
69
|
TestingClassType = Class.new(GraphQL::Schema::Object)
|
46
70
|
assert_equal "TestingClass", TestingClassType.graphql_name
|
@@ -83,7 +107,23 @@ describe GraphQL::Schema::Object do
|
|
83
107
|
end
|
84
108
|
end
|
85
109
|
|
86
|
-
describe "
|
110
|
+
describe "wrapping `nil`" do
|
111
|
+
it "doesn't wrap nil in lists" do
|
112
|
+
query_str = <<-GRAPHQL
|
113
|
+
{
|
114
|
+
namedEntities {
|
115
|
+
name
|
116
|
+
}
|
117
|
+
}
|
118
|
+
GRAPHQL
|
119
|
+
|
120
|
+
res = Jazz::Schema.execute(query_str)
|
121
|
+
expected_items = [{"name" => "Bela Fleck and the Flecktones"}, nil]
|
122
|
+
assert_equal expected_items, res["data"]["namedEntities"]
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe ".to_graphql" do
|
87
127
|
let(:obj_type) { Jazz::Ensemble.to_graphql }
|
88
128
|
it "returns a matching GraphQL::ObjectType" do
|
89
129
|
assert_equal "Ensemble", obj_type.name
|