def test_print_json_shows_difference(capsys):
    old_schema = SchemaLoader.from_file(TESTS_DATA / 'simple_schema.gql')
    new_schema = SchemaLoader.from_file(TESTS_DATA /
                                        'simple_schema_breaking_changes.gql')

    diff = Schema(old_schema, new_schema).diff()
    assert len(diff) == 1
    ret = print_json(diff)
    assert ret is None
    json_output = capsys.readouterr().out
    output_as_dict = json.loads(json_output)
    change_message = "Field `a` was removed from object type `Query`"
    assert output_as_dict == [{
        "message": change_message,
        "path": "Query.a",
        "is_safe_change": False,
        "criticality": {
            "level":
            "BREAKING",
            "reason":
            "Removing a field is a breaking change. "
            "It is preferred to deprecate the field before removing it."
        },
        "checksum": '5fba3d6ffc43c6769c6959ce5cb9b1c8'
    }]
Ejemplo n.º 2
0
def diff_from_file(schema_file: str, other_schema_file: str):
    """Compare two graphql schema files highlighting dangerous and breaking changes.

    Returns:
        changes (List[Change]): List of differences between both schemas with details about each change
    """
    first = SchemaLoader.from_file(schema_file)
    second = SchemaLoader.from_file(other_schema_file)
    return Schema(first, second).diff()
def test_print_diff_shows_difference(capsys):
    old_schema = SchemaLoader.from_file(TESTS_DATA / 'simple_schema.gql')
    new_schema = SchemaLoader.from_file(TESTS_DATA /
                                        'simple_schema_breaking_changes.gql')

    diff = Schema(old_schema, new_schema).diff()
    assert len(diff) == 1
    ret = print_diff(diff)
    assert ret is None
    assert capsys.readouterr().out == (
        '❌ Field `a` was removed from object type `Query`\n')
Ejemplo n.º 4
0
def diff(old_schema: Union[SDL, GQLSchema],
         new_schema: Union[SDL, GQLSchema]) -> List[Change]:
    """Compare two graphql schemas highlighting dangerous and breaking changes.

    Returns:
        changes (List[Change]): List of differences between both schemas with details about each change
    """
    first = SchemaLoader.from_sdl(
        old_schema) if not is_schema(old_schema) else old_schema
    second = SchemaLoader.from_sdl(
        new_schema) if not is_schema(new_schema) else new_schema
    return Schema(first, second).diff()
def test_compare_from_schema_string_sdl():
    old_schema = SchemaLoader.from_file(TESTS_DATA / 'old_schema.gql')
    new_schema = SchemaLoader.from_file(TESTS_DATA / 'new_schema.gql')

    diff = Schema(old_schema, new_schema).diff()
    assert len(diff) == 38

    expected_changes = {
         'Argument `arg: Int` added to `CType.a`',
         "Default value for argument `anotherArg` on `@yolo` directive changed from `Undefined` to `'Test'`",
         'Default value for argument `arg` on field `CType.d` changed from `Undefined` to `10`',
         'Default value for argument `arg` on field `WithArguments.b` changed from `1` to `2`',
         "Default value for input field `AInput.a` changed from `'1'` to `1`",
         'Deprecation reason for enum value `F` changed from `Old` to `New`',
         'Deprecation reason on field `CType.a` changed from `whynot` to `cuz`',
         'Description for Input field `AInput.a` changed from `a` to `None`',
         'Description for argument `a` on field `WithArguments.a` changed from `Meh` to `None`',
         'Description for argument `someArg` on `@yolo` directive changed from `Included when true.` to `None`',
         'Description for directive `@yolo` changed from `Old` to `None`',
         'Description for type `Query` changed from `The Query Root of this schema` to `None`',
         'Directive `@willBeRemoved` was removed',
         'Directive `@yolo2` was added to use on `FIELD`',
         (
            'Directive locations of `@yolo` changed from `FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT` '
            'to `FIELD | FIELD_DEFINITION`'
         ),
         'Enum value `C` was removed from `Options` enum',
         'Enum value `D` was added to `Options` enum',
         'Enum value `E` was deprecated with reason `No longer supported`',
         'Field `anotherInterfaceField` was removed from interface `AnotherInterface`',
         'Field `b` of type `Int` was added to interface `AnotherInterface`',
         'Field `b` was added to object type `CType`',
         'Field `c` was removed from object type `CType`',
         'Input Field `b` removed from input type `AInput`',
         'Input Field `c: String!` was added to input type `AInput`',
         'Removed argument `anArg` from `Query.a`',
         'Removed argument `willBeRemoved: Boolean!` from `@yolo` directive',
         'Type `DType` was added',
         'Type `WillBeRemoved` was removed',
         'Type for argument `b` on field `WithArguments.a` changed from `String` to `String!`',
         'Type for argument `someArg` on `@yolo` directive changed from `Boolean!` to `String!`',
         'Union member `BType` was removed from `MyUnion` Union type',
         'Union member `DType` was added to `MyUnion` Union type',
         '`AInput.a` type changed from `String` to `Int`',
         '`BType` kind changed from `OBJECT` to `INPUT OBJECT`',
         '`CType` implements new interface `AnInterface`',
         '`Query.a` description changed from `Just a simple string` to `None`',
         '`Query.b` type changed from `BType` to `Int!`',
         '`WithInterfaces` no longer implements interface `AnotherInterface`'
    }
    messages = [change.message for change in diff]
    for message in messages:
        assert message in expected_changes
