graphql 1.8.4 → 1.8.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|