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.
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