def test_json_dump_changes():
    old = SchemaLoad.from_file(TESTS_DATA / 'simple_schema.gql')
    new = SchemaLoad.from_file(TESTS_DATA / 'simple_schema_dangerous_and_breaking.gql')

    changes = Schema(old, new).diff()
    expected_changes = [
        {
            'criticality': {
                'level': 'NON_BREAKING',
                'reason': "This change won't break any preexisting query"
            },
            'is_safe_change': True,
            'message': 'Field `c` was added to object type `Query`',
            'path': 'Query.c',
            'checksum': '1e3b776bda2dd8b11804e7341bb8b2d1',
        },
        {
            'criticality': {
                'level': 'BREAKING',
                'reason': (
                    'Removing a field is a breaking change. It is preferred to deprecate the field before removing it.'
                )
            },
            'is_safe_change': False,
            'message': 'Field `a` was removed from object type `Query`',
            'path': 'Query.a',
            'checksum': '5fba3d6ffc43c6769c6959ce5cb9b1c8',
        },
        {
            'criticality': {
                'level': 'DANGEROUS',
                'reason': (
                    'Changing the default value for an argument may '
                    'change the runtime behaviour of a field if it was never provided.'
                )
            },
            'is_safe_change': False,
            'message': 'Default value for argument `x` on field `Field.calculus` changed from `0` to `1`',
            'path': 'Field.calculus',
            'checksum': '8890b911d44b2ead0dceeae066d98617',
        }
    ]

    changes_result = changes_to_dict(changes)
    by_path = itemgetter('path')
    assert sorted(changes_result, key=by_path) == sorted(expected_changes, key=by_path)

    # Assert the json dump has a 32-char checksum and the changes attributes as expected
    json_changes = json.loads(json_dump_changes(changes))
    assert all(len(change['checksum']) == 32 for change in json_changes)
    assert sorted(json_changes, key=by_path) == sorted(expected_changes, key=by_path)
def main(args) -> int:
    # Load schemas from file path args
    old_schema = SchemaLoader.from_sdl(args.old_schema.read())
    new_schema = SchemaLoader.from_sdl(args.new_schema.read())
    args.old_schema.close()
    args.new_schema.close()

    diff = Schema(old_schema, new_schema).diff()
    restricted = evaluate_rules(diff, args.validation_rules)
    if args.allow_list:
        allow_list = args.allow_list.read()
        args.allow_list.close()
        allowed_changes = read_allowed_changes(allow_list)
        diff = [change for change in diff if change.checksum() not in allowed_changes]
    if args.as_json:
        print_json(diff)
    else:
        print_diff(diff)

    return exit_code(diff, args.strict, restricted, args.tolerant)
def test_diff_from_schema():
    schema_object = SchemaLoader.from_sdl("""
    schema {
        query: Query
    }
    type Query {
        a: ID!
        b: Int
    }
    """)
    assert is_schema(schema_object)
    changes = diff(schema_object, schema_object)
    assert changes == []
def test_load_from_string():
    schema_string = """
    schema {
        query: Query
    }
    
    type Query {
        a: ID!
        b: MyType
    }
    
    type MyType {
        c: String
        d: Float
    }
    """
    schema = SchemaLoader.from_sdl(schema_string)
    assert is_schema(schema)
    assert len(schema.query_type.fields) == 2
def test_load_empty_schema(schema):
    with pytest.raises(GraphQLSyntaxError):
        SchemaLoader.from_sdl(schema)
def test_load_invalid_schema():
    with pytest.raises(TypeError, match="Unknown type 'InvalidType'"):
        SchemaLoader.from_file(TESTS_DATA / 'invalid_schema.gql')
def test_load_from_file():
    schema = SchemaLoader.from_file(TESTS_DATA / 'simple_schema.gql')
    assert is_schema(schema)
    assert len(schema.query_type.fields) == 2