def _evaluate_additional_properties_diffs( path, # type: PathType left_spec, # type: Spec right_spec, # type: Spec left_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] right_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] ): # type: (...) -> typing.List[AdditionalPropertiesDiff] result = [] # type: typing.List[AdditionalPropertiesDiff] left_additional_properties = None if left_schema is None else left_schema.get( 'additionalProperties', True) right_additional_properties = None if right_schema is None else right_schema.get( 'additionalProperties', True) left_properties = get_properties(left_spec, left_schema) or set() right_properties = get_properties(right_spec, right_schema) or set() properties_appear_once = left_properties.symmetric_difference( right_properties) if left_additional_properties == {}: # Normalize additional properties left_additional_properties = True if right_additional_properties == {}: # Normalize additional properties right_additional_properties = True if not _cycle_safe_compare(left_additional_properties, right_additional_properties): result.append( AdditionalPropertiesDiff( path=path, diff_type=DiffType.VALUE, additionalProperties=EntityMapping( # casting here is safe as swagger specs are assumed to be valid and bool or dict are the only possible types old=typing.cast( typing.Union[bool, typing.Mapping[typing.Text, typing.Any]], left_additional_properties), new=typing.cast( typing.Union[bool, typing.Mapping[typing.Text, typing.Any]], right_additional_properties), ), properties=None, )) if (left_additional_properties is False or right_additional_properties is False) and properties_appear_once: result.append( AdditionalPropertiesDiff( path=path, diff_type=DiffType.PROPERTIES, additionalProperties=None, properties=EntityMapping( old=properties_appear_once.intersection(left_properties), new=properties_appear_once.intersection(right_properties), ), )) return result
def test_EntityMapping_equality_and_hash(): entity_mappint_1 = EntityMapping(old=1, new=2) entity_mappint_2 = EntityMapping(old=1, new=2) entity_mappint_3 = EntityMapping(old=1, new=3) assert hash(entity_mappint_1) == hash(entity_mappint_2) assert entity_mappint_1 == entity_mappint_2 assert hash(entity_mappint_1) != hash(entity_mappint_3) assert entity_mappint_1 != entity_mappint_3
def test_get_operation_mappings(minimal_spec, spec_and_operation): assert get_operation_mappings(minimal_spec, minimal_spec) == set() spec, operation = spec_and_operation assert get_operation_mappings(spec, minimal_spec) == set() assert get_operation_mappings( spec, spec) == {EntityMapping(operation, operation)}
def test_RequiredPropertiesDifferWalker_recursive_definition( minimal_spec_dict): minimal_spec_dict['definitions'] = { 'recursive_object': { 'type': 'object', 'properties': { 'property': { '$ref': '#/definitions/model' }, 'recursive_property': { '$ref': '#/definitions/recursive_object' }, }, }, } left_spec_dict = dict( minimal_spec_dict, definitions={ 'model': { 'properties': { 'old_only': { 'type': 'string' }, }, 'required': ['old_only'], 'type': 'object', }, }, ) right_spec_dict = dict( minimal_spec_dict, definitions={ 'model': { 'properties': { 'old_only': { 'type': 'string' }, }, 'type': 'object', }, }, ) left_spec = load_spec_from_spec_dict(spec_dict=left_spec_dict) right_spec = load_spec_from_spec_dict(spec_dict=right_spec_dict) assert RequiredPropertiesDifferWalker( left_spec=left_spec, right_spec=right_spec, ).walk() == [ RequiredPropertiesDiff(path=('definitions', 'model'), mapping=EntityMapping(old={'old_only'}, new=set())) ]
def get_operation_mappings(old_spec, new_spec): # type: (Spec, Spec) -> typing.Set[EntityMapping[Operation]] old_endpoints = get_endpoints(old_spec) old_endpoints_map = { # Small hack to make endpoint search easy endpoint: endpoint for endpoint in old_endpoints } new_endpoints = get_endpoints(new_spec) new_endpoints_map = { # Small hack to make endpoint search easy endpoint: endpoint for endpoint in new_endpoints } return { EntityMapping(old_endpoints_map[endpoint].operation, new_endpoints_map[endpoint].operation) for endpoint in old_endpoints.intersection(new_endpoints) }
def _different_properties_mapping( left_spec, # type: Spec right_spec, # type: Spec left_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] right_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] ): # type: (...) -> typing.Optional[EntityMapping[typing.Set[typing.Text]]] left_required = get_required_properties(swagger_spec=left_spec, schema=left_schema) or set() right_required = get_required_properties(swagger_spec=right_spec, schema=right_schema) or set() properties_appear_once = left_required.symmetric_difference(right_required) if not properties_appear_once: # The condition is true if left_required is empty and right_required is not empty or vice-versa return None else: return EntityMapping( old=properties_appear_once.intersection(left_required), new=properties_appear_once.intersection(right_required), )
def iterate_on_responses_status_codes( old_operation, # type: typing.Mapping[typing.Text, typing.Any] new_operation, # type: typing.Mapping[typing.Text, typing.Any] ): # type: (...) -> typing.Generator[StatusCodeSchema, None, None] old_status_code_schema_mapping = old_operation.get('responses') or {} new_status_code_schema_mapping = new_operation.get('responses') or {} common_response_codes = set( iterkeys(old_status_code_schema_mapping)).intersection( set(iterkeys(new_status_code_schema_mapping)), ) # Compare schemas for the same status code only (TODO: what to do for old=default and new=404?) for status_code in common_response_codes: yield StatusCodeSchema( status_code=status_code, mapping=EntityMapping( old=old_status_code_schema_mapping[status_code].get('schema'), new=new_status_code_schema_mapping[status_code].get('schema'), ), )
def test__evaluate_additional_properties_recursive_schema_reference_with_difference( mock_get_properties): left_schema = _construct_recursive_additional_properties() left_schema['properties']['bar'] = {'type': 'string'} right_schema = _construct_recursive_additional_properties() assert _evaluate_additional_properties_diffs( path=mock.sentinel.PATH, left_spec=mock.sentinel.LEFT_SPEC, right_spec=mock.sentinel.RIGHT_SPEC, left_schema=left_schema, right_schema=right_schema, ) == [ AdditionalPropertiesDiff( path=mock.sentinel.PATH, diff_type=DiffType.VALUE, additionalProperties=EntityMapping( old=left_schema, new=right_schema, ), properties=None, ), ]
def _different_enum_values_mapping( left_spec, # type: Spec right_spec, # type: Spec left_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] right_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] ): # type: (...) -> typing.Optional[EntityMapping[typing.Set[typing.Text]]] left_enum_values = (set(left_schema['enum']) if (left_schema and left_schema.get('type') == 'string' and 'enum' in left_schema) else set()) right_enum_values = (set(right_schema['enum']) if (right_schema and right_schema.get('type') == 'string' and 'enum' in right_schema) else set()) enum_values_appear_once = left_enum_values.symmetric_difference( right_enum_values) if not enum_values_appear_once: # The condition is true if left_required is empty and right_required is not empty or vice-versa return None else: return EntityMapping( old=enum_values_appear_once.intersection(left_enum_values), new=enum_values_appear_once.intersection(right_enum_values), )
def _different_types_mapping( left_spec, # type: Spec right_spec, # type: Spec left_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] right_schema, # type: typing.Optional[typing.Mapping[typing.Text, typing.Any]] ): # type: (...) -> typing.Optional[EntityMapping[typing.Optional[typing.Text]]] left_type = left_schema.get('type') if left_schema is not None else None right_type = right_schema.get('type') if right_schema is not None else None if not left_type and left_spec.config[ 'default_type_to_object']: # Normalize type according to spec configuration left_type = 'object' if not right_type and right_spec.config[ 'default_type_to_object']: # Normalize type according to spec configuration right_type = 'object' if left_type == right_type: return None else: return EntityMapping( old=left_type, new=right_type, )
import mock import pytest from swagger_spec_compatibility.spec_utils import load_spec_from_spec_dict from swagger_spec_compatibility.util import EntityMapping from swagger_spec_compatibility.walkers.required_properties import _different_properties_mapping from swagger_spec_compatibility.walkers.required_properties import RequiredPropertiesDiff from swagger_spec_compatibility.walkers.required_properties import RequiredPropertiesDifferWalker @pytest.mark.parametrize( 'left_required_properties, right_required_properties, expected_value', [ (set(), set(), None), ({'property'}, {'property'}, None), (set(), {'property'}, EntityMapping(set(), {'property'})), ({'property'}, set(), EntityMapping({'property'}, set())), ({'property', 'old'}, {'property'}, EntityMapping({'old'}, set())), ({'property'}, {'property', 'new'}, EntityMapping(set(), {'new'})), ({'property', 'old'}, {'property', 'new' }, EntityMapping({'old'}, {'new'})), ], ) @mock.patch( 'swagger_spec_compatibility.walkers.required_properties.get_required_properties', autospec=True) def test__different_properties_mapping( mock_get_required_properties, left_required_properties, right_required_properties, expected_value,
[ { 'responses': { '200': { 'schema': {} } } }, { 'responses': { '200': { 'schema': {} } } }, [StatusCodeSchema('200', EntityMapping({}, {}))], ], [ { 'responses': { '200': { 'schema': {} }, '300': { 'schema': {} } } }, { 'responses': { '200': {
'additionalProperties': {} }, [], ), ( { 'additionalProperties': True }, { 'additionalProperties': False }, [ AdditionalPropertiesDiff( path=mock.sentinel.PATH, diff_type=DiffType.VALUE, additionalProperties=EntityMapping(old=True, new=False), properties=None, ), ], ), ( { 'additionalProperties': {} }, { 'additionalProperties': False }, [ AdditionalPropertiesDiff( path=mock.sentinel.PATH, diff_type=DiffType.VALUE,
def test_EnumValuesDifferWalker_returns_paths_of_endpoints_responses(minimal_spec_dict): old_spec_dict = dict( minimal_spec_dict, definitions={ 'enum_1': { 'type': 'string', 'enum': ['value_to_remove', 'E2', 'E3'], 'x-model': 'enum_1', }, 'enum_2': { 'type': 'string', 'enum': ['E1', 'E2', 'E3'], 'x-model': 'enum_2', }, 'object': { 'properties': { 'enum_1': {'$ref': '#/definitions/enum_1'}, 'enum_2': {'$ref': '#/definitions/enum_2'}, }, 'type': 'object', 'x-model': 'object', }, }, paths={ '/endpoint': { 'get': { 'parameters': [{ 'in': 'body', 'name': 'body', 'required': True, 'schema': { '$ref': '#/definitions/object', }, }], 'responses': { '200': { 'description': '', 'schema': { '$ref': '#/definitions/object', }, }, }, }, }, }, ) new_spec_dict = deepcopy(old_spec_dict) del new_spec_dict['definitions']['enum_1']['enum'][0] new_spec_dict['definitions']['enum_2']['enum'].append('new_value') old_spec = load_spec_from_spec_dict(old_spec_dict) new_spec = load_spec_from_spec_dict(new_spec_dict) assert sorted(EnumValuesDifferWalker(old_spec, new_spec).walk()) == sorted([ EnumValuesDiff( path=('definitions', 'enum_2'), mapping=EntityMapping(old=set(), new={'new_value'}), ), EnumValuesDiff( path=('definitions', 'object', 'properties', 'enum_2'), mapping=EntityMapping(old=set(), new={'new_value'}), ), EnumValuesDiff( path=('definitions', 'object', 'properties', 'enum_1'), mapping=EntityMapping(old={'value_to_remove'}, new=set()), ), EnumValuesDiff( path=('definitions', 'enum_1'), mapping=EntityMapping(old={'value_to_remove'}, new=set()), ), EnumValuesDiff( path=('paths', '/endpoint', 'get', 'responses', '200', 'schema', 'properties', 'enum_2'), mapping=EntityMapping(old=set(), new={'new_value'}), ), EnumValuesDiff( path=('paths', '/endpoint', 'get', 'responses', '200', 'schema', 'properties', 'enum_1'), mapping=EntityMapping(old={'value_to_remove'}, new=set()), ), EnumValuesDiff( path=('paths', '/endpoint', 'get', 'parameters', 0, 'schema', 'properties', 'enum_2'), mapping=EntityMapping(old=set(), new={'new_value'}), ), EnumValuesDiff( path=('paths', '/endpoint', 'get', 'parameters', 0, 'schema', 'properties', 'enum_1'), mapping=EntityMapping(old={'value_to_remove'}, new=set()), ), ])
from swagger_spec_compatibility.spec_utils import load_spec_from_spec_dict from swagger_spec_compatibility.util import EntityMapping from swagger_spec_compatibility.walkers.enum_values import _different_enum_values_mapping from swagger_spec_compatibility.walkers.enum_values import EnumValuesDiff from swagger_spec_compatibility.walkers.enum_values import EnumValuesDifferWalker @pytest.mark.parametrize( 'left_dict, right_dict, expected_value', [ (None, None, None), ({}, {}, None), ({'type': 'object'}, {}, None), ({'enum': ['v1']}, {}, None), ({'type': 'string', 'enum': ['v1']}, {}, EntityMapping({'v1'}, set())), ({}, {'type': 'string', 'enum': ['v1']}, EntityMapping(set(), {'v1'})), ({'type': 'string', 'enum': ['v1']}, {'type': 'string', 'enum': ['v1']}, None), ({'type': 'string', 'enum': ['v1', 'v2']}, {'type': 'string', 'enum': ['v2', 'v1']}, None), ({'type': 'string', 'enum': ['old', 'common']}, {'type': 'string', 'enum': ['common', 'new']}, EntityMapping({'old'}, {'new'})), ], ) def test__different_enum_values_mapping(left_dict, right_dict, expected_value): assert _different_enum_values_mapping( left_spec=mock.sentinel.LEFT_SPEC, right_spec=mock.sentinel.RIGHT_SPEC, left_schema=left_dict, right_schema=right_dict, ) == expected_value
def test_EntityMapping_upacking_works(): entity_mappint = EntityMapping(old=mock.sentinel.OLD, new=mock.sentinel.NEW) old, new = entity_mappint assert old == mock.sentinel.OLD assert new == mock.sentinel.NEW
from swagger_spec_compatibility.util import EntityMapping from swagger_spec_compatibility.walkers.changed_types import _different_types_mapping from swagger_spec_compatibility.walkers.changed_types import ChangedTypesDiff from swagger_spec_compatibility.walkers.changed_types import ChangedTypesDifferWalker @pytest.mark.parametrize( 'default_type_to_object, left_schema, right_schema, expected_value', [ (False, None, None, None), (True, None, None, None), (False, {}, {}, None), (True, {}, {}, None), (False, { 'type': 'object' }, {}, EntityMapping(old='object', new=None)), (True, { 'type': 'object' }, {}, None), (False, { 'type': 'object' }, { 'type': 'string' }, EntityMapping(old='object', new='string')), (True, { 'type': 'object' }, { 'type': 'string' }, EntityMapping(old='object', new='string')), ], )