From 1592c187950fadf81498d26f14f02dc1caa270d4 Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Wed, 6 Mar 2019 19:46:15 +0100 Subject: [PATCH 1/8] Store additional information in the schema (name and deref) and also store schema in errors. This gives us the ability to provide additional context when an error occurs, for example if the schema is named then the name of the schema, or if the yaml is parsed with ruamel.yaml we can show the line and column number that generated the error. --- openapi_core/schema/schemas/exceptions.py | 19 +++- openapi_core/schema/schemas/factories.py | 3 +- openapi_core/schema/schemas/generators.py | 2 +- openapi_core/schema/schemas/models.py | 101 +++++++++++++--------- openapi_core/schema/schemas/registries.py | 6 +- requirements_dev.txt | 3 +- 6 files changed, 88 insertions(+), 46 deletions(-) diff --git a/openapi_core/schema/schemas/exceptions.py b/openapi_core/schema/schemas/exceptions.py index b4656aa3..cd985c8d 100644 --- a/openapi_core/schema/schemas/exceptions.py +++ b/openapi_core/schema/schemas/exceptions.py @@ -3,8 +3,9 @@ import attr +@attr.s class OpenAPISchemaError(OpenAPIMappingError): - pass + schema = attr.ib() @attr.s @@ -77,3 +78,19 @@ class MultipleOneOfSchema(OpenAPISchemaError): def __str__(self): return "Exactly one schema type {0} should be valid, more than one found".format(self.type) + + +@attr.s +class InvalidSchema(OpenAPISchemaError): + msg = attr.ib() + + def __str__(self): + return self.msg + + +@attr.s +class InvalidFormat(OpenAPISchemaError): + msg = attr.ib() + + def __str__(self): + return self.msg diff --git a/openapi_core/schema/schemas/factories.py b/openapi_core/schema/schemas/factories.py index 50f04baa..61de7861 100644 --- a/openapi_core/schema/schemas/factories.py +++ b/openapi_core/schema/schemas/factories.py @@ -13,7 +13,7 @@ class SchemaFactory(object): def __init__(self, dereferencer): self.dereferencer = dereferencer - def create(self, schema_spec): + def create(self, schema_spec, schema_name=''): schema_deref = self.dereferencer.dereference(schema_spec) schema_type = schema_deref.get('type', None) @@ -75,6 +75,7 @@ def create(self, schema_spec): exclusive_maximum=exclusive_maximum, exclusive_minimum=exclusive_minimum, min_properties=min_properties, max_properties=max_properties, + schema_name=schema_name, schema_deref=schema_deref, ) @property diff --git a/openapi_core/schema/schemas/generators.py b/openapi_core/schema/schemas/generators.py index 59fd548b..2d52d148 100644 --- a/openapi_core/schema/schemas/generators.py +++ b/openapi_core/schema/schemas/generators.py @@ -16,5 +16,5 @@ def generate(self, schemas_spec): schemas_deref = self.dereferencer.dereference(schemas_spec) for schema_name, schema_spec in iteritems(schemas_deref): - schema, _ = self.schemas_registry.get_or_create(schema_spec) + schema, _ = self.schemas_registry.get_or_create(schema_spec, schema_name) yield schema_name, schema diff --git a/openapi_core/schema/schemas/models.py b/openapi_core/schema/schemas/models.py index 6ae4dc9a..45d671d4 100644 --- a/openapi_core/schema/schemas/models.py +++ b/openapi_core/schema/schemas/models.py @@ -17,6 +17,7 @@ InvalidSchemaValue, UndefinedSchemaProperty, MissingSchemaProperty, OpenAPISchemaError, NoOneOfSchema, MultipleOneOfSchema, NoValidSchema, UndefinedItemsSchema, InvalidCustomFormatSchemaValue, InvalidSchemaProperty, + InvalidSchema, InvalidFormat ) from openapi_core.schema.schemas.util import ( forcebool, format_date, format_datetime, @@ -72,7 +73,8 @@ def __init__( min_length=None, max_length=None, pattern=None, unique_items=False, minimum=None, maximum=None, multiple_of=None, exclusive_minimum=False, exclusive_maximum=False, - min_properties=None, max_properties=None): + min_properties=None, max_properties=None, schema_name='', + schema_deref=None): self.type = SchemaType(schema_type) self.model = model self.properties = properties and dict(properties) or {} @@ -106,6 +108,9 @@ def __init__( self._all_required_properties_cache = None self._all_optional_properties_cache = None + self.schema_name = schema_name + self.schema_deref = schema_deref + def __getitem__(self, name): return self.properties[name] @@ -165,7 +170,9 @@ def cast(self, value, custom_formatters=None): """Cast value to schema type""" if value is None: if not self.nullable: - raise InvalidSchemaValue("Null value for non-nullable schema", value, self.type) + raise InvalidSchemaValue( + self, "Null value for non-nullable schema", value, self.type + ) return self.default cast_mapping = self.get_cast_mapping(custom_formatters=custom_formatters) @@ -178,7 +185,8 @@ def cast(self, value, custom_formatters=None): return cast_callable(value) except ValueError: raise InvalidSchemaValue( - "Failed to cast value {value} to type {type}", value, self.type) + self, "Failed to cast value {value} to type {type}", value, self.type + ) def unmarshal(self, value, custom_formatters=None): """Unmarshal parameter from the value.""" @@ -192,7 +200,8 @@ def unmarshal(self, value, custom_formatters=None): if self.enum and casted not in self.enum: raise InvalidSchemaValue( - "Value {value} not in enum choices: {type}", value, self.enum) + self, "Value {value} not in enum choices: {type}", value, self.enum + ) return casted @@ -204,9 +213,9 @@ def _unmarshal_string(self, value, custom_formatters=None): if custom_formatters is not None: formatstring = custom_formatters.get(self.format) if formatstring is None: - raise InvalidSchemaValue(msg, value, self.format) + raise InvalidSchemaValue(self, msg, value, self.format) else: - raise InvalidSchemaValue(msg, value, self.format) + raise InvalidSchemaValue(self, msg, value, self.format) else: formatstring = self.STRING_FORMAT_CALLABLE_GETTER[schema_format] @@ -214,7 +223,8 @@ def _unmarshal_string(self, value, custom_formatters=None): return formatstring.unmarshal(value) except ValueError as exc: raise InvalidCustomFormatSchemaValue( - "Failed to format value {value} to format {type}: {exception}", value, self.format, exc) + self, "Failed to format value {value} to format {type}: {exception}", value, self.format, exc + ) def _unmarshal_any(self, value, custom_formatters=None): types_resolve_order = [ @@ -230,11 +240,11 @@ def _unmarshal_any(self, value, custom_formatters=None): except (OpenAPISchemaError, TypeError, ValueError): continue - raise NoValidSchema(value) + raise NoValidSchema(self, value) def _unmarshal_collection(self, value, custom_formatters=None): if self.items is None: - raise UndefinedItemsSchema(self.type) + raise UndefinedItemsSchema(self, self.type) f = functools.partial(self.items.unmarshal, custom_formatters=custom_formatters) @@ -243,7 +253,7 @@ def _unmarshal_collection(self, value, custom_formatters=None): def _unmarshal_object(self, value, model_factory=None, custom_formatters=None): if not isinstance(value, (dict, )): - raise InvalidSchemaValue("Value {value} is not of type {type}", value, self.type) + raise InvalidSchemaValue(self, "Value {value} is not of type {type}", value, self.type) model_factory = model_factory or ModelFactory() @@ -257,11 +267,11 @@ def _unmarshal_object(self, value, model_factory=None, pass else: if properties is not None: - raise MultipleOneOfSchema(self.type) + raise MultipleOneOfSchema(self, self.type) properties = found_props if properties is None: - raise NoOneOfSchema(self.type) + raise NoOneOfSchema(self, self.type) else: properties = self._unmarshal_properties( @@ -285,7 +295,7 @@ def _unmarshal_properties(self, value, one_of_schema=None, value_props_names = value.keys() extra_props = set(value_props_names) - set(all_props_names) if extra_props and self.additional_properties is None: - raise UndefinedSchemaProperty(extra_props) + raise UndefinedSchemaProperty(self, extra_props) properties = {} for prop_name in extra_props: @@ -298,7 +308,7 @@ def _unmarshal_properties(self, value, one_of_schema=None, prop_value = value[prop_name] except KeyError: if prop_name in all_req_props_names: - raise MissingSchemaProperty(prop_name) + raise MissingSchemaProperty(self, prop_name) if not prop.nullable and not prop.default: continue prop_value = prop.default @@ -306,7 +316,7 @@ def _unmarshal_properties(self, value, one_of_schema=None, properties[prop_name] = prop.unmarshal( prop_value, custom_formatters=custom_formatters) except OpenAPISchemaError as exc: - raise InvalidSchemaProperty(prop_name, exc) + raise InvalidSchemaProperty(self, prop_name, exc) self._validate_properties(properties, one_of_schema=one_of_schema, custom_formatters=custom_formatters) @@ -330,7 +340,7 @@ def default(x, **kw): def validate(self, value, custom_formatters=None): if value is None: if not self.nullable: - raise InvalidSchemaValue("Null value for non-nullable schema of type {type}", value, self.type) + raise InvalidSchemaValue(self, "Null value for non-nullable schema of type {type}", value, self.type) return # type validation @@ -338,7 +348,7 @@ def validate(self, value, custom_formatters=None): self.type] if not type_validator_callable(value): raise InvalidSchemaValue( - "Value {value} not valid type {type}", value, self.type.value) + self, "Value {value} not valid type {type}", value, self.type.value) # structure validation validator_mapping = self.get_validator_mapping() @@ -349,30 +359,34 @@ def validate(self, value, custom_formatters=None): def _validate_collection(self, value, custom_formatters=None): if self.items is None: - raise UndefinedItemsSchema(self.type) + raise UndefinedItemsSchema(self, self.type) if self.min_items is not None: if self.min_items < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for collection invalid:" - " minItems must be non-negative" + " minItems must be non-negative", ) if len(value) < self.min_items: raise InvalidSchemaValue( + self, "Value must contain at least {type} item(s)," " {value} found", len(value), self.min_items) if self.max_items is not None: if self.max_items < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for collection invalid:" " maxItems must be non-negative" ) if len(value) > self.max_items: raise InvalidSchemaValue( + self, "Value must contain at most {value} item(s)," " {type} found", len(value), self.max_items) if self.unique_items and len(set(value)) != len(value): - raise OpenAPISchemaError("Value may not contain duplicate items") + raise InvalidSchemaValue(self, "Value may not contain duplicate items") f = functools.partial(self.items.validate, custom_formatters=custom_formatters) @@ -382,22 +396,22 @@ def _validate_number(self, value, custom_formatters=None): if self.minimum is not None: if self.exclusive_minimum and value <= self.minimum: raise InvalidSchemaValue( - "Value {value} is not less than or equal to {type}", value, self.minimum) + self, "Value {value} is not less than or equal to {type}", value, self.minimum) elif value < self.minimum: raise InvalidSchemaValue( - "Value {value} is not less than {type}", value, self.minimum) + self, "Value {value} is not less than {type}", value, self.minimum) if self.maximum is not None: if self.exclusive_maximum and value >= self.maximum: raise InvalidSchemaValue( - "Value {value} is not greater than or equal to {type}", value, self.maximum) + self, "Value {value} is not greater than or equal to {type}", value, self.maximum) elif value > self.maximum: raise InvalidSchemaValue( - "Value {value} is not greater than {type}", value, self.maximum) + self, "Value {value} is not greater than {type}", value, self.maximum) if self.multiple_of is not None and value % self.multiple_of: raise InvalidSchemaValue( - "Value {value} is not a multiple of {type}", + self, "Value {value} is not a multiple of {type}", value, self.multiple_of) def _validate_string(self, value, custom_formatters=None): @@ -408,41 +422,46 @@ def _validate_string(self, value, custom_formatters=None): if custom_formatters is not None: formatstring = custom_formatters.get(self.format) if formatstring is None: - raise OpenAPISchemaError(msg) + raise InvalidFormat(self, msg) else: - raise OpenAPISchemaError(msg) + raise InvalidFormat(self, msg) else: formatstring =\ self.STRING_FORMAT_CALLABLE_GETTER[schema_format] if not formatstring.validate(value): raise InvalidSchemaValue( - "Value {value} not valid format {type}", value, self.format) + self, "Value {value} not valid format {type}", value, self.format) if self.min_length is not None: if self.min_length < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for string invalid:" " minLength must be non-negative" ) if len(value) < self.min_length: raise InvalidSchemaValue( + self, "Value is shorter ({value}) than the minimum length of {type}", len(value), self.min_length ) if self.max_length is not None: if self.max_length < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for string invalid:" " maxLength must be non-negative" ) if len(value) > self.max_length: raise InvalidSchemaValue( + self, "Value is longer ({value}) than the maximum length of {type}", len(value), self.max_length ) if self.pattern is not None and not self.pattern.search(value): raise InvalidSchemaValue( + self, "Value {value} does not match the pattern {type}", value, self.pattern.pattern ) @@ -463,11 +482,11 @@ def _validate_object(self, value, custom_formatters=None): pass else: if valid_one_of_schema is not None: - raise MultipleOneOfSchema(self.type) + raise MultipleOneOfSchema(self, self.type) valid_one_of_schema = True if valid_one_of_schema is None: - raise NoOneOfSchema(self.type) + raise NoOneOfSchema(self, self.type) else: self._validate_properties(properties, @@ -475,25 +494,29 @@ def _validate_object(self, value, custom_formatters=None): if self.min_properties is not None: if self.min_properties < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for object invalid:" " minProperties must be non-negative" ) if len(properties) < self.min_properties: raise InvalidSchemaValue( + self, "Value must contain at least {type} properties," " {value} found", len(properties), self.min_properties ) if self.max_properties is not None: if self.max_properties < 0: - raise OpenAPISchemaError( + raise InvalidSchema( + self, "Schema for object invalid:" " maxProperties must be non-negative" ) if len(properties) > self.max_properties: raise InvalidSchemaValue( + self, "Value must contain at most {type} properties," " {value} found", len(properties), self.max_properties ) @@ -516,7 +539,7 @@ def _validate_properties(self, value, one_of_schema=None, value_props_names = value.keys() extra_props = set(value_props_names) - set(all_props_names) if extra_props and self.additional_properties is None: - raise UndefinedSchemaProperty(extra_props) + raise UndefinedSchemaProperty(self, extra_props) for prop_name in extra_props: prop_value = value[prop_name] @@ -528,13 +551,13 @@ def _validate_properties(self, value, one_of_schema=None, prop_value = value[prop_name] except KeyError: if prop_name in all_req_props_names: - raise MissingSchemaProperty(prop_name) + raise MissingSchemaProperty(self, prop_name) if not prop.nullable and not prop.default: continue prop_value = prop.default try: prop.validate(prop_value, custom_formatters=custom_formatters) except OpenAPISchemaError as exc: - raise InvalidSchemaProperty(prop_name, original_exception=exc) + raise InvalidSchemaProperty(self, prop_name, original_exception=exc) return True diff --git a/openapi_core/schema/schemas/registries.py b/openapi_core/schema/schemas/registries.py index 3a6d963e..c3b97365 100644 --- a/openapi_core/schema/schemas/registries.py +++ b/openapi_core/schema/schemas/registries.py @@ -15,7 +15,7 @@ def __init__(self, dereferencer): super(SchemaRegistry, self).__init__(dereferencer) self._schemas = {} - def get_or_create(self, schema_spec): + def get_or_create(self, schema_spec, schema_name=''): schema_hash = dicthash(schema_spec) schema_deref = self.dereferencer.dereference(schema_spec) @@ -23,9 +23,9 @@ def get_or_create(self, schema_spec): return self._schemas[schema_hash], False if '$ref' in schema_spec: - schema = Proxy(lambda: self.create(schema_deref)) + schema = Proxy(lambda: self.create(schema_deref, schema_name)) else: - schema = self.create(schema_deref) + schema = self.create(schema_deref, schema_name) self._schemas[schema_hash] = schema diff --git a/requirements_dev.txt b/requirements_dev.txt index 7d4afaac..ec1de4d4 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -2,4 +2,5 @@ mock==2.0.0 pytest==3.5.0 pytest-flake8 pytest-cov==2.5.1 -flask \ No newline at end of file +flask +ruamel.yaml==0.15.89 \ No newline at end of file From d77667621f493ab4446f5e77a32fa88b6bb94519 Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Fri, 8 Mar 2019 21:15:51 +0100 Subject: [PATCH 2/8] added tests for InvalidFormat and InvalidSchema --- tests/unit/schema/test_schemas.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 302719fb..c08d9549 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -6,7 +6,8 @@ from openapi_core.extensions.models.models import Model from openapi_core.schema.schemas.exceptions import ( - InvalidSchemaValue, MultipleOneOfSchema, NoOneOfSchema, OpenAPISchemaError, + InvalidSchemaValue, MultipleOneOfSchema, NoOneOfSchema, + OpenAPISchemaError, InvalidSchema, InvalidFormat ) from openapi_core.schema.schemas.models import Schema @@ -763,3 +764,27 @@ def test_list_unique_items_invalid(self, value): with pytest.raises(Exception): schema.validate(value) + + @pytest.mark.parametrize('schema,value', [ + (Schema('array', items=Schema('number'), min_items=-1), []), + (Schema('array', items=Schema('number'), max_items=-1), []), + (Schema('string', min_length=-1), u('')), + (Schema('string', max_length=-1), u('')), + (Schema('object', min_properties=-1), Model()), + (Schema('object', max_properties=-1), Model()), + ]) + def test_validate_invalid_schema(self, schema, value): + with pytest.raises(InvalidSchema): + schema.validate(value) + + @pytest.mark.parametrize('custom_formatters', [ + {}, + None, + ]) + def test_validate_string_format_unknown(self, custom_formatters): + unknown_format = 'unknown' + schema = Schema('string', schema_format=unknown_format) + value = 'x' + + with pytest.raises(InvalidFormat): + schema.validate(value) \ No newline at end of file From e534f23c36082fe07ae0daa9bcd3066b032f559b Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Fri, 8 Mar 2019 21:22:56 +0100 Subject: [PATCH 3/8] add newline to test_schemas.py --- tests/unit/schema/test_schemas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index c08d9549..1d9c688b 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -787,4 +787,4 @@ def test_validate_string_format_unknown(self, custom_formatters): value = 'x' with pytest.raises(InvalidFormat): - schema.validate(value) \ No newline at end of file + schema.validate(value) From bddc61e7b24d264b9ec965f65ff71560052f68f1 Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Fri, 8 Mar 2019 22:03:17 +0100 Subject: [PATCH 4/8] ensure that all changes to raising errors are covered by tests --- tests/unit/schema/test_schemas.py | 65 +++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 1d9c688b..7fcd686d 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -7,7 +7,8 @@ from openapi_core.extensions.models.models import Model from openapi_core.schema.schemas.exceptions import ( InvalidSchemaValue, MultipleOneOfSchema, NoOneOfSchema, - OpenAPISchemaError, InvalidSchema, InvalidFormat + OpenAPISchemaError, InvalidSchema, InvalidFormat, NoValidSchema, + InvalidSchemaProperty ) from openapi_core.schema.schemas.models import Schema @@ -103,27 +104,28 @@ def test_string_format_custom(self): assert result == 'x-custom' - def test_string_format_unknown(self): + @pytest.mark.parametrize("custom_formatters", (None, {})) + def test_string_format_unknown(self, custom_formatters): unknown_format = 'unknown' schema = Schema('string', schema_format=unknown_format) value = 'x' with pytest.raises(OpenAPISchemaError): - schema.unmarshal(value) + schema.unmarshal(value, custom_formatters) - @pytest.mark.xfail(reason="No custom formats support atm") def test_string_format_invalid_value(self): custom_format = 'custom' schema = Schema('string', schema_format=custom_format) value = 'x' - with mock.patch.dict( - Schema.STRING_FORMAT_CAST_CALLABLE_GETTER, - {custom_format: mock.Mock(side_effect=ValueError())}, - ), pytest.raises( - InvalidSchemaValue, message='Failed to format value' - ): - schema.unmarshal(value) + def _raise(e): raise e() + + custom_formatters = { + custom_format: mock.Mock(unmarshal=lambda x: _raise(ValueError)) + } + + with pytest.raises(InvalidSchemaValue, message='Failed to format value'): + schema.unmarshal(value, custom_formatters) def test_integer_valid(self): schema = Schema('integer') @@ -172,6 +174,38 @@ def test_integer_invalid(self): with pytest.raises(InvalidSchemaValue): schema.unmarshal(value) + def test_any_no_valid_schema(self): + schema = Schema() + + class Uncastable: + def _raise(self): + raise ValueError() + __nonzero__ = __bool__ = __trunc__ = __float__ = __str__ = _raise + + value = Uncastable() + + with pytest.raises(NoValidSchema): + schema.unmarshal(value) + + def test_multiple_one_of(self): + schema = Schema('object', one_of=[ + Schema('object', properties={ + 'one': Schema('string') + }), + Schema('object', properties={ + 'one': Schema('string') + }), + ]) + with pytest.raises(MultipleOneOfSchema): + schema.unmarshal({'one': 'one'}) + + def test_invalid_schema_property(self): + schema = Schema('object', properties={ + 'one': Schema('integer') + }) + with pytest.raises(InvalidSchemaProperty): + schema.unmarshal({'one': 'one'}) + class TestSchemaValidate(object): @@ -787,4 +821,11 @@ def test_validate_string_format_unknown(self, custom_formatters): value = 'x' with pytest.raises(InvalidFormat): - schema.validate(value) + schema.validate(value, custom_formatters) + + def test_invalid_schema_property(self): + schema = Schema('object', properties={ + 'one': Schema('integer') + }) + with pytest.raises(InvalidSchemaProperty): + schema.validate(Model({'one': 'one'})) \ No newline at end of file From b7f2e33572f0b7756304d7562ddf5b27337a9951 Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Fri, 8 Mar 2019 22:12:59 +0100 Subject: [PATCH 5/8] fix linter errors --- tests/unit/schema/test_schemas.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/schema/test_schemas.py b/tests/unit/schema/test_schemas.py index 7fcd686d..645eedd8 100644 --- a/tests/unit/schema/test_schemas.py +++ b/tests/unit/schema/test_schemas.py @@ -124,7 +124,10 @@ def _raise(e): raise e() custom_format: mock.Mock(unmarshal=lambda x: _raise(ValueError)) } - with pytest.raises(InvalidSchemaValue, message='Failed to format value'): + with pytest.raises( + InvalidSchemaValue, + message='Failed to format value' + ): schema.unmarshal(value, custom_formatters) def test_integer_valid(self): @@ -828,4 +831,4 @@ def test_invalid_schema_property(self): 'one': Schema('integer') }) with pytest.raises(InvalidSchemaProperty): - schema.validate(Model({'one': 'one'})) \ No newline at end of file + schema.validate(Model({'one': 'one'})) From 4456f580e89f538800071059253051b0823db927 Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Sat, 9 Mar 2019 08:06:10 +0100 Subject: [PATCH 6/8] added simple test that checks that each schema Exception type can be converted to a string without error --- tests/unit/schema/test_exceptions.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 tests/unit/schema/test_exceptions.py diff --git a/tests/unit/schema/test_exceptions.py b/tests/unit/schema/test_exceptions.py new file mode 100644 index 00000000..18fc4f86 --- /dev/null +++ b/tests/unit/schema/test_exceptions.py @@ -0,0 +1,25 @@ +from openapi_core.schema.schemas import exceptions +import pytest + + +def is_open_api_exception(exception_type): + try: + return issubclass(exception_type, exceptions.OpenAPISchemaError) + except TypeError: + return False + + +class TestExceptions: + + @pytest.mark.parametrize( + "exception_type", + ( + exception_type + for exception_type_name in dir(exceptions) + for exception_type in [getattr(exceptions, exception_type_name)] + if is_open_api_exception(exception_type) + ) + ) + def test_convert_to_string(self, exception_type): + # verify that we can convert to a string without error + str(exception_type) From df697c963d0135c23425f79e37856c51750d21fc Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Sat, 9 Mar 2019 08:34:15 +0100 Subject: [PATCH 7/8] actually instantiate exception_type in exception test --- tests/unit/schema/test_exceptions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/unit/schema/test_exceptions.py b/tests/unit/schema/test_exceptions.py index 18fc4f86..d8abe54c 100644 --- a/tests/unit/schema/test_exceptions.py +++ b/tests/unit/schema/test_exceptions.py @@ -1,5 +1,7 @@ from openapi_core.schema.schemas import exceptions import pytest +import inspect +import attr def is_open_api_exception(exception_type): @@ -22,4 +24,5 @@ class TestExceptions: ) def test_convert_to_string(self, exception_type): # verify that we can convert to a string without error - str(exception_type) + args = ['x'] * len(attr.fields(exception_type)) + str(exception_type(*args)) From 9969e6c8caaee1fcb488e30de0890805577131bb Mon Sep 17 00:00:00 2001 From: Daniel Bradburn Date: Sat, 9 Mar 2019 08:38:17 +0100 Subject: [PATCH 8/8] removed unused import --- tests/unit/schema/test_exceptions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/schema/test_exceptions.py b/tests/unit/schema/test_exceptions.py index d8abe54c..8bf7d2cf 100644 --- a/tests/unit/schema/test_exceptions.py +++ b/tests/unit/schema/test_exceptions.py @@ -1,6 +1,5 @@ from openapi_core.schema.schemas import exceptions import pytest -import inspect import attr