예제 #1
0
def test_added_removed_directive():
    no_directive = schema("""
    type A {
        a: String
    }
    """)
    one_directive = schema("""
    directive @somedir on FIELD_DEFINITION
    type A {
        a: String
    }
    """)
    diff = Schema(no_directive, one_directive).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Directive `@somedir` was added to use on `FIELD_DEFINITION`"
    assert diff[0].path == '@somedir'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(one_directive, no_directive).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Directive `@somedir` was removed"
    assert diff[0].path == '@somedir'
    assert diff[0].criticality == Criticality.breaking('Removing a directive may break clients that depend on them.')

    two_locations = schema("""
    directive @somedir on FIELD_DEFINITION | QUERY
    type A {
        a: String
    }
    """)
    diff = Schema(no_directive, two_locations).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Directive `@somedir` was added to use on `FIELD_DEFINITION | QUERY`"
    assert diff[0].path == '@somedir'
    assert diff[0].criticality == Criticality.safe()
예제 #2
0
def test_directive_argument_description_changed():
    no_desc = schema("""
    directive @limit(
        number: Int
    ) on FIELD_DEFINITION

    type A {
        a: String
    }
    """)
    a_desc = schema("""
    directive @limit(
        "number limit"
        number: Int
    ) on FIELD_DEFINITION

    type A {
        a: String
    }
    """)
    other_desc = schema("""
    directive @limit(
        "field limit"
        number: Int
    ) on FIELD_DEFINITION

    type A {
        a: String
    }
    """)

    diff = Schema(no_desc, a_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        "Description for argument `number` on `@limit` directive changed from `None` to `number limit`"
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@limit'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(a_desc, other_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        "Description for argument `number` on `@limit` directive changed from `number limit` to `field limit`"
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@limit'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(other_desc, no_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        "Description for argument `number` on `@limit` directive changed from `field limit` to `None`"
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@limit'
    assert diff[0].criticality == Criticality.safe()
예제 #3
0
def test_directive_argument_changes():
    name_arg = schema("""
    directive @somedir(name: String) on FIELD_DEFINITION
    type A {
        a: String
    }
    """)
    id_arg = schema("""
    directive @somedir(id: ID) on FIELD_DEFINITION
    type A {
        a: String
    }
    """)
    diff = Schema(name_arg, id_arg).diff()
    assert diff and len(diff) == 2
    expected_message = (
        'Removed argument `name: String` from `@somedir` directive'
        "Added argument `id: ID` to `@somedir` directive"
    )
    for change in diff:
        assert change.message in expected_message
        assert change.path == '@somedir'
        assert change.criticality == Criticality.breaking(
            'Removing a directive argument will break existing usages of the argument'
        ) if 'Removed' in change.message else Criticality.safe()
예제 #4
0
def test_deprecated_enum_value():
    a = schema("""
    type Query {
        a: Int
    }
    enum Letters {
        A
        B
    }
    """)
    b = schema("""
    type Query {
        a: Int
    }
    enum Letters {
        A
        B @deprecated(reason: "Changed the alphabet")
    }
    """)
    diff = Schema(a, b).diff()
    assert len(diff) == 1
    assert diff[
        0].message == "Enum value `B` was deprecated with reason `Changed the alphabet`"
    assert diff[0].path == 'Letters.B'
    assert diff[0].criticality == Criticality.safe(
        "A deprecated field can still be used by clients and will give them time to adapt their queries"
    )
예제 #5
0
def test_directive_location_added_and_removed():
    one_location = schema("""
    directive @somedir on FIELD_DEFINITION
    type A {
        a: String
    }
    """)
    two_locations = schema("""
    directive @somedir on FIELD_DEFINITION | FIELD
    type A {
        a: String
    }
    """)
    diff = Schema(one_location, two_locations).diff()
    assert diff and len(diff) == 1
    expected_message = (
        "Directive locations of `@somedir` changed from `FIELD_DEFINITION` to `FIELD_DEFINITION | FIELD`"
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@somedir'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(two_locations, one_location).diff()
    assert diff and len(diff) == 1
    expected_message = (
        "Directive locations of `@somedir` changed from `FIELD_DEFINITION | FIELD` to `FIELD_DEFINITION`"
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@somedir'
    assert diff[0].criticality == Criticality.breaking(
        'Removing a directive location will break any instance of its usage. Be sure no one uses it before removing it'
    )
예제 #6
0
def test_description_changed():
    a = schema('''
    type Query {
        a: Int
    }
    enum Letters {
        """My description"""
        A
    }
    ''')
    b = schema('''
    type Query {
        a: Int
    }
    enum Letters {
        """My new description"""
        A
    }
    ''')
    diff = Schema(a, b).diff()
    assert len(diff) == 1
    assert diff[0].message == (
        "Description for enum value `A` changed from `My description` to `My new description`"
    )
    assert diff[0].path == 'Letters.A'
    assert diff[0].criticality == Criticality.safe()
예제 #7
0
def test_deprecated_reason_changed():
    a = schema("""
    type Query {
        a: Int
    }
    enum Letters {
        A
        B @deprecated(reason: "a reason")
    }
    """)
    b = schema("""
    type Query {
        a: Int
    }
    enum Letters {
        A
        B @deprecated(reason: "a new reason")
    }
    """)
    diff = Schema(a, b).diff()
    assert len(diff) == 1
    assert diff[0].message == (
        "Deprecation reason for enum value `B` changed from `a reason` to `a new reason`"
    )
    assert diff[0].path == 'Letters.B'
    assert diff[0].criticality == Criticality.safe(
        "A deprecated field can still be used by clients and will give them time to adapt their queries"
    )
def test_input_field_added_field():
    a = schema("""
    input Recipe {
        ingredients: [String]
    }
    """)
    b = schema("""
    input Recipe {
        ingredients: [String]
        love: Float
    }
    """)
    diff = Schema(a, b).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == (
        "Input Field `love: Float` was added to input type `Recipe`")
    assert diff[0].path == 'Recipe.love'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(b, a).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == (
        "Input Field `love` removed from input type `Recipe`")
    assert diff[0].path == 'Recipe.love'
    assert diff[0].criticality == Criticality.breaking(
        'Removing an input field will break queries that use this input field.'
    )
def test_object_type_added_field():
    a = schema("""
    type MyType{
        a: Int
    }
    """)
    b = schema("""
    type MyType{
        a: Int
        b: String!
    }
    """)
    diff = Schema(a, b).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Field `b` was added to object type `MyType`"
    assert diff[0].path == 'MyType.b'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(b, a).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Field `b` was removed from object type `MyType`"
    assert diff[0].path == 'MyType.b'
    assert diff[0].criticality == Criticality.breaking(
        'Removing a field is a breaking change. It is preferred to deprecate the field before removing it.'
    )
예제 #10
0
def test_schema_added_type():
    old_schema = schema("""
    schema {
        query: Query
    }

    type Query {
        field: String!
    }
    """)
    new_schema = schema("""
    schema {
        query: Query
    }

    type Query {
        field: String!
    }

    type AddedType {
        added: Int
    }
    """)
    diff = Schema(old_schema, new_schema).diff()
    assert diff and len(diff) == 1
    # Type Int was also added but its ignored because its a primitive.
    assert diff[0].message == "Type `AddedType` was added"
    assert diff[0].criticality == Criticality.safe()
예제 #11
0
def test_directive_description_changed():
    no_desc = schema("""
    directive @my_directive on FIELD
    type A {
        a: String
    }
    """)
    with_desc = schema('''
    """directive desc"""
    directive @my_directive on FIELD
    type A {
        a: String
    }
    ''')
    new_desc = schema('''
    """new description"""
    directive @my_directive on FIELD
    type A {
        a: String
    }
    ''')
    diff = Schema(no_desc, with_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        'Description for directive `@my_directive` changed from `None` to `directive desc`'
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@my_directive'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(with_desc, new_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        'Description for directive `@my_directive` changed from `directive desc` to `new description`'
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@my_directive'
    assert diff[0].criticality == Criticality.safe()

    diff = Schema(with_desc, no_desc).diff()
    assert diff and len(diff) == 1
    expected_message = (
        'Description for directive `@my_directive` changed from `directive desc` to `None`'
    )
    assert diff[0].message == expected_message
    assert diff[0].path == '@my_directive'
    assert diff[0].criticality == Criticality.safe()
예제 #12
0
 def __init__(self, parent_type, field, arg_name, old_arg, new_arg):
     super().__init__(parent_type, field, arg_name, old_arg, new_arg)
     self.criticality = (Criticality.safe(
     ) if is_safe_change_for_input_value(
         old_arg.type, new_arg.type
     ) else Criticality.breaking(
         "Changing the type of a field's argument can break existing queries that use this argument."
     ))
 def __init__(self, directive, arg_name, arg_type):
     self.criticality = Criticality.safe(
     ) if not is_non_null_type(arg_type.type) else Criticality.breaking(
         "Adding a non nullable directive argument will break existing usages of the directive"
     )
     self.directive = directive
     self.arg_name = arg_name
     self.arg_type = arg_type
 def __init__(self, directive, old_locations, new_locations):
     self.directive = directive
     self.old_locations = old_locations
     self.new_locations = new_locations
     self.criticality = (Criticality.safe() if self._only_additions(
     ) else Criticality.breaking(
         "Removing a directive location will break any instance of its usage. "
         "Be sure no one uses it before removing it"))
예제 #15
0
 def __init__(self, type_, field_name, old_field, new_field):
     self.criticality = Criticality.safe()\
                        if is_safe_type_change(old_field.type, new_field.type)\
                        else Criticality.breaking('Changing a field type will break queries that assume its type')
     self.type_ = type_
     self.field_name = field_name
     self.old_field = old_field
     self.new_field = new_field
예제 #16
0
    class NonBreakingChange(Change):
        criticality = Criticality.safe('this is a safe change')

        def message(self):
            return 'test message'

        def path(self):
            return 'test path'
예제 #17
0
    class Testchange(Change):
        criticality = Criticality.safe('this is a safe change')

        @property
        def message(self):
            return 'test message'

        def path(self):
            return 'Query.path'
예제 #18
0
class InterfaceFieldDescriptionChanged(AbstractInterfanceChange):
    criticality = Criticality.safe()

    @property
    def message(self):
        return (
            f"`{self.interface.name}.{self.field_name}` description changed "
            f"from `{self.old_field.description}` to `{self.new_field.description}`"
        )
예제 #19
0
 def __init__(self, parent, field_name, argument_name, arg_type):
     self.criticality = Criticality.safe('Adding an optional argument is a safe change')\
                        if not is_non_null_type(arg_type.type)\
                        else Criticality.breaking("Adding a required argument to an existing field is a breaking "
                                                "change because it will break existing uses of this field")
     self.parent = parent
     self.field_name = field_name
     self.argument_name = argument_name
     self.arg_type = arg_type
    class MyChange(Change):
        criticality = Criticality.safe('Hello')

        @property
        def message(self):
            return ''

        @property
        def path(self):
            return None
예제 #21
0
    def __init__(self, input_object, field_name, field):
        self.criticality = (
            Criticality.safe()
            if is_non_null_type(field.type) is False
            else Criticality.breaking(self.BREAKING_MSG)
        )

        self.input_object = input_object
        self.field_name = field_name
        self.field = field
예제 #22
0
class FieldArgumentDescriptionChanged(FieldAbstractArgumentChange):

    criticality = Criticality.safe()

    @property
    def message(self):
        return (
            f"Description for argument `{self.arg_name}` on field `{self.parent}.{self.field_name}` "
            f"changed from `{self.old_arg.description}` to `{self.new_arg.description}`"
        )
    class UnexpectedChange(Change):
        criticality = Criticality.safe()

        @property
        def message(self):
            return ""

        @property
        def path(self):
            return ""
예제 #24
0
 def __init__(self, input_, name, new_field, old_field):
     self.criticality = (
         Criticality.safe()
         if is_safe_change_for_input_value(old_field.type, new_field.type)
         else Criticality.breaking(
             "Changing the type of an input field can break existing queries that use this field"
         )
     )
     self.input_ = input_
     self.name = name
     self.new_field = new_field
     self.old_field = old_field
class AddedDirective(DirectiveChange):

    criticality = Criticality.safe()

    def __init__(self, directive, directive_locations):
        self.directive = directive
        self.directive_locations = directive_locations

    @property
    def message(self):
        locations = ' | '.join(loc.name for loc in self.directive_locations)
        return f"Directive `{self.directive}` was added to use on `{locations}`"
 def __init__(self, directive, arg_name, old_type, new_type):
     self.criticality = (Criticality.breaking(
         "Changing the argument type is a breaking change"
     ) if not is_safe_change_for_input_value(
         old_type, new_type
     ) else Criticality.safe(
         "Changing an input field from non-null to null is considered non-breaking"
     ))
     self.directive = directive
     self.arg_name = arg_name
     self.old_type = old_type
     self.new_type = new_type
class DirectiveArgumentDescriptionChanged(DirectiveChange):
    criticality = Criticality.safe()

    def __init__(self, directive, arg_name, old_desc, new_desc):
        self.directive = directive
        self.arg_name = arg_name
        self.old_desc = old_desc
        self.new_desc = new_desc

    @property
    def message(self):
        return (
            f"Description for argument `{self.arg_name}` on `{self.directive!s}` directive changed "
            f"from `{self.old_desc}` to `{self.new_desc}`")
def test_added_removed_arguments():
    a = schema("""
    type Football {
        skill: Float!
    }
    """)
    b = schema("""
    type Football {
        skill(player: ID): Float!
    }
    """)
    diff = Schema(a, b).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Argument `player: ID` added to `Football.skill`"
    assert diff[0].path == 'Football.skill'
    assert diff[0].criticality == Criticality.safe(
        'Adding an optional argument is a safe change')

    diff = Schema(b, a).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Removed argument `player` from `Football.skill`"
    assert diff[0].path == 'Football.skill'
    assert diff[0].criticality == Criticality.breaking(
        'Removing a field argument will break queries that use this argument')

    c = schema("""
    type Football {
        skill(player: ID, age: Int): Float!
    }
    """)
    diff = Schema(b, c).diff()
    assert diff and len(diff) == 1
    assert diff[0].message == "Argument `age: Int` added to `Football.skill`"
    assert diff[0].path == 'Football.skill'
    assert diff[0].criticality == Criticality.safe(
        'Adding an optional argument is a safe change')
예제 #29
0
class ObjectTypeFieldAdded(Change):

    criticality = Criticality.safe()

    def __init__(self, parent, field_name):
        self.parent = parent
        self.field_name = field_name
        self.description = parent.fields[field_name].description

    @property
    def message(self):
        return f"Field `{self.field_name}` was added to object type `{self.parent.name}`"

    @property
    def path(self):
        return f"{self.parent.name}.{self.field_name}"
def test_deprecation_reason_changed():
    a = schema('''
    type Query {
        b: Int! @deprecated(reason: "Not used")
    }
    ''')
    b = schema('''
    type Query {
        b: Int! @deprecated(reason: "Some string")
    }
    ''')
    diff = Schema(a, b).diff()
    assert diff and len(diff) == 1
    assert diff[
        0].message == "Deprecation reason on field `Query.b` changed from `Not used` to `Some string`"
    assert diff[0].path == 'Query.b'
    assert diff[0].criticality == Criticality.safe()