graphql 1.0.0 → 1.1.0
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/graphql.rb +10 -0
- data/lib/graphql/base_type.rb +8 -5
- data/lib/graphql/compatibility.rb +3 -0
- data/lib/graphql/compatibility/execution_specification.rb +414 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
- data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
- data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
- data/lib/graphql/define/instance_definable.rb +53 -21
- data/lib/graphql/directive.rb +1 -1
- data/lib/graphql/enum_type.rb +31 -8
- data/lib/graphql/execution/directive_checks.rb +0 -6
- data/lib/graphql/input_object_type.rb +6 -4
- data/lib/graphql/introspection/arguments_field.rb +3 -1
- data/lib/graphql/introspection/enum_values_field.rb +10 -5
- data/lib/graphql/introspection/fields_field.rb +1 -1
- data/lib/graphql/introspection/input_fields_field.rb +2 -2
- data/lib/graphql/introspection/interfaces_field.rb +7 -1
- data/lib/graphql/introspection/possible_types_field.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/introspection/type_by_name_field.rb +4 -2
- data/lib/graphql/introspection/type_type.rb +7 -6
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language/parser.y +1 -1
- data/lib/graphql/list_type.rb +3 -4
- data/lib/graphql/non_null_type.rb +4 -8
- data/lib/graphql/object_type.rb +5 -3
- data/lib/graphql/query.rb +48 -12
- data/lib/graphql/query/context.rb +7 -1
- data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
- data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
- data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
- data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
- data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
- data/lib/graphql/query/variables.rb +7 -2
- data/lib/graphql/scalar_type.rb +1 -1
- data/lib/graphql/schema.rb +49 -18
- data/lib/graphql/schema/build_from_definition.rb +248 -0
- data/lib/graphql/schema/instrumented_field_map.rb +23 -0
- data/lib/graphql/schema/loader.rb +4 -11
- data/lib/graphql/schema/possible_types.rb +4 -2
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +4 -4
- data/lib/graphql/schema/type_map.rb +1 -1
- data/lib/graphql/schema/validation.rb +4 -0
- data/lib/graphql/schema/warden.rb +114 -0
- data/lib/graphql/static_validation/literal_validator.rb +10 -7
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
- data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
- data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
- data/lib/graphql/static_validation/validation_context.rb +7 -1
- data/lib/graphql/union_type.rb +6 -3
- data/lib/graphql/unresolved_type_error.rb +1 -2
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -5
- data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
- data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
- data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
- data/spec/graphql/define/instance_definable_spec.rb +20 -0
- data/spec/graphql/directive_spec.rb +11 -0
- data/spec/graphql/enum_type_spec.rb +20 -1
- data/spec/graphql/input_object_type_spec.rb +9 -9
- data/spec/graphql/introspection/directive_type_spec.rb +4 -4
- data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
- data/spec/graphql/introspection/type_type_spec.rb +28 -26
- data/spec/graphql/language/parser_spec.rb +27 -17
- data/spec/graphql/list_type_spec.rb +2 -2
- data/spec/graphql/query/variables_spec.rb +1 -0
- data/spec/graphql/scalar_type_spec.rb +3 -3
- data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
- data/spec/graphql/schema/type_expression_spec.rb +3 -3
- data/spec/graphql/schema/validation_spec.rb +7 -3
- data/spec/graphql/schema/warden_spec.rb +510 -0
- data/spec/graphql/schema_spec.rb +129 -0
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
- data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
- data/spec/spec_helper.rb +27 -1
- data/spec/support/dairy_app.rb +8 -5
- metadata +21 -3
- data/lib/graphql/language/parser_tests.rb +0 -809
@@ -1,17 +1,17 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
describe GraphQL::Schema::TypeExpression do
|
4
|
-
let(:
|
4
|
+
let(:types) { DummySchema.types }
|
5
5
|
let(:ast_node) {
|
6
6
|
document = GraphQL.parse("query dostuff($var: #{type_name}) { id } ")
|
7
7
|
document.definitions.first.variables.first.type
|
8
8
|
}
|
9
|
-
let(:type_expression_result) { GraphQL::Schema::TypeExpression.build_type(
|
9
|
+
let(:type_expression_result) { GraphQL::Schema::TypeExpression.build_type(types, ast_node) }
|
10
10
|
|
11
11
|
describe "#type" do
|
12
12
|
describe "simple types" do
|
13
13
|
let(:type_name) { "DairyProductInput" }
|
14
|
-
it "it gets types from the
|
14
|
+
it "it gets types from the provided types" do
|
15
15
|
assert_equal(DairyProductInputType, type_expression_result)
|
16
16
|
end
|
17
17
|
end
|
@@ -200,16 +200,20 @@ describe GraphQL::Schema::Validation do
|
|
200
200
|
end
|
201
201
|
}
|
202
202
|
|
203
|
-
let(:
|
203
|
+
let(:invalid_default_argument_for_non_null_field) {
|
204
204
|
GraphQL::Argument.define do
|
205
205
|
name "InvalidDefault"
|
206
|
-
type GraphQL::INT_TYPE
|
207
|
-
default_value
|
206
|
+
type !GraphQL::INT_TYPE
|
207
|
+
default_value 1
|
208
208
|
end
|
209
209
|
}
|
210
210
|
|
211
211
|
it "requires the type is a Base type" do
|
212
212
|
assert_error_includes untyped_argument, "must be a valid input type (Scalar or InputObject), not Symbol"
|
213
213
|
end
|
214
|
+
|
215
|
+
it "does not allow default values for non-null fields" do
|
216
|
+
assert_error_includes invalid_default_argument_for_non_null_field, 'Variable InvalidDefault of type "Int!" is required and will not use the default value. Perhaps you meant to use type "Int".'
|
217
|
+
end
|
214
218
|
end
|
215
219
|
end
|
@@ -0,0 +1,510 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module MaskHelpers
|
4
|
+
PhonemeType = GraphQL::ObjectType.define do
|
5
|
+
name "Phoneme"
|
6
|
+
description "A building block of sound in a given language"
|
7
|
+
metadata :hidden_type, true
|
8
|
+
interfaces [LanguageMemberInterface]
|
9
|
+
|
10
|
+
field :name, types.String.to_non_null_type
|
11
|
+
field :symbol, types.String.to_non_null_type
|
12
|
+
field :languages, LanguageType.to_list_type
|
13
|
+
field :manner, MannerEnum
|
14
|
+
end
|
15
|
+
|
16
|
+
MannerEnum = GraphQL::EnumType.define do
|
17
|
+
name "Manner"
|
18
|
+
description "Manner of articulation for this sound"
|
19
|
+
metadata :hidden_input_type, true
|
20
|
+
value "STOP"
|
21
|
+
value "AFFRICATE"
|
22
|
+
value "FRICATIVE"
|
23
|
+
value "APPROXIMANT"
|
24
|
+
value "VOWEL"
|
25
|
+
value "TRILL" do
|
26
|
+
metadata :hidden_enum_value, true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
LanguageType = GraphQL::ObjectType.define do
|
31
|
+
name "Language"
|
32
|
+
field :name, types.String.to_non_null_type
|
33
|
+
field :families, types.String.to_list_type
|
34
|
+
field :phonemes, PhonemeType.to_list_type
|
35
|
+
field :graphemes, GraphemeType.to_list_type
|
36
|
+
end
|
37
|
+
|
38
|
+
GraphemeType = GraphQL::ObjectType.define do
|
39
|
+
name "Grapheme"
|
40
|
+
description "A building block of spelling in a given language"
|
41
|
+
interfaces [LanguageMemberInterface]
|
42
|
+
|
43
|
+
field :name, types.String.to_non_null_type
|
44
|
+
field :glyph, types.String.to_non_null_type
|
45
|
+
field :languages, LanguageType.to_list_type
|
46
|
+
end
|
47
|
+
|
48
|
+
LanguageMemberInterface = GraphQL::InterfaceType.define do
|
49
|
+
name "LanguageMember"
|
50
|
+
metadata :hidden_abstract_type, true
|
51
|
+
description "Something that belongs to one or more languages"
|
52
|
+
field :languages, LanguageType.to_list_type
|
53
|
+
end
|
54
|
+
|
55
|
+
EmicUnitUnion = GraphQL::UnionType.define do
|
56
|
+
name "EmicUnit"
|
57
|
+
description "A building block of a word in a given language"
|
58
|
+
possible_types [GraphemeType, PhonemeType]
|
59
|
+
end
|
60
|
+
|
61
|
+
WithinInputType = GraphQL::InputObjectType.define do
|
62
|
+
name "WithinInput"
|
63
|
+
metadata :hidden_input_object_type, true
|
64
|
+
argument :latitude, !types.Float
|
65
|
+
argument :longitude, !types.Float
|
66
|
+
argument :miles, !types.Float do
|
67
|
+
metadata :hidden_input_field, true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
QueryType = GraphQL::ObjectType.define do
|
72
|
+
name "Query"
|
73
|
+
field :languages, LanguageType.to_list_type do
|
74
|
+
argument :within, WithinInputType, "Find languages nearby a point"
|
75
|
+
end
|
76
|
+
field :language, LanguageType do
|
77
|
+
metadata :hidden_field, true
|
78
|
+
argument :name, !types.String do
|
79
|
+
metadata :hidden_argument, true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
field :phonemes, PhonemeType.to_list_type do
|
84
|
+
argument :manners, MannerEnum.to_list_type, "Filter phonemes by manner of articulation"
|
85
|
+
end
|
86
|
+
|
87
|
+
field :phoneme, PhonemeType do
|
88
|
+
description "Lookup a phoneme by symbol"
|
89
|
+
argument :symbol, !types.String
|
90
|
+
end
|
91
|
+
|
92
|
+
field :unit, EmicUnitUnion do
|
93
|
+
description "Find an emic unit by its name"
|
94
|
+
argument :name, types.String.to_non_null_type
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Schema = GraphQL::Schema.define do
|
99
|
+
query QueryType
|
100
|
+
resolve_type -> (obj, ctx) { PhonemeType }
|
101
|
+
end
|
102
|
+
|
103
|
+
module Data
|
104
|
+
UVULAR_TRILL = OpenStruct.new({name: "Uvular Trill", symbol: "ʀ", manner: "TRILL"})
|
105
|
+
def self.unit
|
106
|
+
UVULAR_TRILL
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.query_with_mask(str, mask, variables: {})
|
111
|
+
Schema.execute(str, except: mask, root_value: Data, variables: variables)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
describe GraphQL::Schema::Warden do
|
117
|
+
def type_names(introspection_result)
|
118
|
+
introspection_result["data"]["__schema"]["types"].map { |t| t["name"] }
|
119
|
+
end
|
120
|
+
|
121
|
+
def possible_type_names(type_by_name_result)
|
122
|
+
type_by_name_result["possibleTypes"].map { |t| t["name"] }
|
123
|
+
end
|
124
|
+
|
125
|
+
def field_type_names(schema_result)
|
126
|
+
schema_result["types"]
|
127
|
+
.map {|t| t["fields"] }
|
128
|
+
.flatten
|
129
|
+
.map { |f| f ? get_recursive_field_type_names(f["type"]) : [] }
|
130
|
+
.flatten
|
131
|
+
.uniq
|
132
|
+
end
|
133
|
+
|
134
|
+
def get_recursive_field_type_names(field_result)
|
135
|
+
case field_result
|
136
|
+
when Hash
|
137
|
+
[field_result["name"]].concat(get_recursive_field_type_names(field_result["ofType"]))
|
138
|
+
when nil
|
139
|
+
[]
|
140
|
+
else
|
141
|
+
raise "Unexpected field result: #{field_result}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def error_messages(query_result)
|
146
|
+
query_result["errors"].map { |err| err["message"] }
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "hiding fields" do
|
150
|
+
let(:mask) {
|
151
|
+
-> (member) { member.metadata[:hidden_field] || member.metadata[:hidden_type] }
|
152
|
+
}
|
153
|
+
|
154
|
+
it "causes validation errors" do
|
155
|
+
query_string = %|{ phoneme(symbol: "ϕ") { name } }|
|
156
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
157
|
+
err_msg = res["errors"][0]["message"]
|
158
|
+
assert_equal "Field 'phoneme' doesn't exist on type 'Query'", err_msg
|
159
|
+
|
160
|
+
query_string = %|{ language(name: "Uyghur") { name } }|
|
161
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
162
|
+
err_msg = res["errors"][0]["message"]
|
163
|
+
assert_equal "Field 'language' doesn't exist on type 'Query'", err_msg
|
164
|
+
end
|
165
|
+
|
166
|
+
it "doesn't show in introspection" do
|
167
|
+
query_string = %|
|
168
|
+
{
|
169
|
+
LanguageType: __type(name: "Language") { fields { name } }
|
170
|
+
__schema {
|
171
|
+
types {
|
172
|
+
name
|
173
|
+
fields {
|
174
|
+
name
|
175
|
+
}
|
176
|
+
}
|
177
|
+
}
|
178
|
+
}|
|
179
|
+
|
180
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
181
|
+
|
182
|
+
# Fields dont appear when finding the type by name
|
183
|
+
language_fields = res["data"]["LanguageType"]["fields"].map {|f| f["name"] }
|
184
|
+
assert_equal ["families", "graphemes", "name"], language_fields
|
185
|
+
|
186
|
+
# Fields don't appear in the __schema result
|
187
|
+
phoneme_fields = res["data"]["__schema"]["types"]
|
188
|
+
.map { |t| (t["fields"] || []).select { |f| f["name"].start_with?("phoneme") } }
|
189
|
+
.flatten
|
190
|
+
|
191
|
+
assert_equal [], phoneme_fields
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
describe "hiding types" do
|
196
|
+
let(:mask) {
|
197
|
+
-> (member) { member.metadata[:hidden_type] }
|
198
|
+
}
|
199
|
+
|
200
|
+
it "hides types from introspection" do
|
201
|
+
query_string = %|
|
202
|
+
{
|
203
|
+
Phoneme: __type(name: "Phoneme") { name }
|
204
|
+
EmicUnit: __type(name: "EmicUnit") {
|
205
|
+
possibleTypes { name }
|
206
|
+
}
|
207
|
+
LanguageMember: __type(name: "LanguageMember") {
|
208
|
+
possibleTypes { name }
|
209
|
+
}
|
210
|
+
__schema {
|
211
|
+
types {
|
212
|
+
name
|
213
|
+
fields {
|
214
|
+
type {
|
215
|
+
name
|
216
|
+
ofType {
|
217
|
+
name
|
218
|
+
ofType {
|
219
|
+
name
|
220
|
+
}
|
221
|
+
}
|
222
|
+
}
|
223
|
+
}
|
224
|
+
}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
|
|
228
|
+
|
229
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
230
|
+
|
231
|
+
# It's not visible by name
|
232
|
+
assert_equal nil, res["data"]["Phoneme"]
|
233
|
+
|
234
|
+
# It's not visible in `__schema`
|
235
|
+
all_type_names = type_names(res)
|
236
|
+
assert_equal false, all_type_names.include?("Phoneme")
|
237
|
+
|
238
|
+
# No fields return it
|
239
|
+
assert_equal false, field_type_names(res["data"]["__schema"]).include?("Phoneme")
|
240
|
+
|
241
|
+
# It's not visible as a union or interface member
|
242
|
+
assert_equal false, possible_type_names(res["data"]["EmicUnit"]).include?("Phoneme")
|
243
|
+
assert_equal false, possible_type_names(res["data"]["LanguageMember"]).include?("Phoneme")
|
244
|
+
end
|
245
|
+
|
246
|
+
it "can't be a fragment condition" do
|
247
|
+
query_string = %|
|
248
|
+
{
|
249
|
+
unit(name: "bilabial trill") {
|
250
|
+
... on Phoneme { name }
|
251
|
+
... f1
|
252
|
+
}
|
253
|
+
}
|
254
|
+
|
255
|
+
fragment f1 on Phoneme {
|
256
|
+
name
|
257
|
+
}
|
258
|
+
|
|
259
|
+
|
260
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
261
|
+
|
262
|
+
expected_errors = [
|
263
|
+
"No such type Phoneme, so it can't be a fragment condition",
|
264
|
+
"No such type Phoneme, so it can't be a fragment condition",
|
265
|
+
]
|
266
|
+
assert_equal expected_errors, error_messages(res)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "can't be a resolve_type result" do
|
270
|
+
query_string = %|
|
271
|
+
{
|
272
|
+
unit(name: "Uvular Trill") { __typename }
|
273
|
+
}
|
274
|
+
|
|
275
|
+
|
276
|
+
assert_raises(GraphQL::UnresolvedTypeError) {
|
277
|
+
MaskHelpers.query_with_mask(query_string, mask)
|
278
|
+
}
|
279
|
+
end
|
280
|
+
|
281
|
+
describe "hiding an abstract type" do
|
282
|
+
let(:mask) {
|
283
|
+
-> (member) { member.metadata[:hidden_abstract_type] }
|
284
|
+
}
|
285
|
+
|
286
|
+
it "isn't present in a type's interfaces" do
|
287
|
+
query_string = %|
|
288
|
+
{
|
289
|
+
__type(name: "Phoneme") {
|
290
|
+
interfaces { name }
|
291
|
+
}
|
292
|
+
}
|
293
|
+
|
|
294
|
+
|
295
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
296
|
+
interfaces_names = res["data"]["__type"]["interfaces"].map { |i| i["name"] }
|
297
|
+
refute_includes interfaces_names, "LanguageMember"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
describe "hiding arguments" do
|
304
|
+
let(:mask) {
|
305
|
+
-> (member) { member.metadata[:hidden_argument] || member.metadata[:hidden_input_type] }
|
306
|
+
}
|
307
|
+
|
308
|
+
it "isn't present in introspection" do
|
309
|
+
query_string = %|
|
310
|
+
{
|
311
|
+
Query: __type(name: "Query") { fields { name, args { name } } }
|
312
|
+
}
|
313
|
+
|
|
314
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
315
|
+
|
316
|
+
query_field_args = res["data"]["Query"]["fields"].each_with_object({}) { |f, memo| memo[f["name"]] = f["args"].map { |a| a["name"] } }
|
317
|
+
# hidden argument:
|
318
|
+
refute_includes query_field_args["language"], "name"
|
319
|
+
# hidden input type:
|
320
|
+
refute_includes query_field_args["phoneme"], "manner"
|
321
|
+
end
|
322
|
+
|
323
|
+
it "isn't valid in a query" do
|
324
|
+
query_string = %|
|
325
|
+
{
|
326
|
+
language(name: "Catalan") { name }
|
327
|
+
phonemes(manners: STOP) { symbol }
|
328
|
+
}
|
329
|
+
|
|
330
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
331
|
+
expected_errors = [
|
332
|
+
"Field 'language' doesn't accept argument 'name'",
|
333
|
+
"Field 'phonemes' doesn't accept argument 'manners'",
|
334
|
+
]
|
335
|
+
assert_equal expected_errors, error_messages(res)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
describe "hidding input type arguments" do
|
340
|
+
let(:mask) {
|
341
|
+
-> (member) { member.metadata[:hidden_input_field] }
|
342
|
+
}
|
343
|
+
|
344
|
+
it "isn't present in introspection" do
|
345
|
+
query_string = %|
|
346
|
+
{
|
347
|
+
WithinInput: __type(name: "WithinInput") { inputFields { name } }
|
348
|
+
}|
|
349
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
350
|
+
input_field_names = res["data"]["WithinInput"]["inputFields"].map { |f| f["name"] }
|
351
|
+
refute_includes input_field_names, "miles"
|
352
|
+
end
|
353
|
+
|
354
|
+
it "isn't a valid default value" do
|
355
|
+
query_string = %|
|
356
|
+
query findLanguages($nearby: WithinInput = {latitude: 1.0, longitude: 2.2, miles: 3.3}) {
|
357
|
+
languages(within: $nearby) { name }
|
358
|
+
}|
|
359
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
360
|
+
expected_errors = ["Default value for $nearby doesn't match type WithinInput"]
|
361
|
+
assert_equal expected_errors, error_messages(res)
|
362
|
+
end
|
363
|
+
|
364
|
+
it "isn't a valid literal input" do
|
365
|
+
query_string = %|
|
366
|
+
{
|
367
|
+
languages(within: {latitude: 1.0, longitude: 2.2, miles: 3.3}) { name }
|
368
|
+
}|
|
369
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
370
|
+
expected_errors = [
|
371
|
+
"Argument 'within' on Field 'languages' has an invalid value. Expected type 'WithinInput'.",
|
372
|
+
"InputObject 'WithinInput' doesn't accept argument 'miles'"
|
373
|
+
]
|
374
|
+
assert_equal expected_errors, error_messages(res)
|
375
|
+
end
|
376
|
+
|
377
|
+
it "isn't a valid variable input" do
|
378
|
+
query_string = %|
|
379
|
+
query findLanguages($nearby: WithinInput!) {
|
380
|
+
languages(within: $nearby) { name }
|
381
|
+
}|
|
382
|
+
res = MaskHelpers.query_with_mask(query_string, mask, variables: { "latitude" => 1.0, "longitude" => 2.2, "miles" => 3.3})
|
383
|
+
expected_errors = ["Variable nearby of type WithinInput! was provided invalid value"]
|
384
|
+
assert_equal expected_errors, error_messages(res)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
describe "hidding input types" do
|
389
|
+
let(:mask) {
|
390
|
+
-> (member) { member.metadata[:hidden_input_object_type] }
|
391
|
+
}
|
392
|
+
|
393
|
+
it "isn't present in introspection" do
|
394
|
+
query_string = %|
|
395
|
+
{
|
396
|
+
WithinInput: __type(name: "WithinInput") { name }
|
397
|
+
Query: __type(name: "Query") { fields { name, args { name } } }
|
398
|
+
__schema {
|
399
|
+
types { name }
|
400
|
+
}
|
401
|
+
}
|
402
|
+
|
|
403
|
+
|
404
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
405
|
+
|
406
|
+
assert_equal nil, res["data"]["WithinInput"], "The type isn't accessible by name"
|
407
|
+
|
408
|
+
languages_arg_names = res["data"]["Query"]["fields"].find { |f| f["name"] == "languages" }["args"].map { |a| a["name"] }
|
409
|
+
refute_includes languages_arg_names, "within", "Arguments that point to it are gone"
|
410
|
+
|
411
|
+
type_names = res["data"]["__schema"]["types"].map { |t| t["name"] }
|
412
|
+
refute_includes type_names, "WithinInput", "It isn't in the schema's types"
|
413
|
+
end
|
414
|
+
|
415
|
+
it "isn't a valid input" do
|
416
|
+
query_string = %|
|
417
|
+
query findLanguages($nearby: WithinInput!) {
|
418
|
+
languages(within: $nearby) { name }
|
419
|
+
}
|
420
|
+
|
|
421
|
+
|
422
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
423
|
+
expected_errors = [
|
424
|
+
"WithinInput isn't a defined input type (on $nearby)",
|
425
|
+
"Field 'languages' doesn't accept argument 'within'",
|
426
|
+
"Variable $nearby is declared by findLanguages but not used",
|
427
|
+
]
|
428
|
+
|
429
|
+
assert_equal expected_errors, error_messages(res)
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
describe "hiding enum values" do
|
434
|
+
let(:mask) {
|
435
|
+
-> (member) { member.metadata[:hidden_enum_value] }
|
436
|
+
}
|
437
|
+
|
438
|
+
it "isn't present in introspection" do
|
439
|
+
query_string = %|
|
440
|
+
{
|
441
|
+
Manner: __type(name: "Manner") { enumValues { name } }
|
442
|
+
__schema {
|
443
|
+
types {
|
444
|
+
enumValues { name }
|
445
|
+
}
|
446
|
+
}
|
447
|
+
}
|
448
|
+
|
|
449
|
+
|
450
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
451
|
+
|
452
|
+
manner_values = res["data"]["Manner"]["enumValues"]
|
453
|
+
.map { |v| v["name"] }
|
454
|
+
|
455
|
+
schema_values = res["data"]["__schema"]["types"]
|
456
|
+
.map { |t| t["enumValues"] || [] }
|
457
|
+
.flatten
|
458
|
+
.map { |v| v["name"] }
|
459
|
+
|
460
|
+
refute_includes manner_values, "TRILL", "It's not present on __type"
|
461
|
+
refute_includes schema_values, "TRILL", "It's not present in __schema"
|
462
|
+
end
|
463
|
+
|
464
|
+
it "isn't a valid literal input" do
|
465
|
+
query_string = %|
|
466
|
+
{ phonemes(manners: [STOP, TRILL]) { symbol } }
|
467
|
+
|
|
468
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
469
|
+
# It's not a good error message ... but it's something!
|
470
|
+
expected_errors = [
|
471
|
+
"Argument 'manners' on Field 'phonemes' has an invalid value. Expected type '[Manner]'.",
|
472
|
+
]
|
473
|
+
assert_equal expected_errors, error_messages(res)
|
474
|
+
end
|
475
|
+
|
476
|
+
it "isn't a valid default value" do
|
477
|
+
query_string = %|
|
478
|
+
query getPhonemes($manners: [Manner] = [STOP, TRILL]){ phonemes(manners: $manners) { symbol } }
|
479
|
+
|
|
480
|
+
res = MaskHelpers.query_with_mask(query_string, mask)
|
481
|
+
expected_errors = ["Default value for $manners doesn't match type [Manner]"]
|
482
|
+
assert_equal expected_errors, error_messages(res)
|
483
|
+
end
|
484
|
+
|
485
|
+
it "isn't a valid variable input" do
|
486
|
+
query_string = %|
|
487
|
+
query getPhonemes($manners: [Manner]!) {
|
488
|
+
phonemes(manners: $manners) { symbol }
|
489
|
+
}
|
490
|
+
|
|
491
|
+
res = MaskHelpers.query_with_mask(query_string, mask, variables: { "manners" => ["STOP", "TRILL"] })
|
492
|
+
# It's not a good error message ... but it's something!
|
493
|
+
expected_errors = [
|
494
|
+
"Variable manners of type [Manner]! was provided invalid value",
|
495
|
+
]
|
496
|
+
assert_equal expected_errors, error_messages(res)
|
497
|
+
end
|
498
|
+
|
499
|
+
it "raises a runtime error" do
|
500
|
+
query_string = %|
|
501
|
+
{
|
502
|
+
unit(name: "Uvular Trill") { ... on Phoneme { manner } }
|
503
|
+
}
|
504
|
+
|
|
505
|
+
assert_raises(GraphQL::EnumType::UnresolvedValueError) {
|
506
|
+
MaskHelpers.query_with_mask(query_string, mask)
|
507
|
+
}
|
508
|
+
end
|
509
|
+
end
|
510
|
+
end
|