graphql 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +10 -0
  3. data/lib/graphql/base_type.rb +8 -5
  4. data/lib/graphql/compatibility.rb +3 -0
  5. data/lib/graphql/compatibility/execution_specification.rb +414 -0
  6. data/lib/graphql/compatibility/query_parser_specification.rb +117 -0
  7. data/lib/graphql/compatibility/query_parser_specification/parse_error_specification.rb +81 -0
  8. data/lib/graphql/compatibility/query_parser_specification/query_assertions.rb +78 -0
  9. data/lib/graphql/compatibility/schema_parser_specification.rb +239 -0
  10. data/lib/graphql/define/instance_definable.rb +53 -21
  11. data/lib/graphql/directive.rb +1 -1
  12. data/lib/graphql/enum_type.rb +31 -8
  13. data/lib/graphql/execution/directive_checks.rb +0 -6
  14. data/lib/graphql/input_object_type.rb +6 -4
  15. data/lib/graphql/introspection/arguments_field.rb +3 -1
  16. data/lib/graphql/introspection/enum_values_field.rb +10 -5
  17. data/lib/graphql/introspection/fields_field.rb +1 -1
  18. data/lib/graphql/introspection/input_fields_field.rb +2 -2
  19. data/lib/graphql/introspection/interfaces_field.rb +7 -1
  20. data/lib/graphql/introspection/possible_types_field.rb +1 -1
  21. data/lib/graphql/introspection/schema_type.rb +1 -1
  22. data/lib/graphql/introspection/type_by_name_field.rb +4 -2
  23. data/lib/graphql/introspection/type_type.rb +7 -6
  24. data/lib/graphql/language/lexer.rl +0 -4
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language/parser.y +1 -1
  27. data/lib/graphql/list_type.rb +3 -4
  28. data/lib/graphql/non_null_type.rb +4 -8
  29. data/lib/graphql/object_type.rb +5 -3
  30. data/lib/graphql/query.rb +48 -12
  31. data/lib/graphql/query/context.rb +7 -1
  32. data/lib/graphql/query/serial_execution/execution_context.rb +8 -3
  33. data/lib/graphql/query/serial_execution/field_resolution.rb +8 -5
  34. data/lib/graphql/query/serial_execution/operation_resolution.rb +2 -2
  35. data/lib/graphql/query/serial_execution/selection_resolution.rb +4 -21
  36. data/lib/graphql/query/serial_execution/value_resolution.rb +59 -99
  37. data/lib/graphql/query/variables.rb +7 -2
  38. data/lib/graphql/scalar_type.rb +1 -1
  39. data/lib/graphql/schema.rb +49 -18
  40. data/lib/graphql/schema/build_from_definition.rb +248 -0
  41. data/lib/graphql/schema/instrumented_field_map.rb +23 -0
  42. data/lib/graphql/schema/loader.rb +4 -11
  43. data/lib/graphql/schema/possible_types.rb +4 -2
  44. data/lib/graphql/schema/printer.rb +1 -1
  45. data/lib/graphql/schema/type_expression.rb +4 -4
  46. data/lib/graphql/schema/type_map.rb +1 -1
  47. data/lib/graphql/schema/validation.rb +4 -0
  48. data/lib/graphql/schema/warden.rb +114 -0
  49. data/lib/graphql/static_validation/literal_validator.rb +10 -7
  50. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -3
  51. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +1 -1
  52. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +1 -1
  53. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -14
  54. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +1 -1
  55. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  56. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +3 -4
  57. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +2 -1
  58. data/lib/graphql/static_validation/validation_context.rb +7 -1
  59. data/lib/graphql/union_type.rb +6 -3
  60. data/lib/graphql/unresolved_type_error.rb +1 -2
  61. data/lib/graphql/version.rb +1 -1
  62. data/readme.md +1 -5
  63. data/spec/graphql/compatibility/execution_specification_spec.rb +3 -0
  64. data/spec/graphql/compatibility/query_parser_specification_spec.rb +5 -0
  65. data/spec/graphql/compatibility/schema_parser_specification_spec.rb +5 -0
  66. data/spec/graphql/define/instance_definable_spec.rb +20 -0
  67. data/spec/graphql/directive_spec.rb +11 -0
  68. data/spec/graphql/enum_type_spec.rb +20 -1
  69. data/spec/graphql/input_object_type_spec.rb +9 -9
  70. data/spec/graphql/introspection/directive_type_spec.rb +4 -4
  71. data/spec/graphql/introspection/input_value_type_spec.rb +6 -6
  72. data/spec/graphql/introspection/type_type_spec.rb +28 -26
  73. data/spec/graphql/language/parser_spec.rb +27 -17
  74. data/spec/graphql/list_type_spec.rb +2 -2
  75. data/spec/graphql/query/variables_spec.rb +1 -0
  76. data/spec/graphql/scalar_type_spec.rb +3 -3
  77. data/spec/graphql/schema/build_from_definition_spec.rb +693 -0
  78. data/spec/graphql/schema/type_expression_spec.rb +3 -3
  79. data/spec/graphql/schema/validation_spec.rb +7 -3
  80. data/spec/graphql/schema/warden_spec.rb +510 -0
  81. data/spec/graphql/schema_spec.rb +129 -0
  82. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +1 -1
  83. data/spec/graphql/static_validation/type_stack_spec.rb +3 -3
  84. data/spec/spec_helper.rb +27 -1
  85. data/spec/support/dairy_app.rb +8 -5
  86. metadata +21 -3
  87. 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(:schema) { DummySchema }
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(schema, ast_node) }
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 schema" do
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(:invalid_default_argument) {
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 "abc"
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