1from operator import attrgetter
2from typing import Any, List, Union
3
4from pytest import mark, raises
5
6from graphql.language import parse, DirectiveLocation
7from graphql.pyutils import inspect
8from graphql.type import (
9    assert_directive,
10    assert_enum_type,
11    assert_input_object_type,
12    assert_interface_type,
13    assert_object_type,
14    assert_scalar_type,
15    assert_union_type,
16    assert_valid_schema,
17    is_input_type,
18    is_output_type,
19    validate_schema,
20    GraphQLArgument,
21    GraphQLDirective,
22    GraphQLEnumType,
23    GraphQLEnumValue,
24    GraphQLField,
25    GraphQLInputField,
26    GraphQLInputType,
27    GraphQLInputObjectType,
28    GraphQLInt,
29    GraphQLInterfaceType,
30    GraphQLList,
31    GraphQLNamedType,
32    GraphQLNonNull,
33    GraphQLObjectType,
34    GraphQLOutputType,
35    GraphQLSchema,
36    GraphQLString,
37    GraphQLUnionType,
38)
39from graphql.utilities import build_schema, extend_schema
40
41from ..utils import dedent
42
43SomeSchema = build_schema(
44    """
45    scalar SomeScalar
46
47    interface SomeInterface { f: SomeObject }
48
49    type SomeObject implements SomeInterface { f: SomeObject }
50
51    union SomeUnion = SomeObject
52
53    enum SomeEnum { ONLY }
54
55    input SomeInputObject { val: String = "hello" }
56
57    directive @SomeDirective on QUERY
58    """
59)
60
61get_type = SomeSchema.get_type
62SomeScalarType = assert_scalar_type(get_type("SomeScalar"))
63SomeInterfaceType = assert_interface_type(get_type("SomeInterface"))
64SomeObjectType = assert_object_type(get_type("SomeObject"))
65SomeUnionType = assert_union_type(get_type("SomeUnion"))
66SomeEnumType = assert_enum_type(get_type("SomeEnum"))
67SomeInputObjectType = assert_input_object_type(get_type("SomeInputObject"))
68SomeDirective = assert_directive(SomeSchema.get_directive("SomeDirective"))
69
70
71def with_modifiers(
72    type_: GraphQLNamedType,
73) -> List[Union[GraphQLNamedType, GraphQLNonNull, GraphQLList]]:
74    return [
75        type_,
76        GraphQLList(type_),
77        GraphQLNonNull(type_),
78        GraphQLNonNull(GraphQLList(type_)),
79    ]
80
81
82output_types = [
83    *with_modifiers(GraphQLString),
84    *with_modifiers(SomeScalarType),
85    *with_modifiers(SomeEnumType),
86    *with_modifiers(SomeObjectType),
87    *with_modifiers(SomeUnionType),
88    *with_modifiers(SomeInterfaceType),
89]
90
91not_output_types = with_modifiers(SomeInputObjectType)
92
93input_types = [
94    *with_modifiers(GraphQLString),
95    *with_modifiers(SomeScalarType),
96    *with_modifiers(SomeEnumType),
97    *with_modifiers(SomeInputObjectType),
98]
99
100not_input_types = [
101    *with_modifiers(SomeObjectType),
102    *with_modifiers(SomeUnionType),
103    *with_modifiers(SomeInterfaceType),
104]
105
106not_graphql_types = [
107    type("IntType", (int,), {"name": "IntType"}),
108    type("FloatType", (float,), {"name": "FloatType"}),
109    type("StringType", (str,), {"name": "StringType"}),
110]
111
112
113get_name = attrgetter("__class__.__name__")
114
115
116def schema_with_field_type(type_):
117    return GraphQLSchema(
118        query=GraphQLObjectType(name="Query", fields={"f": GraphQLField(type_)})
119    )
120
121
122def describe_type_system_a_schema_must_have_object_root_types():
123    def accepts_a_schema_whose_query_type_is_an_object_type():
124        schema = build_schema(
125            """
126            type Query {
127              test: String
128            }
129            """
130        )
131        assert validate_schema(schema) == []
132
133        schema_with_def = build_schema(
134            """
135            schema {
136              query: QueryRoot
137            }
138
139            type QueryRoot {
140              test: String
141            }
142            """
143        )
144
145        assert validate_schema(schema_with_def) == []
146
147    def accepts_a_schema_whose_query_and_mutation_types_are_object_types():
148        schema = build_schema(
149            """
150            type Query {
151              test: String
152            }
153
154            type Mutation {
155              test: String
156            }
157            """
158        )
159        assert validate_schema(schema) == []
160
161        schema_with_def = build_schema(
162            """
163            schema {
164              query: QueryRoot
165              mutation: MutationRoot
166            }
167
168            type QueryRoot {
169              test: String
170            }
171
172            type MutationRoot {
173              test: String
174            }
175            """
176        )
177        assert validate_schema(schema_with_def) == []
178
179    def accepts_a_schema_whose_query_and_subscription_types_are_object_types():
180        schema = build_schema(
181            """
182            type Query {
183              test: String
184            }
185
186            type Subscription {
187              test: String
188            }
189            """
190        )
191        assert validate_schema(schema) == []
192
193        schema_with_def = build_schema(
194            """
195            schema {
196              query: QueryRoot
197              subscription: SubscriptionRoot
198            }
199
200            type QueryRoot {
201              test: String
202            }
203
204            type SubscriptionRoot {
205              test: String
206            }
207            """
208        )
209        assert validate_schema(schema_with_def) == []
210
211    def rejects_a_schema_without_a_query_type():
212        schema = build_schema(
213            """
214            type Mutation {
215              test: String
216            }
217            """
218        )
219        assert validate_schema(schema) == [
220            {"message": "Query root type must be provided.", "locations": None}
221        ]
222
223        schema_with_def = build_schema(
224            """
225            schema {
226              mutation: MutationRoot
227            }
228
229            type MutationRoot {
230              test: String
231            }
232            """
233        )
234        assert validate_schema(schema_with_def) == [
235            {"message": "Query root type must be provided.", "locations": [(2, 13)]}
236        ]
237
238    def rejects_a_schema_whose_query_root_type_is_not_an_object_type():
239        schema = build_schema(
240            """
241            input Query {
242              test: String
243            }
244            """
245        )
246        assert validate_schema(schema) == [
247            {
248                "message": "Query root type must be Object type,"
249                " it cannot be Query.",
250                "locations": [(2, 13)],
251            }
252        ]
253
254        schema_with_def = build_schema(
255            """
256            schema {
257              query: SomeInputObject
258            }
259
260            input SomeInputObject {
261              test: String
262            }
263            """
264        )
265        assert validate_schema(schema_with_def) == [
266            {
267                "message": "Query root type must be Object type,"
268                " it cannot be SomeInputObject.",
269                "locations": [(3, 22)],
270            }
271        ]
272
273    def rejects_a_schema_whose_mutation_type_is_an_input_type():
274        schema = build_schema(
275            """
276            type Query {
277              field: String
278            }
279
280            input Mutation {
281              test: String
282            }
283            """
284        )
285        assert validate_schema(schema) == [
286            {
287                "message": "Mutation root type must be Object type if provided,"
288                " it cannot be Mutation.",
289                "locations": [(6, 13)],
290            }
291        ]
292
293        schema_with_def = build_schema(
294            """
295            schema {
296              query: Query
297              mutation: SomeInputObject
298            }
299
300            type Query {
301              field: String
302            }
303
304            input SomeInputObject {
305              test: String
306            }
307            """
308        )
309        assert validate_schema(schema_with_def) == [
310            {
311                "message": "Mutation root type must be Object type if provided,"
312                " it cannot be SomeInputObject.",
313                "locations": [(4, 25)],
314            }
315        ]
316
317    def rejects_a_schema_whose_subscription_type_is_an_input_type():
318        schema = build_schema(
319            """
320            type Query {
321              field: String
322            }
323
324            input Subscription {
325              test: String
326            }
327            """
328        )
329        assert validate_schema(schema) == [
330            {
331                "message": "Subscription root type must be Object type if"
332                " provided, it cannot be Subscription.",
333                "locations": [(6, 13)],
334            }
335        ]
336
337        schema_with_def = build_schema(
338            """
339            schema {
340              query: Query
341              subscription: SomeInputObject
342            }
343
344            type Query {
345              field: String
346            }
347
348            input SomeInputObject {
349              test: String
350            }
351            """
352        )
353        assert validate_schema(schema_with_def) == [
354            {
355                "message": "Subscription root type must be Object type if"
356                " provided, it cannot be SomeInputObject.",
357                "locations": [(4, 29)],
358            }
359        ]
360
361    def rejects_a_schema_extended_with_invalid_root_types():
362        schema = build_schema(
363            """
364            input SomeInputObject {
365              test: String
366            }
367            """
368        )
369        schema = extend_schema(
370            schema,
371            parse(
372                """
373                extend schema {
374                  query: SomeInputObject
375                }
376                """
377            ),
378        )
379        schema = extend_schema(
380            schema,
381            parse(
382                """
383                extend schema {
384                  mutation: SomeInputObject
385                }
386                """
387            ),
388        )
389        schema = extend_schema(
390            schema,
391            parse(
392                """
393                extend schema {
394                  subscription: SomeInputObject
395                }
396                """
397            ),
398        )
399        assert validate_schema(schema) == [
400            {
401                "message": "Query root type must be Object type,"
402                " it cannot be SomeInputObject.",
403                "locations": [(3, 26)],
404            },
405            {
406                "message": "Mutation root type must be Object type"
407                " if provided, it cannot be SomeInputObject.",
408                "locations": [(3, 29)],
409            },
410            {
411                "message": "Subscription root type must be Object type"
412                " if provided, it cannot be SomeInputObject.",
413                "locations": [(3, 33)],
414            },
415        ]
416
417    def rejects_a_schema_whose_types_are_incorrectly_type():
418        # invalid schema cannot be built with Python
419        with raises(TypeError) as exc_info:
420            # noinspection PyTypeChecker
421            GraphQLSchema(
422                SomeObjectType,
423                types=[{"name": "SomeType"}, SomeDirective],  # type: ignore
424            )
425        assert str(exc_info.value) == (
426            "Schema types must be specified as a collection of GraphQL types."
427        )
428        # construct invalid schema manually
429        schema = GraphQLSchema(SomeObjectType)
430        schema.type_map = {
431            "SomeType": {"name": "SomeType"},  # type: ignore
432            "SomeDirective": SomeDirective,  # type: ignore
433        }
434        assert validate_schema(schema) == [
435            {"message": "Expected GraphQL named type but got: {'name': 'SomeType'}."},
436            {"message": "Expected GraphQL named type but got: @SomeDirective."},
437        ]
438
439    def rejects_a_schema_whose_directives_are_incorrectly_typed():
440        schema = GraphQLSchema(
441            SomeObjectType,
442            directives=[None, "SomeDirective", SomeScalarType],  # type: ignore
443        )
444        assert validate_schema(schema) == [
445            {"message": "Expected directive but got: None."},
446            {"message": "Expected directive but got: 'SomeDirective'."},
447            {"message": "Expected directive but got: SomeScalar."},
448        ]
449
450
451def describe_type_system_objects_must_have_fields():
452    def accepts_an_object_type_with_fields_object():
453        schema = build_schema(
454            """
455            type Query {
456              field: SomeObject
457            }
458
459            type SomeObject {
460              field: String
461            }
462            """
463        )
464        assert validate_schema(schema) == []
465
466    def rejects_an_object_type_with_missing_fields():
467        schema = build_schema(
468            """
469            type Query {
470              test: IncompleteObject
471            }
472
473            type IncompleteObject
474            """
475        )
476        assert validate_schema(schema) == [
477            {
478                "message": "Type IncompleteObject must define one or more fields.",
479                "locations": [(6, 13)],
480            }
481        ]
482
483        manual_schema = schema_with_field_type(
484            GraphQLObjectType("IncompleteObject", {})
485        )
486        msg = validate_schema(manual_schema)[0].message
487        assert msg == "Type IncompleteObject must define one or more fields."
488
489        manual_schema_2 = schema_with_field_type(
490            GraphQLObjectType("IncompleteObject", lambda: {})
491        )
492        msg = validate_schema(manual_schema_2)[0].message
493        assert msg == "Type IncompleteObject must define one or more fields."
494
495    def rejects_an_object_type_with_incorrectly_named_fields():
496        schema = schema_with_field_type(
497            GraphQLObjectType(
498                "SomeObject", {"bad-name-with-dashes": GraphQLField(GraphQLString)}
499            )
500        )
501        msg = validate_schema(schema)[0].message
502        assert msg == (
503            "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/"
504            " but 'bad-name-with-dashes' does not."
505        )
506
507
508def describe_type_system_field_args_must_be_properly_named():
509    def accepts_field_args_with_valid_names():
510        schema = schema_with_field_type(
511            GraphQLObjectType(
512                "SomeObject",
513                {
514                    "goodField": GraphQLField(
515                        GraphQLString, args={"goodArg": GraphQLArgument(GraphQLString)}
516                    )
517                },
518            )
519        )
520        assert validate_schema(schema) == []
521
522    def rejects_field_args_with_invalid_names():
523        schema = schema_with_field_type(
524            GraphQLObjectType(
525                "SomeObject",
526                {
527                    "badField": GraphQLField(
528                        GraphQLString,
529                        args={"bad-name-with-dashes": GraphQLArgument(GraphQLString)},
530                    )
531                },
532            )
533        )
534
535        msg = validate_schema(schema)[0].message
536        assert msg == (
537            "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/"
538            " but 'bad-name-with-dashes' does not."
539        )
540
541
542def describe_type_system_union_types_must_be_valid():
543    def accepts_a_union_type_with_member_types():
544        schema = build_schema(
545            """
546            type Query {
547              test: GoodUnion
548            }
549
550            type TypeA {
551              field: String
552            }
553
554            type TypeB {
555              field: String
556            }
557
558            union GoodUnion =
559              | TypeA
560              | TypeB
561            """
562        )
563        assert validate_schema(schema) == []
564
565    def rejects_a_union_type_with_empty_types():
566        schema = build_schema(
567            """
568            type Query {
569              test: BadUnion
570            }
571
572            union BadUnion
573            """
574        )
575
576        schema = extend_schema(
577            schema,
578            parse(
579                """
580                directive @test on UNION
581
582                extend union BadUnion @test
583                """
584            ),
585        )
586
587        assert validate_schema(schema) == [
588            {
589                "message": "Union type BadUnion must define one or more member types.",
590                "locations": [(6, 13), (4, 17)],
591            }
592        ]
593
594    def rejects_a_union_type_with_duplicated_member_type():
595        schema = build_schema(
596            """
597            type Query {
598              test: BadUnion
599            }
600
601            type TypeA {
602              field: String
603            }
604
605            type TypeB {
606              field: String
607            }
608
609            union BadUnion =
610              | TypeA
611              | TypeB
612              | TypeA
613            """
614        )
615
616        assert validate_schema(schema) == [
617            {
618                "message": "Union type BadUnion can only include type TypeA once.",
619                "locations": [(15, 17), (17, 17)],
620            }
621        ]
622
623        schema = extend_schema(schema, parse("extend union BadUnion = TypeB"))
624
625        assert validate_schema(schema) == [
626            {
627                "message": "Union type BadUnion can only include type TypeA once.",
628                "locations": [(15, 17), (17, 17)],
629            },
630            {
631                "message": "Union type BadUnion can only include type TypeB once.",
632                "locations": [(16, 17), (1, 25)],
633            },
634        ]
635
636    def rejects_a_union_type_with_non_object_member_types():
637        # invalid schema cannot be built with Python
638        with raises(TypeError) as exc_info:
639            build_schema(
640                """
641                type Query {
642                  test: BadUnion
643                }
644
645                type TypeA {
646                  field: String
647                }
648
649                type TypeB {
650                  field: String
651                }
652
653                union BadUnion =
654                  | TypeA
655                  | String
656                  | TypeB
657                """
658            )
659        assert str(exc_info.value) == (
660            "BadUnion types must be specified"
661            " as a collection of GraphQLObjectType instances."
662        )
663        # construct invalid schema manually
664        schema = build_schema(
665            """
666            type Query {
667              test: BadUnion
668            }
669
670            type TypeA {
671              field: String
672            }
673
674            type TypeB {
675              field: String
676            }
677
678            union BadUnion =
679              | TypeA
680              | TypeA
681              | TypeB
682            """
683        )
684        with raises(TypeError) as exc_info:
685            extend_schema(schema, parse("extend union BadUnion = Int"))
686        assert str(exc_info.value) == (
687            "BadUnion types must be specified"
688            " as a collection of GraphQLObjectType instances."
689        )
690        schema = extend_schema(schema, parse("extend union BadUnion = TypeB"))
691        bad_union: Any = schema.get_type("BadUnion")
692        assert bad_union.types[1].name == "TypeA"
693        bad_union.types[1] = GraphQLString
694        assert bad_union.types[3].name == "TypeB"
695        bad_union.types[3] = GraphQLInt
696        bad_union.ast_node.types[1].name.value = "String"
697        bad_union.extension_ast_nodes[0].types[0].name.value = "Int"
698        assert validate_schema(schema) == [
699            {
700                "message": "Union type BadUnion can only include Object types,"
701                " it cannot include String.",
702                "locations": [(16, 17)],
703            },
704            {
705                "message": "Union type BadUnion can only include Object types,"
706                " it cannot include Int.",
707                "locations": [(1, 25)],
708            },
709        ]
710
711        bad_union_member_types = [
712            GraphQLString,
713            GraphQLNonNull(SomeObjectType),
714            GraphQLList(SomeObjectType),
715            SomeInterfaceType,
716            SomeUnionType,
717            SomeEnumType,
718            SomeInputObjectType,
719        ]
720        for member_type in bad_union_member_types:
721            # invalid union type cannot be built with Python
722            bad_union = GraphQLUnionType(
723                "BadUnion", types=[member_type]  # type: ignore
724            )
725            with raises(TypeError) as exc_info:
726                schema_with_field_type(bad_union)
727            assert str(exc_info.value) == (
728                "BadUnion types must be specified"
729                " as a collection of GraphQLObjectType instances."
730            )
731            # noinspection PyPropertyAccess
732            bad_union.types = []
733            bad_schema = schema_with_field_type(bad_union)
734            # noinspection PyPropertyAccess
735            bad_union.types = [member_type]
736            assert validate_schema(bad_schema) == [
737                {
738                    "message": "Union type BadUnion can only include Object types,"
739                    + f" it cannot include {inspect(member_type)}."
740                }
741            ]
742
743
744def describe_type_system_input_objects_must_have_fields():
745    def accepts_an_input_object_type_with_fields():
746        schema = build_schema(
747            """
748            type Query {
749               field(arg: SomeInputObject): String
750            }
751
752            input SomeInputObject {
753              field: String
754            }
755            """
756        )
757        assert validate_schema(schema) == []
758
759    def rejects_an_input_object_type_with_missing_fields():
760        schema = build_schema(
761            """
762            type Query {
763              field(arg: SomeInputObject): String
764            }
765
766            input SomeInputObject
767            """
768        )
769        schema = extend_schema(
770            schema,
771            parse(
772                """
773                directive @test on INPUT_OBJECT
774
775                extend input SomeInputObject @test
776                """
777            ),
778        )
779        assert validate_schema(schema) == [
780            {
781                "message": "Input Object type SomeInputObject"
782                " must define one or more fields.",
783                "locations": [(6, 13), (4, 17)],
784            }
785        ]
786
787    def accepts_an_input_object_with_breakable_circular_reference():
788        schema = build_schema(
789            """
790            type Query {
791              field(arg: SomeInputObject): String
792            }
793
794            input SomeInputObject {
795              self: SomeInputObject
796              arrayOfSelf: [SomeInputObject]
797              nonNullArrayOfSelf: [SomeInputObject]!
798              nonNullArrayOfNonNullSelf: [SomeInputObject!]!
799              intermediateSelf: AnotherInputObject
800            }
801
802            input AnotherInputObject {
803              parent: SomeInputObject
804            }
805            """
806        )
807        assert validate_schema(schema) == []
808
809    def rejects_an_input_object_with_non_breakable_circular_reference():
810        schema = build_schema(
811            """
812            type Query {
813              field(arg: SomeInputObject): String
814            }
815
816            input SomeInputObject {
817              startLoop: AnotherInputObject!
818            }
819
820            input AnotherInputObject {
821              nextInLoop: YetAnotherInputObject!
822            }
823
824            input YetAnotherInputObject {
825              closeLoop: SomeInputObject!
826            }
827            """
828        )
829        assert validate_schema(schema) == [
830            {
831                "message": "Cannot reference Input Object 'SomeInputObject'"
832                " within itself through a series of non-null fields:"
833                " 'startLoop.nextInLoop.closeLoop'.",
834                "locations": [(7, 15), (11, 15), (15, 15)],
835            }
836        ]
837
838    def rejects_an_input_object_with_multiple_non_breakable_circular_reference():
839        schema = build_schema(
840            """
841            type Query {
842              field(arg: SomeInputObject): String
843            }
844
845            input SomeInputObject {
846              startLoop: AnotherInputObject!
847            }
848
849            input AnotherInputObject {
850              closeLoop: SomeInputObject!
851              startSecondLoop: YetAnotherInputObject!
852            }
853
854            input YetAnotherInputObject {
855              closeSecondLoop: AnotherInputObject!
856              nonNullSelf: YetAnotherInputObject!
857            }
858            """
859        )
860        assert validate_schema(schema) == [
861            {
862                "message": "Cannot reference Input Object 'SomeInputObject'"
863                " within itself through a series of non-null fields:"
864                " 'startLoop.closeLoop'.",
865                "locations": [(7, 15), (11, 15)],
866            },
867            {
868                "message": "Cannot reference Input Object 'AnotherInputObject'"
869                " within itself through a series of non-null fields:"
870                " 'startSecondLoop.closeSecondLoop'.",
871                "locations": [(12, 15), (16, 15)],
872            },
873            {
874                "message": "Cannot reference Input Object 'YetAnotherInputObject'"
875                " within itself through a series of non-null fields:"
876                " 'nonNullSelf'.",
877                "locations": [(17, 15)],
878            },
879        ]
880
881    def rejects_an_input_object_type_with_incorrectly_typed_fields():
882        # invalid schema cannot be built with Python
883        with raises(TypeError) as exc_info:
884            build_schema(
885                """
886                type Query {
887                  field(arg: SomeInputObject): String
888                }
889
890                type SomeObject {
891                  field: String
892                }
893
894                union SomeUnion = SomeObject
895
896                input SomeInputObject {
897                  badObject: SomeObject
898                  badUnion: SomeUnion
899                  goodInputObject: SomeInputObject
900                }
901                """
902            )
903        assert str(exc_info.value) == (
904            "SomeInputObject fields cannot be resolved."
905            " Input field type must be a GraphQL input type."
906        )
907        # construct invalid schema manually
908        schema = build_schema(
909            """
910            type Query {
911              field(arg: SomeInputObject): String
912            }
913
914            type SomeObject {
915              field: String
916            }
917
918            union SomeUnion = SomeObject
919
920            input SomeInputObject {
921              badObject: SomeInputObject
922              badUnion: SomeInputObject
923              goodInputObject: SomeInputObject
924            }
925            """
926        )
927        some_input_obj: Any = schema.get_type("SomeInputObject")
928        some_input_obj.fields["badObject"].type = schema.get_type("SomeObject")
929        some_input_obj.fields["badUnion"].type = schema.get_type("SomeUnion")
930        assert validate_schema(schema) == [
931            {
932                "message": "The type of SomeInputObject.badObject must be Input Type"
933                " but got: SomeObject.",
934                "locations": [(13, 26)],
935            },
936            {
937                "message": "The type of SomeInputObject.badUnion must be Input Type"
938                " but got: SomeUnion.",
939                "locations": [(14, 25)],
940            },
941        ]
942
943    def rejects_an_input_object_type_with_required_arguments_that_is_deprecated():
944        schema = build_schema(
945            """
946            type Query {
947              field(arg: SomeInputObject): String
948            }
949
950            input SomeInputObject {
951              badField: String! @deprecated
952              optionalField: String @deprecated
953              anotherOptionalField: String! = "" @deprecated
954            }
955            """
956        )
957        assert validate_schema(schema) == [
958            {
959                "message": "Required input field SomeInputObject.badField"
960                " cannot be deprecated.",
961                "locations": [(7, 33), (7, 25)],
962            }
963        ]
964
965
966def describe_type_system_enum_types_must_be_well_defined():
967    def rejects_an_enum_type_without_values():
968        schema = build_schema(
969            """
970            type Query {
971              field: SomeEnum
972            }
973
974            enum SomeEnum
975            """
976        )
977
978        schema = extend_schema(
979            schema,
980            parse(
981                """
982                directive @test on ENUM
983
984                extend enum SomeEnum @test
985                """
986            ),
987        )
988
989        assert validate_schema(schema) == [
990            {
991                "message": "Enum type SomeEnum must define one or more values.",
992                "locations": [(6, 13), (4, 17)],
993            }
994        ]
995
996    def rejects_an_enum_type_with_incorrectly_named_values():
997        def schema_with_enum(name: str) -> GraphQLSchema:
998            return schema_with_field_type(
999                GraphQLEnumType("SomeEnum", {name: GraphQLEnumValue(1)})
1000            )
1001
1002        schema1 = schema_with_enum("#value")
1003        msg = validate_schema(schema1)[0].message
1004        assert msg == (
1005            "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but '#value' does not."
1006        )
1007
1008        schema2 = schema_with_enum("1value")
1009        msg = validate_schema(schema2)[0].message
1010        assert msg == (
1011            "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but '1value' does not."
1012        )
1013
1014        schema3 = schema_with_enum("KEBAB-CASE")
1015        msg = validate_schema(schema3)[0].message
1016        assert msg == (
1017            "Names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/ but 'KEBAB-CASE' does not."
1018        )
1019
1020        schema4 = schema_with_enum("true")
1021        msg = validate_schema(schema4)[0].message
1022        assert msg == "Enum type SomeEnum cannot include value: true."
1023
1024        schema5 = schema_with_enum("false")
1025        msg = validate_schema(schema5)[0].message
1026        assert msg == "Enum type SomeEnum cannot include value: false."
1027
1028        schema6 = schema_with_enum("null")
1029        msg = validate_schema(schema6)[0].message
1030        assert msg == "Enum type SomeEnum cannot include value: null."
1031
1032
1033def describe_type_system_object_fields_must_have_output_types():
1034    def _schema_with_object_field(type_: GraphQLOutputType) -> GraphQLSchema:
1035        if is_output_type(type_):
1036            field = GraphQLField(type_)
1037        else:
1038            # invalid field cannot be built with Python directly
1039            with raises(TypeError) as exc_info:
1040                GraphQLField(type_)
1041            assert str(exc_info.value) == "Field type must be an output type."
1042            # therefore we need to monkey-patch a valid field
1043            field = GraphQLField(GraphQLString)
1044            field.type = type_
1045        bad_object_type = GraphQLObjectType("BadObject", {"badField": field})
1046        return GraphQLSchema(
1047            GraphQLObjectType("Query", {"f": GraphQLField(bad_object_type)}),
1048            types=[SomeObjectType],
1049        )
1050
1051    @mark.parametrize("type_", output_types, ids=get_name)
1052    def accepts_an_output_type_as_an_object_field_type(type_):
1053        schema = _schema_with_object_field(type_)
1054        assert validate_schema(schema) == []
1055
1056    def rejects_an_empty_object_field_type():
1057        # noinspection PyTypeChecker
1058        schema = _schema_with_object_field(None)  # type: ignore
1059        assert validate_schema(schema) == [
1060            {
1061                "message": "The type of BadObject.badField must be Output Type"
1062                " but got: None."
1063            }
1064        ]
1065
1066    @mark.parametrize("type_", not_output_types, ids=get_name)
1067    def rejects_a_non_output_type_as_an_object_field_type(type_):
1068        schema = _schema_with_object_field(type_)
1069        assert validate_schema(schema) == [
1070            {
1071                "message": "The type of BadObject.badField must be Output Type"
1072                f" but got: {type_}."
1073            }
1074        ]
1075
1076    @mark.parametrize("type_", not_graphql_types, ids=get_name)
1077    def rejects_a_non_type_value_as_an_object_field_type(type_):
1078        schema = _schema_with_object_field(type_)
1079        assert validate_schema(schema) == [
1080            {
1081                "message": "The type of BadObject.badField must be Output Type"
1082                f" but got: {inspect(type_)}.",
1083            },
1084            {"message": f"Expected GraphQL named type but got: {inspect(type_)}."},
1085        ]
1086
1087    def rejects_with_relevant_locations_for_a_non_output_type():
1088        # invalid schema cannot be built with Python
1089        with raises(TypeError) as exc_info:
1090            build_schema(
1091                """
1092                type Query {
1093                  field: [SomeInputObject]
1094                }
1095
1096                input SomeInputObject {
1097                  field: String
1098                }
1099                """
1100            )
1101        assert str(exc_info.value) == (
1102            "Query fields cannot be resolved. Field type must be an output type."
1103        )
1104        # therefore we need to monkey-patch a valid schema
1105        schema = build_schema(
1106            """
1107            type Query {
1108              field: [String]
1109            }
1110
1111            input SomeInputObject {
1112              field: String
1113            }
1114            """
1115        )
1116        some_input_obj = schema.get_type("SomeInputObject")
1117        schema.query_type.fields["field"].type.of_type = some_input_obj  # type: ignore
1118        assert validate_schema(schema) == [
1119            {
1120                "message": "The type of Query.field must be Output Type"
1121                " but got: [SomeInputObject].",
1122                "locations": [(3, 22)],
1123            }
1124        ]
1125
1126
1127def describe_type_system_objects_can_only_implement_unique_interfaces():
1128    def rejects_an_object_implementing_a_non_type_values():
1129        query_type = GraphQLObjectType(
1130            "BadObject", {"f": GraphQLField(GraphQLString)}, interfaces=[]
1131        )
1132        # noinspection PyTypeChecker
1133        query_type.interfaces.append(None)
1134        schema = GraphQLSchema(query_type)
1135
1136        assert validate_schema(schema) == [
1137            {
1138                "message": "Type BadObject must only implement Interface types,"
1139                " it cannot implement None."
1140            }
1141        ]
1142
1143    def rejects_an_object_implementing_a_non_interface_type():
1144        # invalid schema cannot be built with Python
1145        with raises(TypeError) as exc_info:
1146            build_schema(
1147                """
1148                type Query {
1149                  test: BadObject
1150                }
1151
1152                input SomeInputObject {
1153                  field: String
1154                }
1155
1156                type BadObject implements SomeInputObject {
1157                  field: String
1158                }
1159                """
1160            )
1161        assert str(exc_info.value) == (
1162            "BadObject interfaces must be specified"
1163            " as a collection of GraphQLInterfaceType instances."
1164        )
1165
1166    def rejects_an_object_implementing_the_same_interface_twice():
1167        schema = build_schema(
1168            """
1169            type Query {
1170              test: AnotherObject
1171            }
1172
1173            interface AnotherInterface {
1174              field: String
1175            }
1176
1177            type AnotherObject implements AnotherInterface & AnotherInterface {
1178              field: String
1179            }
1180            """
1181        )
1182        assert validate_schema(schema) == [
1183            {
1184                "message": "Type AnotherObject can only implement"
1185                " AnotherInterface once.",
1186                "locations": [(10, 43), (10, 62)],
1187            }
1188        ]
1189
1190    def rejects_an_object_implementing_same_interface_twice_due_to_extension():
1191        schema = build_schema(
1192            """
1193            type Query {
1194              test: AnotherObject
1195            }
1196
1197            interface AnotherInterface {
1198              field: String
1199            }
1200
1201            type AnotherObject implements AnotherInterface {
1202              field: String
1203            }
1204            """
1205        )
1206        extended_schema = extend_schema(
1207            schema, parse("extend type AnotherObject implements AnotherInterface")
1208        )
1209        assert validate_schema(extended_schema) == [
1210            {
1211                "message": "Type AnotherObject can only implement"
1212                " AnotherInterface once.",
1213                "locations": [(10, 43), (1, 38)],
1214            }
1215        ]
1216
1217
1218def describe_type_system_interface_extensions_should_be_valid():
1219    def rejects_object_implementing_extended_interface_due_to_missing_field():
1220        schema = build_schema(
1221            """
1222            type Query {
1223              test: AnotherObject
1224            }
1225
1226            interface AnotherInterface {
1227              field: String
1228            }
1229
1230            type AnotherObject implements AnotherInterface {
1231              field: String
1232            }
1233            """
1234        )
1235        extended_schema = extend_schema(
1236            schema,
1237            parse(
1238                """
1239                extend interface AnotherInterface {
1240                  newField: String
1241                }
1242
1243                extend type AnotherObject {
1244                  differentNewField: String
1245                }
1246                """
1247            ),
1248        )
1249        assert validate_schema(extended_schema) == [
1250            {
1251                "message": "Interface field AnotherInterface.newField expected"
1252                " but AnotherObject does not provide it.",
1253                "locations": [(3, 19), (10, 13), (6, 17)],
1254            }
1255        ]
1256
1257    def rejects_object_implementing_extended_interface_due_to_missing_args():
1258        schema = build_schema(
1259            """
1260            type Query {
1261              test: AnotherObject
1262            }
1263
1264            interface AnotherInterface {
1265              field: String
1266            }
1267
1268            type AnotherObject implements AnotherInterface {
1269              field: String
1270            }
1271            """
1272        )
1273        extended_schema = extend_schema(
1274            schema,
1275            parse(
1276                """
1277                extend interface AnotherInterface {
1278                  newField(test: Boolean): String
1279                }
1280
1281                extend type AnotherObject {
1282                  newField: String
1283                }
1284                """
1285            ),
1286        )
1287        assert validate_schema(extended_schema) == [
1288            {
1289                "message": "Interface field argument"
1290                " AnotherInterface.newField(test:) expected"
1291                " but AnotherObject.newField does not provide it.",
1292                "locations": [(3, 28), (7, 19)],
1293            }
1294        ]
1295
1296    def rejects_object_implementing_extended_interface_due_to_type_mismatch():
1297        schema = build_schema(
1298            """
1299            type Query {
1300              test: AnotherObject
1301            }
1302
1303            interface AnotherInterface {
1304              field: String
1305            }
1306
1307            type AnotherObject implements AnotherInterface {
1308              field: String
1309            }
1310            """
1311        )
1312        extended_schema = extend_schema(
1313            schema,
1314            parse(
1315                """
1316                extend interface AnotherInterface {
1317                  newInterfaceField: NewInterface
1318                }
1319
1320                interface NewInterface {
1321                  newField: String
1322                }
1323
1324                interface MismatchingInterface {
1325                  newField: String
1326                }
1327
1328                extend type AnotherObject {
1329                  newInterfaceField: MismatchingInterface
1330                }
1331
1332                # Required to prevent unused interface errors
1333                type DummyObject implements NewInterface & MismatchingInterface {
1334                  newField: String
1335                }
1336                """
1337            ),
1338        )
1339        assert validate_schema(extended_schema) == [
1340            {
1341                "message": "Interface field AnotherInterface.newInterfaceField"
1342                " expects type NewInterface"
1343                " but AnotherObject.newInterfaceField"
1344                " is type MismatchingInterface.",
1345                "locations": [(3, 38), (15, 38)],
1346            }
1347        ]
1348
1349
1350def describe_type_system_interface_fields_must_have_output_types():
1351    def _schema_with_interface_field(type_: GraphQLOutputType) -> GraphQLSchema:
1352        if is_output_type(type_):
1353            field = GraphQLField(type_)
1354        else:
1355            # invalid field cannot be built with Python directly
1356            with raises(TypeError) as exc_info:
1357                GraphQLField(type_)
1358            assert str(exc_info.value) == "Field type must be an output type."
1359            # therefore we need to monkey-patch a valid field
1360            field = GraphQLField(GraphQLString)
1361            field.type = type_
1362        fields = {"badField": field}
1363
1364        bad_interface_type = GraphQLInterfaceType("BadInterface", fields)
1365        bad_implementing_type = GraphQLObjectType(
1366            "BadImplementing",
1367            fields,
1368            interfaces=[bad_interface_type],
1369        )
1370        return GraphQLSchema(
1371            GraphQLObjectType("Query", {"f": GraphQLField(bad_interface_type)}),
1372            types=[bad_implementing_type, SomeObjectType],
1373        )
1374
1375    @mark.parametrize("type_", output_types, ids=get_name)
1376    def accepts_an_output_type_as_an_interface_field_type(type_):
1377        schema = _schema_with_interface_field(type_)
1378        assert validate_schema(schema) == []
1379
1380    def rejects_an_empty_interface_field_type():
1381        # noinspection PyTypeChecker
1382        schema = _schema_with_interface_field(None)  # type: ignore
1383        assert validate_schema(schema) == [
1384            {
1385                "message": "The type of BadImplementing.badField must be Output Type"
1386                " but got: None.",
1387            },
1388            {
1389                "message": "The type of BadInterface.badField must be Output Type"
1390                " but got: None.",
1391            },
1392        ]
1393
1394    @mark.parametrize("type_", not_output_types, ids=get_name)
1395    def rejects_a_non_output_type_as_an_interface_field_type(type_):
1396        schema = _schema_with_interface_field(type_)
1397        assert validate_schema(schema) == [
1398            {
1399                "message": "The type of BadImplementing.badField must be Output Type"
1400                f" but got: {type_}.",
1401            },
1402            {
1403                "message": "The type of BadInterface.badField must be Output Type"
1404                f" but got: {type_}.",
1405            },
1406        ]
1407
1408    @mark.parametrize("type_", not_graphql_types, ids=get_name)
1409    def rejects_a_non_type_value_as_an_interface_field_type(type_):
1410        schema = _schema_with_interface_field(type_)
1411        assert validate_schema(schema) == [
1412            {
1413                "message": "The type of BadImplementing.badField must be Output Type"
1414                f" but got: {inspect(type_)}.",
1415            },
1416            {
1417                "message": "The type of BadInterface.badField must be Output Type"
1418                f" but got: {inspect(type_)}.",
1419            },
1420            {"message": f"Expected GraphQL named type but got: {inspect(type_)}."},
1421        ]
1422
1423    def rejects_a_non_output_type_as_an_interface_field_with_locations():
1424        # invalid schema cannot be built with Python
1425        with raises(TypeError) as exc_info:
1426            build_schema(
1427                """
1428                type Query {
1429                  test: SomeInterface
1430                }
1431
1432                interface SomeInterface {
1433                  field: SomeInputObject
1434                }
1435
1436                input SomeInputObject {
1437                  foo: String
1438                }
1439
1440                type SomeObject implements SomeInterface {
1441                  field: SomeInputObject
1442                }
1443                """
1444            )
1445        assert str(exc_info.value) == (
1446            "SomeInterface fields cannot be resolved."
1447            " Field type must be an output type."
1448        )
1449        # therefore we need to monkey-patch a valid schema
1450        schema = build_schema(
1451            """
1452            type Query {
1453              test: SomeInterface
1454            }
1455
1456            interface SomeInterface {
1457              field: String
1458            }
1459
1460            input SomeInputObject {
1461              foo: String
1462            }
1463
1464            type SomeObject implements SomeInterface {
1465              field: String
1466            }
1467            """
1468        )
1469        # therefore we need to monkey-patch a valid schema
1470        some_input_obj = schema.get_type("SomeInputObject")
1471        some_interface: Any = schema.get_type("SomeInterface")
1472        some_interface.fields["field"].type = some_input_obj
1473        some_object: Any = schema.get_type("SomeObject")
1474        some_object.fields["field"].type = some_input_obj
1475        assert validate_schema(schema) == [
1476            {
1477                "message": "The type of SomeInterface.field must be Output Type"
1478                " but got: SomeInputObject.",
1479                "locations": [(7, 22)],
1480            },
1481            {
1482                "message": "The type of SomeObject.field must be Output Type"
1483                " but got: SomeInputObject.",
1484                "locations": [(15, 22)],
1485            },
1486        ]
1487
1488    def accepts_an_interface_not_implemented_by_at_least_one_object():
1489        schema = build_schema(
1490            """
1491            type Query {
1492              test: SomeInterface
1493            }
1494
1495            interface SomeInterface {
1496              foo: String
1497            }
1498            """
1499        )
1500        assert validate_schema(schema) == []
1501
1502
1503def describe_type_system_arguments_must_have_input_types():
1504    def _schema_with_arg(type_: GraphQLInputType) -> GraphQLSchema:
1505        if is_input_type(type_):
1506            argument = GraphQLArgument(type_)
1507        else:
1508            # invalid argument cannot be built with Python directly
1509            with raises(TypeError) as exc_info:
1510                GraphQLArgument(type_)
1511            assert str(exc_info.value) == "Argument type must be a GraphQL input type."
1512            # therefore we need to monkey-patch a valid argument
1513            argument = GraphQLArgument(GraphQLString)
1514            argument.type = type_
1515        args = {"badArg": argument}
1516        bad_object_type = GraphQLObjectType(
1517            "BadObject",
1518            {"badField": GraphQLField(GraphQLString, args)},
1519        )
1520        return GraphQLSchema(
1521            GraphQLObjectType("Query", {"f": GraphQLField(bad_object_type)}),
1522            directives=[
1523                GraphQLDirective(
1524                    "BadDirective",
1525                    [DirectiveLocation.QUERY],
1526                    args,
1527                )
1528            ],
1529        )
1530
1531    @mark.parametrize("type_", input_types, ids=get_name)
1532    def accepts_an_input_type_as_a_field_arg_type(type_):
1533        schema = _schema_with_arg(type_)
1534        assert validate_schema(schema) == []
1535
1536    def rejects_an_empty_field_arg_type():
1537        # noinspection PyTypeChecker
1538        schema = _schema_with_arg(None)  # type: ignore
1539        assert validate_schema(schema) == [
1540            {
1541                "message": "The type of @BadDirective(badArg:) must be Input Type"
1542                " but got: None."
1543            },
1544            {
1545                "message": "The type of BadObject.badField(badArg:) must be Input Type"
1546                " but got: None."
1547            },
1548        ]
1549
1550    @mark.parametrize("type_", not_input_types, ids=get_name)
1551    def rejects_a_non_input_type_as_a_field_arg_type(type_):
1552        schema = _schema_with_arg(type_)
1553        assert validate_schema(schema) == [
1554            {
1555                "message": "The type of @BadDirective(badArg:) must be Input Type"
1556                f" but got: {type_}."
1557            },
1558            {
1559                "message": "The type of BadObject.badField(badArg:) must be Input Type"
1560                f" but got: {type_}."
1561            },
1562        ]
1563
1564    @mark.parametrize("type_", not_graphql_types, ids=get_name)
1565    def rejects_a_non_type_value_as_a_field_arg_type(type_):
1566        schema = _schema_with_arg(type_)
1567        assert validate_schema(schema) == [
1568            {
1569                "message": "The type of @BadDirective(badArg:) must be Input Type"
1570                f" but got: {inspect(type_)}."
1571            },
1572            {
1573                "message": "The type of BadObject.badField(badArg:) must be Input Type"
1574                f" but got: {inspect(type_)}."
1575            },
1576            {"message": f"Expected GraphQL named type but got: {inspect(type_)}."},
1577        ]
1578
1579    def rejects_a_required_argument_that_is_deprecated():
1580        schema = build_schema(
1581            """
1582            directive @BadDirective(
1583              badArg: String! @deprecated
1584              optionalArg: String @deprecated
1585              anotherOptionalArg: String! = "" @deprecated
1586            ) on FIELD
1587
1588            type Query {
1589              test(
1590                badArg: String! @deprecated
1591                optionalArg: String @deprecated
1592                anotherOptionalArg: String! = "" @deprecated
1593              ): String
1594            }
1595            """
1596        )
1597        assert validate_schema(schema) == [
1598            {
1599                "message": "Required argument @BadDirective(badArg:)"
1600                " cannot be deprecated.",
1601                "locations": [(3, 31), (3, 23)],
1602            },
1603            {
1604                "message": "Required argument Query.test(badArg:)"
1605                " cannot be deprecated.",
1606                "locations": [(10, 33), (10, 25)],
1607            },
1608        ]
1609
1610    def rejects_a_non_input_type_as_a_field_arg_with_locations():
1611        # invalid schema cannot be built with Python
1612        with raises(TypeError) as exc_info:
1613            build_schema(
1614                """
1615                type Query {
1616                  test(arg: SomeObject): String
1617                }
1618
1619                type SomeObject {
1620                  foo: String
1621                }
1622                """
1623            )
1624        assert str(exc_info.value) == (
1625            "Query fields cannot be resolved."
1626            " Argument type must be a GraphQL input type."
1627        )
1628        # therefore we need to monkey-patch a valid schema
1629        schema = build_schema(
1630            """
1631            type Query {
1632              test(arg: String): String
1633            }
1634
1635            type SomeObject {
1636              foo: String
1637            }
1638            """
1639        )
1640        some_object = schema.get_type("SomeObject")
1641        schema.query_type.fields["test"].args["arg"].type = some_object  # type: ignore
1642        assert validate_schema(schema) == [
1643            {
1644                "message": "The type of Query.test(arg:) must be Input Type"
1645                " but got: SomeObject.",
1646                "locations": [(3, 25)],
1647            },
1648        ]
1649
1650
1651def describe_type_system_input_object_fields_must_have_input_types():
1652    def _schema_with_input_field(type_: GraphQLInputType) -> GraphQLSchema:
1653        if is_input_type(type_):
1654            input_field = GraphQLInputField(type_)
1655        else:
1656            # invalid input field cannot be built with Python directly
1657            with raises(TypeError) as exc_info:
1658                GraphQLInputField(type_)
1659            assert str(exc_info.value) == (
1660                "Input field type must be a GraphQL input type."
1661            )
1662            # therefore we need to monkey-patch a valid input field
1663            input_field = GraphQLInputField(GraphQLString)
1664            input_field.type = type_
1665        bad_input_object_type = GraphQLInputObjectType(
1666            "BadInputObject", {"badField": input_field}
1667        )
1668        return GraphQLSchema(
1669            GraphQLObjectType(
1670                "Query",
1671                {
1672                    "f": GraphQLField(
1673                        GraphQLString,
1674                        args={"badArg": GraphQLArgument(bad_input_object_type)},
1675                    )
1676                },
1677            )
1678        )
1679
1680    @mark.parametrize("type_", input_types, ids=get_name)
1681    def accepts_an_input_type_as_an_input_field_type(type_):
1682        schema = _schema_with_input_field(type_)
1683        assert validate_schema(schema) == []
1684
1685    def rejects_an_empty_input_field_type():
1686        # noinspection PyTypeChecker
1687        schema = _schema_with_input_field(None)  # type: ignore
1688        assert validate_schema(schema) == [
1689            {
1690                "message": "The type of BadInputObject.badField must be Input Type"
1691                " but got: None."
1692            }
1693        ]
1694
1695    @mark.parametrize("type_", not_input_types, ids=get_name)
1696    def rejects_a_non_input_type_as_an_input_field_type(type_):
1697        schema = _schema_with_input_field(type_)
1698        assert validate_schema(schema) == [
1699            {
1700                "message": "The type of BadInputObject.badField must be Input Type"
1701                f" but got: {type_}."
1702            }
1703        ]
1704
1705    @mark.parametrize("type_", not_graphql_types, ids=get_name)
1706    def rejects_a_non_type_value_as_an_input_field_type(type_):
1707        schema = _schema_with_input_field(type_)
1708        assert validate_schema(schema) == [
1709            {
1710                "message": "The type of BadInputObject.badField must be Input Type"
1711                f" but got: {inspect(type_)}."
1712            },
1713            {"message": f"Expected GraphQL named type but got: {inspect(type_)}."},
1714        ]
1715
1716    def rejects_with_relevant_locations_for_a_non_input_type():
1717        # invalid schema cannot be built with Python
1718        with raises(TypeError) as exc_info:
1719            build_schema(
1720                """
1721                type Query {
1722                  test(arg: SomeInputObject): String
1723                }
1724
1725                input SomeInputObject {
1726                  foo: SomeObject
1727                }
1728
1729                type SomeObject {
1730                  bar: String
1731                }
1732                """
1733            )
1734        assert str(exc_info.value) == (
1735            "SomeInputObject fields cannot be resolved."
1736            " Input field type must be a GraphQL input type."
1737        )
1738        # therefore we need to monkey-patch a valid schema
1739        schema = build_schema(
1740            """
1741            type Query {
1742              test(arg: SomeInputObject): String
1743            }
1744
1745            input SomeInputObject {
1746              foo: String
1747            }
1748
1749            type SomeObject {
1750              bar: String
1751            }
1752            """
1753        )
1754        some_object = schema.get_type("SomeObject")
1755        some_input_object: Any = schema.get_type("SomeInputObject")
1756        some_input_object.fields["foo"].type = some_object
1757        assert validate_schema(schema) == [
1758            {
1759                "message": "The type of SomeInputObject.foo must be Input Type"
1760                " but got: SomeObject.",
1761                "locations": [(7, 20)],
1762            }
1763        ]
1764
1765
1766def describe_objects_must_adhere_to_interfaces_they_implement():
1767    def accepts_an_object_which_implements_an_interface():
1768        schema = build_schema(
1769            """
1770            type Query {
1771              test: AnotherObject
1772            }
1773
1774            interface AnotherInterface {
1775              field(input: String): String
1776            }
1777
1778            type AnotherObject implements AnotherInterface {
1779              field(input: String): String
1780            }
1781            """
1782        )
1783        assert validate_schema(schema) == []
1784
1785    def accepts_an_object_which_implements_an_interface_and_with_more_fields():
1786        schema = build_schema(
1787            """
1788            type Query {
1789              test: AnotherObject
1790            }
1791
1792            interface AnotherInterface {
1793              field(input: String): String
1794            }
1795
1796            type AnotherObject implements AnotherInterface {
1797              field(input: String): String
1798              anotherField: String
1799            }
1800            """
1801        )
1802        assert validate_schema(schema) == []
1803
1804    def accepts_an_object_which_implements_an_interface_field_with_more_args():
1805        schema = build_schema(
1806            """
1807            type Query {
1808              test: AnotherObject
1809            }
1810
1811            interface AnotherInterface {
1812              field(input: String): String
1813            }
1814
1815            type AnotherObject implements AnotherInterface {
1816              field(input: String, anotherInput: String): String
1817            }
1818            """
1819        )
1820        assert validate_schema(schema) == []
1821
1822    def rejects_an_object_missing_an_interface_field():
1823        schema = build_schema(
1824            """
1825            type Query {
1826              test: AnotherObject
1827            }
1828
1829            interface AnotherInterface {
1830              field(input: String): String
1831            }
1832
1833            type AnotherObject implements AnotherInterface {
1834              anotherField: String
1835            }
1836            """
1837        )
1838        assert validate_schema(schema) == [
1839            {
1840                "message": "Interface field AnotherInterface.field expected but"
1841                " AnotherObject does not provide it.",
1842                "locations": [(7, 15), (10, 13)],
1843            }
1844        ]
1845
1846    def rejects_an_object_with_an_incorrectly_typed_interface_field():
1847        schema = build_schema(
1848            """
1849            type Query {
1850              test: AnotherObject
1851            }
1852
1853            interface AnotherInterface {
1854              field(input: String): String
1855            }
1856
1857            type AnotherObject implements AnotherInterface {
1858              field(input: String): Int
1859            }
1860            """
1861        )
1862        assert validate_schema(schema) == [
1863            {
1864                "message": "Interface field AnotherInterface.field"
1865                " expects type String but"
1866                " AnotherObject.field is type Int.",
1867                "locations": [(7, 37), (11, 37)],
1868            }
1869        ]
1870
1871    def rejects_an_object_with_a_differently_typed_interface_field():
1872        schema = build_schema(
1873            """
1874            type Query {
1875              test: AnotherObject
1876            }
1877
1878            type A { foo: String }
1879            type B { foo: String }
1880
1881            interface AnotherInterface {
1882              field: A
1883            }
1884
1885            type AnotherObject implements AnotherInterface {
1886              field: B
1887            }
1888            """
1889        )
1890        assert validate_schema(schema) == [
1891            {
1892                "message": "Interface field AnotherInterface.field"
1893                " expects type A but AnotherObject.field is type B.",
1894                "locations": [(10, 22), (14, 22)],
1895            }
1896        ]
1897
1898    def accepts_an_object_with_a_subtyped_interface_field_interface():
1899        schema = build_schema(
1900            """
1901            type Query {
1902              test: AnotherObject
1903            }
1904
1905            interface AnotherInterface {
1906              field: AnotherInterface
1907            }
1908
1909            type AnotherObject implements AnotherInterface {
1910              field: AnotherObject
1911            }
1912            """
1913        )
1914        assert validate_schema(schema) == []
1915
1916    def accepts_an_object_with_a_subtyped_interface_field_union():
1917        schema = build_schema(
1918            """
1919            type Query {
1920              test: AnotherObject
1921            }
1922
1923            type SomeObject {
1924              field: String
1925            }
1926
1927            union SomeUnionType = SomeObject
1928
1929            interface AnotherInterface {
1930              field: SomeUnionType
1931            }
1932
1933            type AnotherObject implements AnotherInterface {
1934              field: SomeObject
1935            }
1936            """
1937        )
1938        assert validate_schema(schema) == []
1939
1940    def rejects_an_object_missing_an_interface_argument():
1941        schema = build_schema(
1942            """
1943            type Query {
1944              test: AnotherObject
1945            }
1946
1947            interface AnotherInterface {
1948              field(input: String): String
1949            }
1950
1951            type AnotherObject implements AnotherInterface {
1952              field: String
1953            }
1954            """
1955        )
1956        assert validate_schema(schema) == [
1957            {
1958                "message": "Interface field argument"
1959                " AnotherInterface.field(input:) expected"
1960                " but AnotherObject.field does not provide it.",
1961                "locations": [(7, 21), (11, 15)],
1962            }
1963        ]
1964
1965    def rejects_an_object_with_an_incorrectly_typed_interface_argument():
1966        schema = build_schema(
1967            """
1968            type Query {
1969              test: AnotherObject
1970            }
1971
1972            interface AnotherInterface {
1973              field(input: String): String
1974            }
1975
1976            type AnotherObject implements AnotherInterface {
1977              field(input: Int): String
1978            }
1979            """
1980        )
1981        assert validate_schema(schema) == [
1982            {
1983                "message": "Interface field argument"
1984                " AnotherInterface.field(input:) expects type String"
1985                " but AnotherObject.field(input:) is type Int.",
1986                "locations": [(7, 28), (11, 28)],
1987            }
1988        ]
1989
1990    def rejects_an_object_with_an_incorrectly_typed_field_and_argument():
1991        schema = build_schema(
1992            """
1993            type Query {
1994              test: AnotherObject
1995            }
1996
1997            interface AnotherInterface {
1998              field(input: String): String
1999            }
2000
2001            type AnotherObject implements AnotherInterface {
2002              field(input: Int): Int
2003            }
2004            """
2005        )
2006        assert validate_schema(schema) == [
2007            {
2008                "message": "Interface field AnotherInterface.field expects"
2009                " type String but AnotherObject.field is type Int.",
2010                "locations": [(7, 37), (11, 34)],
2011            },
2012            {
2013                "message": "Interface field argument"
2014                " AnotherInterface.field(input:) expects type String"
2015                " but AnotherObject.field(input:) is type Int.",
2016                "locations": [(7, 28), (11, 28)],
2017            },
2018        ]
2019
2020    def rejects_object_implementing_an_interface_field_with_additional_args():
2021        schema = build_schema(
2022            """
2023            type Query {
2024              test: AnotherObject
2025            }
2026
2027            interface AnotherInterface {
2028              field(baseArg: String): String
2029            }
2030
2031            type AnotherObject implements AnotherInterface {
2032              field(
2033                baseArg: String,
2034                requiredArg: String!
2035                optionalArg1: String,
2036                optionalArg2: String = "",
2037              ): String
2038            }
2039            """
2040        )
2041        assert validate_schema(schema) == [
2042            {
2043                "message": "Object field AnotherObject.field includes required"
2044                " argument requiredArg that is missing from the"
2045                " Interface field AnotherInterface.field.",
2046                "locations": [(13, 17), (7, 15)],
2047            }
2048        ]
2049
2050    def accepts_an_object_with_an_equivalently_wrapped_interface_field_type():
2051        schema = build_schema(
2052            """
2053            type Query {
2054              test: AnotherObject
2055            }
2056
2057            interface AnotherInterface {
2058              field: [String]!
2059            }
2060
2061            type AnotherObject implements AnotherInterface {
2062              field: [String]!
2063            }
2064            """
2065        )
2066        assert validate_schema(schema) == []
2067
2068    def rejects_an_object_with_a_non_list_interface_field_list_type():
2069        schema = build_schema(
2070            """
2071            type Query {
2072              test: AnotherObject
2073            }
2074
2075            interface AnotherInterface {
2076              field: [String]
2077            }
2078
2079            type AnotherObject implements AnotherInterface {
2080              field: String
2081            }
2082            """
2083        )
2084        assert validate_schema(schema) == [
2085            {
2086                "message": "Interface field AnotherInterface.field expects type"
2087                " [String] but AnotherObject.field is type String.",
2088                "locations": [(7, 22), (11, 22)],
2089            }
2090        ]
2091
2092    def rejects_an_object_with_a_list_interface_field_non_list_type():
2093        schema = build_schema(
2094            """
2095            type Query {
2096              test: AnotherObject
2097            }
2098
2099            interface AnotherInterface {
2100              field: String
2101            }
2102
2103            type AnotherObject implements AnotherInterface {
2104              field: [String]
2105            }
2106            """
2107        )
2108        assert validate_schema(schema) == [
2109            {
2110                "message": "Interface field AnotherInterface.field expects type"
2111                " String but AnotherObject.field is type [String].",
2112                "locations": [(7, 22), (11, 22)],
2113            }
2114        ]
2115
2116    def accepts_an_object_with_a_subset_non_null_interface_field_type():
2117        schema = build_schema(
2118            """
2119            type Query {
2120              test: AnotherObject
2121            }
2122
2123            interface AnotherInterface {
2124              field: String
2125            }
2126
2127            type AnotherObject implements AnotherInterface {
2128              field: String!
2129            }
2130            """
2131        )
2132        assert validate_schema(schema) == []
2133
2134    def rejects_an_object_with_a_superset_nullable_interface_field_type():
2135        schema = build_schema(
2136            """
2137            type Query {
2138              test: AnotherObject
2139            }
2140
2141            interface AnotherInterface {
2142              field: String!
2143            }
2144
2145            type AnotherObject implements AnotherInterface {
2146              field: String
2147            }
2148            """
2149        )
2150        assert validate_schema(schema) == [
2151            {
2152                "message": "Interface field AnotherInterface.field expects type"
2153                " String! but AnotherObject.field is type String.",
2154                "locations": [(7, 22), (11, 22)],
2155            }
2156        ]
2157
2158    def rejects_an_object_missing_a_transitive_interface():
2159        schema = build_schema(
2160            """
2161            type Query {
2162              test: AnotherObject
2163            }
2164
2165            interface SuperInterface {
2166              field: String!
2167            }
2168
2169            interface AnotherInterface implements SuperInterface {
2170              field: String!
2171            }
2172
2173            type AnotherObject implements AnotherInterface {
2174              field: String!
2175            }
2176            """
2177        )
2178        assert validate_schema(schema) == [
2179            {
2180                "message": "Type AnotherObject must implement SuperInterface"
2181                " because it is implemented by AnotherInterface.",
2182                "locations": [(10, 51), (14, 43)],
2183            }
2184        ]
2185
2186
2187def describe_interfaces_must_adhere_to_interface_they_implement():
2188    def accepts_an_interface_which_implements_an_interface():
2189        schema = build_schema(
2190            """
2191            type Query {
2192              test: ChildInterface
2193            }
2194
2195            interface ParentInterface {
2196              field(input: String): String
2197            }
2198
2199            interface ChildInterface implements ParentInterface {
2200              field(input: String): String
2201            }
2202            """
2203        )
2204        assert validate_schema(schema) == []
2205
2206    def accepts_an_interface_which_implements_an_interface_along_with_more_fields():
2207        schema = build_schema(
2208            """
2209            type Query {
2210              test: ChildInterface
2211            }
2212
2213            interface ParentInterface {
2214              field(input: String): String
2215            }
2216
2217            interface ChildInterface implements ParentInterface {
2218              field(input: String): String
2219              anotherField: String
2220            }
2221            """
2222        )
2223        assert validate_schema(schema) == []
2224
2225    def accepts_an_interface_which_implements_an_interface_with_additional_args():
2226        schema = build_schema(
2227            """
2228            type Query {
2229              test: ChildInterface
2230            }
2231
2232            interface ParentInterface {
2233              field(input: String): String
2234            }
2235
2236            interface ChildInterface implements ParentInterface {
2237              field(input: String, anotherInput: String): String
2238            }
2239            """
2240        )
2241        assert validate_schema(schema) == []
2242
2243    def rejects_an_interface_missing_an_interface_field():
2244        schema = build_schema(
2245            """
2246            type Query {
2247              test: ChildInterface
2248            }
2249
2250            interface ParentInterface {
2251              field(input: String): String
2252            }
2253
2254            interface ChildInterface implements ParentInterface {
2255              anotherField: String
2256            }
2257            """
2258        )
2259        assert validate_schema(schema) == [
2260            {
2261                "message": "Interface field ParentInterface.field expected"
2262                " but ChildInterface does not provide it.",
2263                "locations": [(7, 15), (10, 13)],
2264            }
2265        ]
2266
2267    def rejects_an_interface_with_an_incorrectly_typed_interface_field():
2268        schema = build_schema(
2269            """
2270            type Query {
2271              test: ChildInterface
2272            }
2273
2274            interface ParentInterface {
2275              field(input: String): String
2276            }
2277
2278            interface ChildInterface implements ParentInterface {
2279              field(input: String): Int
2280            }
2281            """
2282        )
2283        assert validate_schema(schema) == [
2284            {
2285                "message": "Interface field ParentInterface.field expects type String"
2286                " but ChildInterface.field is type Int.",
2287                "locations": [(7, 37), (11, 37)],
2288            }
2289        ]
2290
2291    def rejects_an_interface_with_a_differently_typed_interface_field():
2292        schema = build_schema(
2293            """
2294            type Query {
2295              test: ChildInterface
2296            }
2297
2298            type A { foo: String }
2299            type B { foo: String }
2300
2301            interface ParentInterface {
2302              field: A
2303            }
2304
2305            interface ChildInterface implements ParentInterface {
2306              field: B
2307            }
2308            """
2309        )
2310        assert validate_schema(schema) == [
2311            {
2312                "message": "Interface field ParentInterface.field expects type A"
2313                " but ChildInterface.field is type B.",
2314                "locations": [(10, 22), (14, 22)],
2315            }
2316        ]
2317
2318    def accepts_an_interface_with_a_subtyped_interface_field_interface():
2319        schema = build_schema(
2320            """
2321            type Query {
2322              test: ChildInterface
2323            }
2324
2325            interface ParentInterface {
2326              field: ParentInterface
2327            }
2328
2329            interface ChildInterface implements ParentInterface {
2330              field: ChildInterface
2331            }
2332            """
2333        )
2334        assert validate_schema(schema) == []
2335
2336    def accepts_an_interface_with_a_subtyped_interface_field_union():
2337        schema = build_schema(
2338            """
2339            type Query {
2340              test: ChildInterface
2341            }
2342
2343            type SomeObject {
2344              field: String
2345            }
2346
2347            union SomeUnionType = SomeObject
2348
2349            interface ParentInterface {
2350              field: SomeUnionType
2351            }
2352
2353            interface ChildInterface implements ParentInterface {
2354              field: SomeObject
2355            }
2356            """
2357        )
2358        assert validate_schema(schema) == []
2359
2360    def rejects_an_interface_implementing_a_non_interface_type():
2361        # invalid schema cannot be built with Python
2362        with raises(TypeError) as exc_info:
2363            build_schema(
2364                """
2365                type Query {
2366                  field: String
2367                }
2368
2369                input SomeInputObject {
2370                  field: String
2371                }
2372
2373                interface BadInterface implements SomeInputObject {
2374                  field: String
2375                }
2376                """
2377            )
2378        assert str(exc_info.value) == (
2379            "BadInterface interfaces must be specified as a collection"
2380            " of GraphQLInterfaceType instances."
2381        )
2382        # therefore we construct the invalid schema manually
2383        some_input_obj = GraphQLInputObjectType(
2384            "SomeInputObject", {"field": GraphQLInputField(GraphQLString)}
2385        )
2386        bad_interface = GraphQLInterfaceType(
2387            "BadInterface", {"field": GraphQLField(GraphQLString)}
2388        )
2389        # noinspection PyTypeChecker
2390        bad_interface.interfaces.append(some_input_obj)
2391        schema = GraphQLSchema(
2392            GraphQLObjectType("Query", {"field": GraphQLField(GraphQLString)}),
2393            types=[bad_interface],
2394        )
2395        assert validate_schema(schema) == [
2396            {
2397                "message": "Type BadInterface must only implement Interface types,"
2398                " it cannot implement SomeInputObject.",
2399            }
2400        ]
2401
2402    def rejects_an_interface_missing_an_interface_argument():
2403        schema = build_schema(
2404            """
2405            type Query {
2406              test: ChildInterface
2407            }
2408
2409            interface ParentInterface {
2410              field(input: String): String
2411            }
2412
2413            interface ChildInterface implements ParentInterface {
2414              field: String
2415            }
2416            """
2417        )
2418        assert validate_schema(schema) == [
2419            {
2420                "message": "Interface field argument ParentInterface.field(input:)"
2421                " expected but ChildInterface.field does not provide it.",
2422                "locations": [(7, 21), (11, 15)],
2423            }
2424        ]
2425
2426    def rejects_an_interface_with_an_incorrectly_typed_interface_argument():
2427        schema = build_schema(
2428            """
2429            type Query {
2430              test: ChildInterface
2431            }
2432
2433            interface ParentInterface {
2434              field(input: String): String
2435            }
2436
2437            interface ChildInterface implements ParentInterface {
2438              field(input: Int): String
2439            }
2440            """
2441        )
2442        assert validate_schema(schema) == [
2443            {
2444                "message": "Interface field argument ParentInterface.field(input:)"
2445                " expects type String but ChildInterface.field(input:) is type Int.",
2446                "locations": [(7, 28), (11, 28)],
2447            }
2448        ]
2449
2450    def rejects_an_interface_with_both_an_incorrectly_typed_field_and_argument():
2451        schema = build_schema(
2452            """
2453            type Query {
2454              test: ChildInterface
2455            }
2456
2457            interface ParentInterface {
2458              field(input: String): String
2459            }
2460
2461            interface ChildInterface implements ParentInterface {
2462              field(input: Int): Int
2463            }
2464            """
2465        )
2466        assert validate_schema(schema) == [
2467            {
2468                "message": "Interface field ParentInterface.field expects type String"
2469                " but ChildInterface.field is type Int.",
2470                "locations": [(7, 37), (11, 34)],
2471            },
2472            {
2473                "message": "Interface field argument ParentInterface.field(input:)"
2474                " expects type String but ChildInterface.field(input:) is type Int.",
2475                "locations": [(7, 28), (11, 28)],
2476            },
2477        ]
2478
2479    def rejects_an_interface_implementing_an_interface_field_with_additional_args():
2480        schema = build_schema(
2481            """
2482            type Query {
2483              test: ChildInterface
2484            }
2485
2486            interface ParentInterface {
2487              field(baseArg: String): String
2488            }
2489
2490            interface ChildInterface implements ParentInterface {
2491              field(
2492                baseArg: String,
2493                requiredArg: String!
2494                optionalArg1: String,
2495                optionalArg2: String = "",
2496              ): String
2497            }
2498            """
2499        )
2500        assert validate_schema(schema) == [
2501            {
2502                "message": "Object field ChildInterface.field includes"
2503                " required argument requiredArg that is missing"
2504                " from the Interface field ParentInterface.field.",
2505                "locations": [(13, 17), (7, 15)],
2506            }
2507        ]
2508
2509    def accepts_an_interface_with_an_equivalently_wrapped_interface_field_type():
2510        schema = build_schema(
2511            """
2512            type Query {
2513              test: ChildInterface
2514            }
2515
2516            interface ParentInterface {
2517              field: [String]!
2518            }
2519
2520            interface ChildInterface implements ParentInterface {
2521              field: [String]!
2522            }
2523            """
2524        )
2525        assert validate_schema(schema) == []
2526
2527    def rejects_an_interface_with_a_non_list_interface_field_list_type():
2528        schema = build_schema(
2529            """
2530            type Query {
2531              test: ChildInterface
2532            }
2533
2534            interface ParentInterface {
2535              field: [String]
2536            }
2537
2538            interface ChildInterface implements ParentInterface {
2539              field: String
2540            }
2541            """
2542        )
2543        assert validate_schema(schema) == [
2544            {
2545                "message": "Interface field ParentInterface.field"
2546                " expects type [String] but ChildInterface.field is type String.",
2547                "locations": [(7, 22), (11, 22)],
2548            }
2549        ]
2550
2551    def rejects_an_interface_with_a_list_interface_field_non_list_type():
2552        schema = build_schema(
2553            """
2554            type Query {
2555              test: ChildInterface
2556            }
2557
2558            interface ParentInterface {
2559              field: String
2560            }
2561
2562            interface ChildInterface implements ParentInterface {
2563              field: [String]
2564            }
2565            """
2566        )
2567        assert validate_schema(schema) == [
2568            {
2569                "message": "Interface field ParentInterface.field expects type String"
2570                " but ChildInterface.field is type [String].",
2571                "locations": [(7, 22), (11, 22)],
2572            }
2573        ]
2574
2575    def accepts_an_interface_with_a_subset_non_null_interface_field_type():
2576        schema = build_schema(
2577            """
2578            type Query {
2579              test: ChildInterface
2580            }
2581
2582            interface ParentInterface {
2583              field: String
2584            }
2585
2586            interface ChildInterface implements ParentInterface {
2587              field: String!
2588            }
2589            """
2590        )
2591        assert validate_schema(schema) == []
2592
2593    def rejects_an_interface_with_a_superset_nullable_interface_field_type():
2594        schema = build_schema(
2595            """
2596            type Query {
2597              test: ChildInterface
2598            }
2599
2600            interface ParentInterface {
2601              field: String!
2602            }
2603
2604            interface ChildInterface implements ParentInterface {
2605              field: String
2606            }
2607            """
2608        )
2609        assert validate_schema(schema) == [
2610            {
2611                "message": "Interface field ParentInterface.field expects type String!"
2612                " but ChildInterface.field is type String.",
2613                "locations": [(7, 22), (11, 22)],
2614            }
2615        ]
2616
2617    def rejects_an_object_missing_a_transitive_interface():
2618        schema = build_schema(
2619            """
2620            type Query {
2621              test: ChildInterface
2622            }
2623
2624            interface SuperInterface {
2625              field: String!
2626            }
2627
2628            interface ParentInterface implements SuperInterface {
2629              field: String!
2630            }
2631
2632            interface ChildInterface implements ParentInterface {
2633              field: String!
2634            }
2635            """
2636        )
2637        assert validate_schema(schema) == [
2638            {
2639                "message": "Type ChildInterface must implement SuperInterface"
2640                " because it is implemented by ParentInterface.",
2641                "locations": [(10, 50), (14, 49)],
2642            }
2643        ]
2644
2645    def rejects_a_self_reference_interface():
2646        schema = build_schema(
2647            """
2648            type Query {
2649            test: FooInterface
2650            }
2651
2652            interface FooInterface implements FooInterface {
2653            field: String
2654            }
2655            """
2656        )
2657        assert validate_schema(schema) == [
2658            {
2659                "message": "Type FooInterface cannot implement itself"
2660                " because it would create a circular reference.",
2661                "locations": [(6, 47)],
2662            }
2663        ]
2664
2665    def rejects_a_circular_interface_implementation():
2666        schema = build_schema(
2667            """
2668            type Query {
2669              test: FooInterface
2670            }
2671
2672            interface FooInterface implements BarInterface {
2673              field: String
2674            }
2675
2676            interface BarInterface implements FooInterface {
2677              field: String
2678            }
2679            """
2680        )
2681        assert validate_schema(schema) == [
2682            {
2683                "message": "Type FooInterface cannot implement BarInterface"
2684                " because it would create a circular reference.",
2685                "locations": [(10, 47), (6, 47)],
2686            },
2687            {
2688                "message": "Type BarInterface cannot implement FooInterface"
2689                " because it would create a circular reference.",
2690                "locations": [(6, 47), (10, 47)],
2691            },
2692        ]
2693
2694
2695def describe_assert_valid_schema():
2696    def do_not_throw_on_valid_schemas():
2697        schema = build_schema(
2698            (
2699                """
2700             type Query {
2701               foo: String
2702             }
2703            """
2704            )
2705        )
2706        assert_valid_schema(schema)
2707
2708    def include_multiple_errors_into_a_description():
2709        schema = build_schema("type SomeType")
2710        with raises(TypeError) as exc_info:
2711            assert_valid_schema(schema)
2712        assert (
2713            str(exc_info.value)
2714            == dedent(
2715                """
2716            Query root type must be provided.
2717
2718            Type SomeType must define one or more fields.
2719            """
2720            ).rstrip()
2721        )
2722