def should_detect_if_a_field_on_type_was_deleted_or_changed_type(): old_schema = build_schema(""" type TypeA { field1: String } interface Type1 { field1: TypeA field2: String field3: String field4: TypeA field6: String field7: [String] field8: Int field9: Int! field10: [Int]! field11: Int field12: [Int] field13: [Int!] field14: [Int] field15: [[Int]] field16: Int! field17: [Int] field18: [[Int!]!] } type Query { field1: String } """) new_schema = build_schema(""" type TypeA { field1: String } type TypeB { field1: String } interface Type1 { field1: TypeA field3: Boolean field4: TypeB field5: String field6: [String] field7: String field8: Int! field9: Int field10: [Int] field11: [Int]! field12: [Int!] field13: [Int] field14: [[Int]] field15: [Int] field16: [Int]! field17: [Int]! field18: [[Int!]] } type Query { field1: String } """) expected_field_changes = [ (BreakingChangeType.FIELD_REMOVED, "Type1.field2 was removed."), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field3 changed type from String to Boolean.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field4 changed type from TypeA to TypeB.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field6 changed type from String to [String].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field7 changed type from [String] to String.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field9 changed type from Int! to Int.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field10 changed type from [Int]! to [Int].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field11 changed type from Int to [Int]!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field13 changed type from [Int!] to [Int].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field14 changed type from [Int] to [[Int]].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field15 changed type from [[Int]] to [Int].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field16 changed type from Int! to [Int]!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "Type1.field18 changed type from [[Int!]!] to [[Int!]].", ), ] assert (find_fields_that_changed_type_on_object_or_interface_types( old_schema, new_schema) == expected_field_changes)
from functools import partial from graphql.utilities import build_schema from graphql.validation import NoDeprecatedCustomRule from .harness import assert_validation_errors schema = build_schema( """ enum EnumType { NORMAL_VALUE DEPRECATED_VALUE @deprecated(reason: "Some enum reason.") DEPRECATED_VALUE_WITH_NO_REASON @deprecated } type Query { normalField(enumArg: [EnumType]): String deprecatedField: String @deprecated(reason: "Some field reason.") deprecatedFieldWithNoReason: String @deprecated } """ ) assert_errors = partial(assert_validation_errors, NoDeprecatedCustomRule, schema=schema) assert_valid = partial(assert_errors, errors=[]) def describe_validate_no_deprecated(): def ignores_fields_and_enum_values_that_are_not_deprecated(): assert_valid(
def should_detect_if_a_field_argument_has_changed_type(): old_schema = build_schema(""" type Type1 { field1( arg1: String arg2: String arg3: [String] arg4: String arg5: String! arg6: String! arg7: [Int]! arg8: Int arg9: [Int] arg10: [Int!] arg11: [Int] arg12: [[Int]] arg13: Int! arg14: [[Int]!] arg15: [[Int]!] ): String } type Query { field1: String } """) new_schema = build_schema(""" type Type1 { field1( arg1: Int arg2: [String] arg3: String arg4: String! arg5: Int arg6: Int! arg7: [Int] arg8: [Int]! arg9: [Int!] arg10: [Int] arg11: [[Int]] arg12: [Int] arg13: [Int]! arg14: [[Int]] arg15: [[Int!]!] ): String } type Query { field1: String } """) assert find_arg_changes(old_schema, new_schema).breaking_changes == [ ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg1 has changed type from String to Int", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg2 has changed type from String to [String]", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg3 has changed type from [String] to String", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg4 has changed type from String to String!", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg5 has changed type from String! to Int", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg6 has changed type from String! to Int!", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg8 has changed type from Int to [Int]!", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg9 has changed type from [Int] to [Int!]", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg11 has changed type from [Int] to [[Int]]", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg12 has changed type from [[Int]] to [Int]", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg13 has changed type from Int! to [Int]!", ), ( BreakingChangeType.ARG_CHANGED_KIND, "Type1.field1 arg arg15 has changed type from [[Int]!] to [[Int!]!]", ), ]
def should_detect_all_breaking_changes(): old_schema = build_schema(""" directive @DirectiveThatIsRemoved on FIELD_DEFINITION directive @DirectiveThatRemovesArg(arg1: String) on FIELD_DEFINITION directive @NonNullDirectiveAdded on FIELD_DEFINITION directive @DirectiveName on FIELD_DEFINITION | QUERY type ArgThatChanges { field1(id: Int): String } enum EnumTypeThatLosesAValue { VALUE0 VALUE1 VALUE2 } interface Interface1 { field1: String } type TypeThatGainsInterface1 implements Interface1 { field1: String } type TypeInUnion1 { field1: String } type TypeInUnion2 { field1: String } union UnionTypeThatLosesAType = TypeInUnion1 | TypeInUnion2 type TypeThatChangesType { field1: String } type TypeThatGetsRemoved { field1: String } interface TypeThatHasBreakingFieldChanges { field1: String field2: String } type Query { field1: String } """) new_schema = build_schema(""" directive @DirectiveThatRemovesArg on FIELD_DEFINITION directive @NonNullDirectiveAdded(arg1: Boolean!) on FIELD_DEFINITION directive @DirectiveName on FIELD_DEFINITION type ArgThatChanges { field1(id: String): String } enum EnumTypeThatLosesAValue { VALUE1 VALUE2 } interface Interface1 { field1: String } type TypeInUnion1 { field1: String } union UnionTypeThatLosesAType = TypeInUnion1 interface TypeThatChangesType { field1: String } type TypeThatGainsInterface1 { field1: String } interface TypeThatHasBreakingFieldChanges { field2: Boolean } type Query { field1: String } """) expected_breaking_changes = [ (BreakingChangeType.TYPE_REMOVED, "Int was removed."), (BreakingChangeType.TYPE_REMOVED, "TypeInUnion2 was removed."), (BreakingChangeType.TYPE_REMOVED, "TypeThatGetsRemoved was removed."), ( BreakingChangeType.TYPE_CHANGED_KIND, "TypeThatChangesType changed from an Object type to an" " Interface type.", ), ( BreakingChangeType.FIELD_REMOVED, "TypeThatHasBreakingFieldChanges.field1 was removed.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "TypeThatHasBreakingFieldChanges.field2 changed type" " from String to Boolean.", ), ( BreakingChangeType.TYPE_REMOVED_FROM_UNION, "TypeInUnion2 was removed from union type UnionTypeThatLosesAType.", ), ( BreakingChangeType.VALUE_REMOVED_FROM_ENUM, "VALUE0 was removed from enum type EnumTypeThatLosesAValue.", ), ( BreakingChangeType.ARG_CHANGED_KIND, "ArgThatChanges.field1 arg id has changed type from Int to String", ), ( BreakingChangeType.INTERFACE_REMOVED_FROM_OBJECT, "TypeThatGainsInterface1 no longer implements interface Interface1.", ), ( BreakingChangeType.DIRECTIVE_REMOVED, "DirectiveThatIsRemoved was removed", ), ( BreakingChangeType.DIRECTIVE_ARG_REMOVED, "arg1 was removed from DirectiveThatRemovesArg", ), ( BreakingChangeType.REQUIRED_DIRECTIVE_ARG_ADDED, "A required arg arg1 on directive NonNullDirectiveAdded was added", ), ( BreakingChangeType.DIRECTIVE_LOCATION_REMOVED, "QUERY was removed from DirectiveName", ), ] assert (find_breaking_changes(old_schema, new_schema) == expected_breaking_changes)
def sort_sdl(sdl): schema = build_schema(sdl) return print_schema(lexicographic_sort_schema(schema))
def should_detect_if_fields_on_input_types_changed_kind_or_were_removed(): old_schema = build_schema(""" input InputType1 { field1: String field2: Boolean field3: [String] field4: String! field5: String field6: [Int] field7: [Int]! field8: Int field9: [Int] field10: [Int!] field11: [Int] field12: [[Int]] field13: Int! field14: [[Int]!] field15: [[Int]!] } type Query { field1: String }""") new_schema = build_schema(""" input InputType1 { field1: Int field3: String field4: String field5: String! field6: [Int]! field7: [Int] field8: [Int]! field9: [Int!] field10: [Int] field11: [[Int]] field12: [Int] field13: [Int]! field14: [[Int]] field15: [[Int!]!] } type Query { field1: String } """) expected_field_changes = [ ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field1 changed type from String to Int.", ), (BreakingChangeType.FIELD_REMOVED, "InputType1.field2 was removed."), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field3 changed type from [String] to String.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field5 changed type from String to String!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field6 changed type from [Int] to [Int]!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field8 changed type from Int to [Int]!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field9 changed type from [Int] to [Int!].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field11 changed type from [Int] to [[Int]].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field12 changed type from [[Int]] to [Int].", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field13 changed type from Int! to [Int]!.", ), ( BreakingChangeType.FIELD_CHANGED_KIND, "InputType1.field15 changed type from [[Int]!] to [[Int!]!].", ), ] assert (find_fields_that_changed_type_on_input_object_types( old_schema, new_schema).breaking_changes == expected_field_changes)
def describe_throws_when_given_invalid_introspection(): dummy_schema = build_schema(""" type Query { foo(bar: String): String } interface SomeInterface { foo: String } union SomeUnion = Query enum SomeEnum { FOO } input SomeInputObject { foo: String } directive @SomeDirective on QUERY """) def throws_when_introspection_is_missing_schema_property(): with raises(TypeError) as exc_info: # noinspection PyTypeChecker build_client_schema(None) # type: ignore assert str(exc_info.value) == ( "Invalid or incomplete introspection result. Ensure that you" " are passing the 'data' attribute of an introspection response" " and no 'errors' were returned alongside: None.") with raises(TypeError) as exc_info: # noinspection PyTypeChecker build_client_schema({}) # type: ignore assert str(exc_info.value) == ( "Invalid or incomplete introspection result. Ensure that you" " are passing the 'data' attribute of an introspection response" " and no 'errors' were returned alongside: {}.") def throws_when_referenced_unknown_type(): introspection = introspection_from_schema(dummy_schema) introspection["__schema"]["types"] = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] != "Query" ] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value) == ( "Invalid or incomplete schema, unknown type: Query." " Ensure that a full introspection query is used" " in order to build a client schema.") def throws_when_missing_definition_for_one_of_the_standard_scalars(): schema = build_schema(""" type Query { foo: Float } """) introspection = introspection_from_schema(schema) introspection["__schema"]["types"] = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] != "Float" ] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).endswith( "Invalid or incomplete schema, unknown type: Float." " Ensure that a full introspection query is used" " in order to build a client schema.") def throws_when_type_reference_is_missing_name(): introspection = introspection_from_schema(dummy_schema) query_type = cast(IntrospectionType, introspection["__schema"]["queryType"]) assert query_type["name"] == "Query" del query_type["name"] # type: ignore with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value) == "Unknown type reference: {}." def throws_when_missing_kind(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = next( type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query") assert query_type_introspection["kind"] == "OBJECT" del query_type_introspection["kind"] with raises( TypeError, match=r"^Invalid or incomplete introspection result\." " Ensure that a full introspection query is used" r" in order to build a client schema: {'name': 'Query', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_interfaces(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = cast( IntrospectionObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query"), ) assert query_type_introspection["interfaces"] == [] del query_type_introspection["interfaces"] # type: ignore with raises( TypeError, match="^Query interfaces cannot be resolved." " Introspection result missing interfaces:" r" {'kind': 'OBJECT', 'name': 'Query', .*}\.$", ): build_client_schema(introspection) def legacy_support_for_interfaces_with_null_as_interfaces_field(): introspection = introspection_from_schema(dummy_schema) some_interface_introspection = cast( IntrospectionInterfaceType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeInterface"), ) assert some_interface_introspection["interfaces"] == [] some_interface_introspection["interfaces"] = None # type: ignore client_schema = build_client_schema(introspection) assert print_schema(client_schema) == print_schema(dummy_schema) def throws_when_missing_fields(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = cast( IntrospectionObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query"), ) assert query_type_introspection["fields"] del query_type_introspection["fields"] # type: ignore with raises( TypeError, match="^Query fields cannot be resolved." " Introspection result missing fields:" r" {'kind': 'OBJECT', 'name': 'Query', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_field_args(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = cast( IntrospectionObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query"), ) field = query_type_introspection["fields"][0] assert field["args"] del field["args"] # type: ignore with raises( TypeError, match="^Query fields cannot be resolved." r" Introspection result missing field args: {'name': 'foo', .*}\.$", ): build_client_schema(introspection) def throws_when_output_type_is_used_as_an_arg_type(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = cast( IntrospectionObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query"), ) arg = query_type_introspection["fields"][0]["args"][0] assert arg["type"]["name"] == "String" arg["type"]["name"] = "SomeUnion" with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved." " Introspection must provide input type for arguments," " but received: SomeUnion.") def throws_when_output_type_is_used_as_an_input_value_type(): introspection = introspection_from_schema(dummy_schema) input_object_type_introspection = cast( IntrospectionInputObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeInputObject"), ) input_field = input_object_type_introspection["inputFields"][0] assert input_field["type"]["name"] == "String" input_field["type"]["name"] = "SomeUnion" with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "SomeInputObject fields cannot be resolved." " Introspection must provide input type for input fields," " but received: SomeUnion.") def throws_when_input_type_is_used_as_a_field_type(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = cast( IntrospectionObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query"), ) field = query_type_introspection["fields"][0] assert field["type"]["name"] == "String" field["type"]["name"] = "SomeInputObject" with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved." " Introspection must provide output type for fields," " but received: SomeInputObject.") def throws_when_missing_possible_types(): introspection = introspection_from_schema(dummy_schema) some_union_introspection = cast( IntrospectionUnionType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeUnion"), ) assert some_union_introspection["possibleTypes"] del some_union_introspection["possibleTypes"] # type: ignore with raises( TypeError, match="^Introspection result missing possibleTypes:" r" {'kind': 'UNION', 'name': 'SomeUnion', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_enum_values(): introspection = introspection_from_schema(dummy_schema) some_enum_introspection = cast( IntrospectionEnumType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeEnum"), ) assert some_enum_introspection["enumValues"] del some_enum_introspection["enumValues"] # type: ignore with raises( TypeError, match="^Introspection result missing enumValues:" r" {'kind': 'ENUM', 'name': 'SomeEnum', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_input_fields(): introspection = introspection_from_schema(dummy_schema) some_input_object_introspection = cast( IntrospectionInputObjectType, next(type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeInputObject"), ) assert some_input_object_introspection["inputFields"] del some_input_object_introspection["inputFields"] # type: ignore with raises( TypeError, match="^Introspection result missing inputFields:" r" {'kind': 'INPUT_OBJECT', 'name': 'SomeInputObject', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_directive_locations(): introspection = introspection_from_schema(dummy_schema) some_directive_introspection = introspection["__schema"][ "directives"][0] assert some_directive_introspection["name"] == "SomeDirective" assert some_directive_introspection["locations"] == ["QUERY"] del some_directive_introspection["locations"] # type: ignore with raises( TypeError, match="^Introspection result missing directive locations:" r" {'name': 'SomeDirective', .*}\.$", ): build_client_schema(introspection) def throws_when_missing_directive_args(): introspection = introspection_from_schema(dummy_schema) some_directive_introspection = introspection["__schema"][ "directives"][0] assert some_directive_introspection["name"] == "SomeDirective" assert some_directive_introspection["args"] == [] del some_directive_introspection["args"] # type: ignore with raises( TypeError, match="^Introspection result missing directive args:" r" {'name': 'SomeDirective', .*}\.$", ): build_client_schema(introspection)
def can_build_invalid_schema(): # Invalid schema, because it is missing query root type schema = build_schema("type Mutation") errors = validate_schema(schema) assert errors
def misplaced_directive(directive_name, placement, line, column): return { 'message': misplaced_directive_message(directive_name, placement), 'locations': [(line, column)] } schema_with_sdl_directives = build_schema(""" directive @onSchema on SCHEMA directive @onScalar on SCALAR directive @onObject on OBJECT directive @onFieldDefinition on FIELD_DEFINITION directive @onArgumentDefinition on ARGUMENT_DEFINITION directive @onInterface on INTERFACE directive @onUnion on UNION directive @onEnum on ENUM directive @onEnumValue on ENUM_VALUE directive @onInputObject on INPUT_OBJECT directive @onInputFieldDefinition on INPUT_FIELD_DEFINITION """) def describe_known_directives(): def with_no_directives(): expect_passes_rule( KnownDirectivesRule, """ query Foo { name ...Frag
def unknown_type_references_inside_extension_document(): schema = build_schema("type A") sdl = """ type B type SomeObject implements C { e(d: D): E } union SomeUnion = F | G interface SomeInterface { i(h: H): I } input SomeInput { j: J } directive @SomeDirective(k: K) on QUERY schema { query: L mutation: M subscription: N } """ assert_sdl_errors( sdl, [ { "message": "Unknown type 'C'. Did you mean 'A' or 'B'?", "locations": [(4, 40)], }, { "message": "Unknown type 'D'. Did you mean 'ID', 'A', or 'B'?", "locations": [(5, 20)], }, { "message": "Unknown type 'E'. Did you mean 'A' or 'B'?", "locations": [(5, 24)], }, { "message": "Unknown type 'F'. Did you mean 'A' or 'B'?", "locations": [(8, 31)], }, { "message": "Unknown type 'G'. Did you mean 'A' or 'B'?", "locations": [(8, 35)], }, { "message": "Unknown type 'H'. Did you mean 'A' or 'B'?", "locations": [(11, 20)], }, { "message": "Unknown type 'I'. Did you mean 'ID', 'A', or 'B'?", "locations": [(11, 24)], }, { "message": "Unknown type 'J'. Did you mean 'A' or 'B'?", "locations": [(15, 18)], }, { "message": "Unknown type 'K'. Did you mean 'A' or 'B'?", "locations": [(18, 41)], }, { "message": "Unknown type 'L'. Did you mean 'A' or 'B'?", "locations": [(21, 22)], }, { "message": "Unknown type 'M'. Did you mean 'A' or 'B'?", "locations": [(22, 25)], }, { "message": "Unknown type 'N'. Did you mean 'A' or 'B'?", "locations": [(23, 29)], }, ], schema, )
def supports_deprecated_directive(): sdl = dedent( """ enum MyEnum { VALUE OLD_VALUE @deprecated OTHER_VALUE @deprecated(reason: "Terrible reasons") } input MyInput { oldInput: String @deprecated otherInput: String @deprecated(reason: "Use newInput") newInput: String } type Query { field1: String @deprecated field2: Int @deprecated(reason: "Because I said so") enum: MyEnum field3(oldArg: String @deprecated, arg: String): String field4(oldArg: String @deprecated(reason: "Why not?"), arg: String): String field5(arg: MyInput): String } """ # noqa: E501 ) assert cycle_sdl(sdl) == sdl schema = build_schema(sdl) my_enum = assert_enum_type(schema.get_type("MyEnum")) value = my_enum.values["VALUE"] assert value.deprecation_reason is None old_value = my_enum.values["OLD_VALUE"] assert old_value.deprecation_reason == "No longer supported" other_value = my_enum.values["OTHER_VALUE"] assert other_value.deprecation_reason == "Terrible reasons" root_fields = assert_object_type(schema.get_type("Query")).fields field1 = root_fields["field1"] assert field1.deprecation_reason == "No longer supported" field2 = root_fields["field2"] assert field2.deprecation_reason == "Because I said so" input_fields = assert_input_object_type(schema.get_type("MyInput")).fields new_input = input_fields["newInput"] assert new_input.deprecation_reason is None old_input = input_fields["oldInput"] assert old_input.deprecation_reason == "No longer supported" other_input = input_fields["otherInput"] assert other_input.deprecation_reason == "Use newInput" field3_old_arg = root_fields["field3"].args["oldArg"] assert field3_old_arg.deprecation_reason == "No longer supported" field4_old_arg = root_fields["field4"].args["oldArg"] assert field4_old_arg.deprecation_reason == "Why not?"
def specifying_interface_type_using_typename(): schema = build_schema(""" type Query { characters: [Character] } interface Character { name: String! } type Human implements Character { name: String! totalCredits: Int } type Droid implements Character { name: String! primaryFunction: String } """) query = """ { characters { name ... on Human { totalCredits } ... on Droid { primaryFunction } } } """ root = { "characters": [ { "name": "Han Solo", "totalCredits": 10, "__typename": "Human" }, { "name": "R2-D2", "primaryFunction": "Astromech", "__typename": "Droid", }, ] } assert graphql_sync(schema, query, root) == ( { "characters": [ { "name": "Han Solo", "totalCredits": 10 }, { "name": "R2-D2", "primaryFunction": "Astromech" }, ] }, None, )
def describe_return_types_must_be_unambiguous(): schema = build_schema(""" interface SomeBox { deepBox: SomeBox unrelatedField: String } type StringBox implements SomeBox { scalar: String deepBox: StringBox unrelatedField: String listStringBox: [StringBox] stringBox: StringBox intBox: IntBox } type IntBox implements SomeBox { scalar: Int deepBox: IntBox unrelatedField: String listStringBox: [StringBox] stringBox: StringBox intBox: IntBox } interface NonNullStringBox1 { scalar: String! } type NonNullStringBox1Impl implements SomeBox & NonNullStringBox1 { scalar: String! unrelatedField: String deepBox: SomeBox } interface NonNullStringBox2 { scalar: String! } type NonNullStringBox2Impl implements SomeBox & NonNullStringBox2 { scalar: String! unrelatedField: String deepBox: SomeBox } type Connection { edges: [Edge] } type Edge { node: Node } type Node { id: ID name: String } type Query { someBox: SomeBox connection: Connection } """) def conflicting_return_types_which_potentially_overlap(): # This is invalid since an object could potentially be both the # Object type IntBox and the interface type NonNullStringBox1. # While that condition does not exist in the current schema, the # schema could expand in the future to allow this. assert_errors( """ { someBox { ...on IntBox { scalar } ...on NonNullStringBox1 { scalar } } } """, [{ "message": "Fields 'scalar' conflict because" " they return conflicting types 'Int' and 'String!'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (8, 23)], }], schema, ) def compatible_return_shapes_on_different_return_types(): # In this case `deepBox` returns `SomeBox` in the first usage, and # `StringBox` in the second usage. These types are not the same! # However this is valid because the return *shapes* are compatible. assert_valid( """ { someBox { ... on SomeBox { deepBox { unrelatedField } } ... on StringBox { deepBox { unrelatedField } } } } """, schema=schema, ) def disallows_differing_return_types_despite_no_overlap(): assert_errors( """ { someBox { ... on IntBox { scalar } ... on StringBox { scalar } } } """, [{ "message": "Fields 'scalar' conflict because" " they return conflicting types 'Int' and 'String'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (8, 23)], }], schema, ) def reports_correctly_when_a_non_exclusive_follows_an_exclusive(): assert_errors( """ { someBox { ... on IntBox { deepBox { ...X } } } someBox { ... on StringBox { deepBox { ...Y } } } memoed: someBox { ... on IntBox { deepBox { ...X } } } memoed: someBox { ... on StringBox { deepBox { ...Y } } } other: someBox { ...X } other: someBox { ...Y } } fragment X on SomeBox { scalar } fragment Y on SomeBox { scalar: unrelatedField } """, [{ "message": "Fields 'other' conflict because" " subfields 'scalar' conflict because" " 'scalar' and 'unrelatedField' are different fields." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(31, 19), (39, 19), (34, 19), (42, 19)], "path": None, }], schema, ) def disallows_differing_return_type_nullability_despite_no_overlap(): assert_errors( """ { someBox { ... on NonNullStringBox1 { scalar } ... on StringBox { scalar } } } """, [{ "message": "Fields 'scalar' conflict because" " they return conflicting types 'String!' and 'String'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (8, 23)], }], schema, ) def disallows_differing_return_type_list_despite_no_overlap_1(): assert_errors( """ { someBox { ... on IntBox { box: listStringBox { scalar } } ... on StringBox { box: stringBox { scalar } } } } """, [{ "message": "Fields 'box' conflict because they return" " conflicting types '[StringBox]' and 'StringBox'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (10, 23)], }], schema, ) assert_errors( """ { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: listStringBox { scalar } } } } """, [{ "message": "Fields 'box' conflict because they return" " conflicting types 'StringBox' and '[StringBox]'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (10, 23)], }], schema, ) def disallows_differing_subfields(): assert_errors( """ { someBox { ... on IntBox { box: stringBox { val: scalar val: unrelatedField } } ... on StringBox { box: stringBox { val: scalar } } } } """, [{ "message": "Fields 'val' conflict because" " 'scalar' and 'unrelatedField' are different fields." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(6, 25), (7, 25)], }], schema, ) def disallows_differing_deep_return_types_despite_no_overlap(): assert_errors( """ { someBox { ... on IntBox { box: stringBox { scalar } } ... on StringBox { box: intBox { scalar } } } } """, [{ "message": "Fields 'box' conflict" " because subfields 'scalar' conflict" " because they return conflicting types 'String' and 'Int'." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [(5, 23), (6, 25), (10, 23), (11, 25)], "path": None, }], schema, ) def allows_non_conflicting_overlapping_types(): assert_valid( """ { someBox { ... on IntBox { scalar: unrelatedField } ... on StringBox { scalar } } } """, schema=schema, ) def same_wrapped_scalar_return_types(): assert_valid( """ { someBox { ...on NonNullStringBox1 { scalar } ...on NonNullStringBox2 { scalar } } } """, schema=schema, ) def allows_inline_fragments_without_type_condition(): assert_valid( """ { a ... { a } } """, schema=schema, ) def compares_deep_types_including_list(): assert_errors( """ { connection { ...edgeID edges { node { id: name } } } } fragment edgeID on Connection { edges { node { id } } } """, [{ "message": "Fields 'edges' conflict" " because subfields 'node' conflict" " because subfields 'id' conflict" " because 'name' and 'id' are different fields." " Use different aliases on the fields" " to fetch both if this was intentional.", "locations": [ (5, 21), (6, 23), (7, 25), (14, 19), (15, 21), (16, 23), ], "path": None, }], schema, ) def ignores_unknown_types(): assert_valid( """ { someBox { ...on UnknownType { scalar } ...on NonNullStringBox2 { scalar } } } """, schema=schema, ) def works_for_field_names_that_are_js_keywords(): schema_with_keywords = build_schema(""" type Foo { constructor: String } type Query { foo: Foo } """) assert_valid( """ { foo { constructor } } """, schema=schema_with_keywords, ) def works_for_field_names_that_are_python_keywords(): schema_with_keywords = build_schema(""" type Foo { class: String } type Query { foo: Foo } """) assert_valid( """ { foo { class } } """, schema=schema_with_keywords, )
def describe_specifying_interface_type_using_typename(): schema = build_schema(""" type Query { characters: [Character] } interface Character { name: String! } type Human implements Character { name: String! totalCredits: Int } type Droid implements Character { name: String! primaryFunction: String } """) source = """ { characters { name ... on Human { totalCredits } ... on Droid { primaryFunction } } } """ expected = ( { "characters": [ { "name": "Han Solo", "totalCredits": 10 }, { "name": "R2-D2", "primaryFunction": "Astromech" }, ] }, None, ) def using_dicts(): root_value = { "characters": [ { "name": "Han Solo", "totalCredits": 10, "__typename": "Human" }, { "name": "R2-D2", "primaryFunction": "Astromech", "__typename": "Droid", }, ] } assert (graphql_sync(schema=schema, source=source, root_value=root_value) == expected) def using_objects(): class Human: __typename = "Human" name = "Han Solo" totalCredits = 10 class Droid: __typename = "Droid" name = "R2-D2" primaryFunction = "Astromech" class RootValue: characters = [Human(), Droid()] assert (graphql_sync(schema=schema, source=source, root_value=RootValue()) == expected) def using_inheritance(): class Character: __typename = "Character" class Human(Character): __typename = "Human" class HanSolo(Human): name = "Han Solo" totalCredits = 10 class Droid(Character): __typename = "Droid" class RemoteControlled: name = "R2" class Mobile: name = "D2" class R2D2(RemoteControlled, Droid, Mobile): name = "R2-D2" primaryFunction = "Astromech" class RootValue: characters = [HanSolo(), R2D2()] assert (graphql_sync(schema=schema, source=source, root_value=RootValue()) == expected)
def resolve_type_on_interface_yields_useful_error(): schema = build_schema(""" type Query { pet: Pet } interface Pet { name: String } type Cat implements Pet { name: String } type Dog implements Pet { name: String } """) document = parse(""" { pet { name } } """) def expect_error(for_type_name: Any, message: str) -> None: root_value = {"pet": {"__typename": for_type_name}} result = execute_sync(schema, document, root_value) expected = ( { "pet": None }, [{ "message": message, "locations": [(3, 15)], "path": ["pet"] }], ) assert result == expected expect_error( None, "Abstract type 'Pet' must resolve" " to an Object type at runtime for field 'Query.pet'." " Either the 'Pet' type should provide a 'resolve_type' function" " or each possible type should provide an 'is_type_of' function.", ) expect_error( "Human", "Abstract type 'Pet' was resolved to a type 'Human'" " that does not exist inside the schema.", ) expect_error( "String", "Abstract type 'Pet' was resolved to a non-object type 'String'.") expect_error( "__Schema", "Runtime Object type '__Schema' is not a possible type for 'Pet'.", ) # workaround since we can't inject resolve_type into SDL schema.get_type("Pet").resolve_type = lambda *_args: [] # type: ignore expect_error( None, "Abstract type 'Pet' must resolve" " to an Object type at runtime for field 'Query.pet'" " with value {'__typename': None}, received '[]'.", )
def _complete(list_field): return execute_sync( build_schema("type Query { listField: [String] }"), parse("{ listField }"), Data(list_field), )
def should_find_all_dangerous_changes(): old_schema = build_schema(""" enum EnumType1 { VALUE0 VALUE1 } type Type1 { field1(name: String = "test"): String } type TypeThatGainsInterface1 { field1: String } type TypeInUnion1 { field1: String } union UnionTypeThatGainsAType = TypeInUnion1 type Query { field1: String } """) new_schema = build_schema(""" enum EnumType1 { VALUE0 VALUE1 VALUE2 } interface Interface1 { field1: String } type TypeThatGainsInterface1 implements Interface1 { field1: String } type Type1 { field1(name: String = "Test"): String } type TypeInUnion1 { field1: String } type TypeInUnion2 { field1: String } union UnionTypeThatGainsAType = TypeInUnion1 | TypeInUnion2 type Query { field1: String } """) expected_dangerous_changes = [ ( DangerousChangeType.ARG_DEFAULT_VALUE_CHANGE, "Type1.field1 arg name has changed defaultValue", ), ( DangerousChangeType.VALUE_ADDED_TO_ENUM, "VALUE2 was added to enum type EnumType1.", ), ( DangerousChangeType.INTERFACE_ADDED_TO_OBJECT, "Interface1 added to interfaces implemented" " by TypeThatGainsInterface1.", ), ( DangerousChangeType.TYPE_ADDED_TO_UNION, "TypeInUnion2 was added to union type UnionTypeThatGainsAType.", ), ] assert (find_dangerous_changes( old_schema, new_schema) == expected_dangerous_changes)
def describe_specifying_union_type_using_typename(): schema = build_schema(""" type Query { fruits: [Fruit] } union Fruit = Apple | Banana type Apple { color: String } type Banana { length: Int } """) source = """ { fruits { ... on Apple { color } ... on Banana { length } } } """ expected = ({"fruits": [{"color": "green"}, {"length": 5}]}, None) def using_dicts(): root_value = { "fruits": [ { "color": "green", "__typename": "Apple" }, { "length": 5, "__typename": "Banana" }, ] } assert (graphql_sync(schema=schema, source=source, root_value=root_value) == expected) def using_objects(): class Apple: __typename = "Apple" color = "green" class Banana: __typename = "Banana" length = 5 class RootValue: fruits = [Apple(), Banana()] assert (graphql_sync(schema=schema, source=source, root_value=RootValue()) == expected) def using_inheritance(): class Fruit: __typename = "Fruit" class Apple(Fruit): __typename = "Apple" class Delicious(Apple): color = "golden or red" class GoldenDelicious(Delicious): color = "golden" class RedDelicious(Delicious): color = "red" class GrannySmith(Apple): color = "green" class Banana(Fruit): __typename = "Banana" length = 5 class RootValue: fruits = [ GrannySmith(), RedDelicious(), GoldenDelicious(), Banana() ] assert graphql_sync(schema=schema, source=source, root_value=RootValue()) == ( { "fruits": [ { "color": "green" }, { "color": "red" }, { "color": "golden" }, { "length": 5 }, ] }, None, )
def describe_throws_when_given_invalid_introspection(): dummy_schema = build_schema(""" type Query { foo(bar: String): String } union SomeUnion = Query enum SomeEnum { FOO } input SomeInputObject { foo: String } directive @SomeDirective on QUERY """) def throws_when_referenced_unknown_type(): introspection = introspection_from_schema(dummy_schema) introspection["__schema"]["types"] = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] != "Query" ] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value) == ( "Invalid or incomplete schema, unknown type: Query." " Ensure that a full introspection query is used" " in order to build a client schema.") def throws_when_type_reference_is_missing_name(): introspection = introspection_from_schema(dummy_schema) assert introspection["__schema"]["queryType"]["name"] == "Query" del introspection["__schema"]["queryType"]["name"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value) == "Unknown type reference: {}" def throws_when_missing_kind(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert query_type_introspection["kind"] == "OBJECT" del query_type_introspection["kind"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Invalid or incomplete introspection result." " Ensure that a full introspection query is used" " in order to build a client schema: {'name': 'Query',") def throws_when_missing_interfaces(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert query_type_introspection["interfaces"] == [] del query_type_introspection["interfaces"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing interfaces:" " {'kind': 'OBJECT', 'name': 'Query',") def throws_when_missing_fields(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert query_type_introspection["fields"] del query_type_introspection["fields"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved:" " Introspection result missing fields:" " {'kind': 'OBJECT', 'name': 'Query',") def throws_when_missing_field_args(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert query_type_introspection["fields"][0]["args"] del query_type_introspection["fields"][0]["args"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved:" " Introspection result missing field args: {'name': 'foo',") def throws_when_output_type_is_used_as_an_arg_type(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert (query_type_introspection["fields"][0]["args"][0]["type"] ["name"] == "String") query_type_introspection["fields"][0]["args"][0]["type"][ "name"] = "SomeUnion" with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved:" " Introspection must provide input type for arguments," " but received: SomeUnion.") def throws_when_input_type_is_used_as_a_field_type(): introspection = introspection_from_schema(dummy_schema) query_type_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "Query" ][0] assert query_type_introspection["fields"][0]["type"][ "name"] == "String" query_type_introspection["fields"][0]["type"][ "name"] = "SomeInputObject" with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Query fields cannot be resolved:" " Introspection must provide output type for fields," " but received: SomeInputObject.") def throws_when_missing_possible_types(): introspection = introspection_from_schema(dummy_schema) some_union_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeUnion" ][0] assert some_union_introspection["possibleTypes"] del some_union_introspection["possibleTypes"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing possibleTypes:" " {'kind': 'UNION', 'name': 'SomeUnion',") def throws_when_missing_enum_values(): introspection = introspection_from_schema(dummy_schema) some_enum_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeEnum" ][0] assert some_enum_introspection["enumValues"] del some_enum_introspection["enumValues"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing enumValues:" " {'kind': 'ENUM', 'name': 'SomeEnum',") def throws_when_missing_input_fields(): introspection = introspection_from_schema(dummy_schema) some_input_object_introspection = [ type_ for type_ in introspection["__schema"]["types"] if type_["name"] == "SomeInputObject" ][0] assert some_input_object_introspection["inputFields"] del some_input_object_introspection["inputFields"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing inputFields:" " {'kind': 'INPUT_OBJECT', 'name': 'SomeInputObject',") def throws_when_missing_directive_locations(): introspection = introspection_from_schema(dummy_schema) some_directive_introspection = introspection["__schema"][ "directives"][0] assert some_directive_introspection["name"] == "SomeDirective" assert some_directive_introspection["locations"] == ["QUERY"] del some_directive_introspection["locations"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing directive locations:" " {'name': 'SomeDirective',") def throws_when_missing_directive_args(): introspection = introspection_from_schema(dummy_schema) some_directive_introspection = introspection["__schema"][ "directives"][0] assert some_directive_introspection["name"] == "SomeDirective" assert some_directive_introspection["args"] == [] del some_directive_introspection["args"] with raises(TypeError) as exc_info: build_client_schema(introspection) assert str(exc_info.value).startswith( "Introspection result missing directive args:" " {'name': 'SomeDirective',")