From 40944119d156f3496d65f55b4b3c9ae377287d50 Mon Sep 17 00:00:00 2001 From: Gerhard Weis Date: Sun, 9 Jun 2019 08:41:28 +1000 Subject: [PATCH 1/3] add failing test for parameters on path item object --- tests/integration/test_validators.py | 69 ++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 37b48329..0c3fa34d 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -9,6 +9,7 @@ from openapi_core.extensions.models.models import BaseModel from openapi_core.schema.operations.exceptions import InvalidOperation from openapi_core.schema.parameters.exceptions import MissingRequiredParameter +from openapi_core.schema.parameters.exceptions import InvalidParameterValue from openapi_core.schema.request_bodies.exceptions import MissingRequestBody from openapi_core.schema.responses.exceptions import ( MissingResponseContent, InvalidResponse, @@ -220,6 +221,74 @@ def test_get_pet(self, validator): } +class TestPathItemParamsValidator(object): + + @pytest.fixture + def spec_dict(self, factory): + return { + "openapi": "3.0.0", + "info": { + "title": "Test path item parameter validation", + "version": "0.1", + }, + "paths": { + "/resource": { + "parameters": [ + { + "name": "resId", + "in": "query", + "required": True, + "schema": { + "type": "integer", + }, + }, + ], + "get": { + "responses": { + "default": { + "description": "Return the resource." + } + } + } + } + } + } + + @pytest.fixture + def spec(self, spec_dict): + return create_spec(spec_dict) + + @pytest.fixture + def validator(self, spec): + return RequestValidator(spec) + + def test_request_missing_param(self, validator): + request = MockRequest('http://example.com', 'get', '/resource') + result = validator.validate(request) + + assert len(result.errors) == 1 + assert type(result.errors[0]) == MissingRequiredParameter + assert result.body is None + assert result.parameters == {} + + def test_request_invalid_param(self, validator): + request = MockRequest('http://example.com', 'get', '/resource', args={'resId': 'invalid'}) + result = validator.validate(request) + + assert len(result.errors) == 1 + assert type(result.errors[0]) == InvalidParameterValue + assert result.body is None + assert result.parameters == {} + + def test_request_valid_param(self, validator): + request = MockRequest('http://example.com', 'get', '/resource', args={'resId': '10'}) + result = validator.validate(request) + + assert len(result.errors) == 0 + assert result.body is None + assert result.parameters == {'query': {'resId': 10}} + + class TestResponseValidator(object): host_url = 'http://petstore.swagger.io' From 27ebae3182784eeb718dcb5e22d25c3ac5da6873 Mon Sep 17 00:00:00 2001 From: Artur Maciag Date: Tue, 18 Jun 2019 16:13:44 +0100 Subject: [PATCH 2/3] Parameters on path item object support --- openapi_core/schema/paths/exceptions.py | 15 +++++++++++++++ openapi_core/schema/specs/models.py | 11 +++++++++-- openapi_core/validation/request/models.py | 10 ++++++++++ openapi_core/validation/request/validators.py | 14 +++++++++++--- tests/integration/test_minimal.py | 18 +++++++++++++++++- tests/integration/test_validators.py | 13 ++++++++++++- 6 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 openapi_core/schema/paths/exceptions.py diff --git a/openapi_core/schema/paths/exceptions.py b/openapi_core/schema/paths/exceptions.py new file mode 100644 index 00000000..6a287739 --- /dev/null +++ b/openapi_core/schema/paths/exceptions.py @@ -0,0 +1,15 @@ +import attr + +from openapi_core.schema.exceptions import OpenAPIMappingError + + +class OpenAPIPathError(OpenAPIMappingError): + pass + + +@attr.s(hash=True) +class InvalidPath(OpenAPIPathError): + path_pattern = attr.ib() + + def __str__(self): + return "Unknown path {0}".format(self.path_pattern) diff --git a/openapi_core/schema/specs/models.py b/openapi_core/schema/specs/models.py index e1aa8933..7e7c4e14 100644 --- a/openapi_core/schema/specs/models.py +++ b/openapi_core/schema/specs/models.py @@ -4,6 +4,7 @@ from openapi_core.compat import partialmethod from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.schema.servers.exceptions import InvalidServer @@ -19,8 +20,8 @@ def __init__(self, info, paths, servers=None, components=None): self.servers = servers or [] self.components = components - def __getitem__(self, path_name): - return self.paths[path_name] + def __getitem__(self, path_pattern): + return self.get_path(path_pattern) @property def default_url(self): @@ -36,6 +37,12 @@ def get_server(self, full_url_pattern): def get_server_url(self, index=0): return self.servers[index].default_url + def get_path(self, path_pattern): + try: + return self.paths[path_pattern] + except KeyError: + raise InvalidPath(path_pattern) + def get_operation(self, path_pattern, http_method): try: return self.paths[path_pattern].operations[http_method] diff --git a/openapi_core/validation/request/models.py b/openapi_core/validation/request/models.py index 55d48870..85ebc110 100644 --- a/openapi_core/validation/request/models.py +++ b/openapi_core/validation/request/models.py @@ -16,6 +16,16 @@ def __getitem__(self, location): def __setitem__(self, location, value): raise NotImplementedError + def __add__(self, other): + if not isinstance(other, self.__class__): + raise ValueError("Invalid type") + + for location in self.valid_locations: + if location in other: + self[location].update(other[location]) + + return self + @classmethod def validate_location(cls, location): if location not in cls.valid_locations: diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 08593f63..c36b6a20 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -26,6 +26,14 @@ def validate(self, request): server.default_url, request.full_url_pattern ) + try: + path = self.spec[operation_pattern] + # don't process if operation errors + except OpenAPIMappingError as exc: + return RequestValidationResult([exc, ], None, None) + + path_params, path_params_errors = self._get_parameters(request, path) + try: operation = self.spec.get_operation( operation_pattern, request.method) @@ -33,11 +41,11 @@ def validate(self, request): except OpenAPIMappingError as exc: return RequestValidationResult([exc, ], None, None) - params, params_errors = self._get_parameters(request, operation) + op_params, op_params_errors = self._get_parameters(request, operation) body, body_errors = self._get_body(request, operation) - errors = params_errors + body_errors - return RequestValidationResult(errors, body, params) + errors = path_params_errors + op_params_errors + body_errors + return RequestValidationResult(errors, body, path_params + op_params) def _get_parameters(self, request, operation): errors = [] diff --git a/tests/integration/test_minimal.py b/tests/integration/test_minimal.py index 1dcc79d4..a3079855 100644 --- a/tests/integration/test_minimal.py +++ b/tests/integration/test_minimal.py @@ -1,6 +1,7 @@ import pytest from openapi_core.schema.operations.exceptions import InvalidOperation +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.shortcuts import create_spec from openapi_core.validation.request.validators import RequestValidator from openapi_core.wrappers.mock import MockRequest @@ -39,7 +40,7 @@ def test_invalid_operation(self, factory, server, spec_path): spec_dict = factory.spec_from_file(spec_path) spec = create_spec(spec_dict) validator = RequestValidator(spec) - request = MockRequest(server, "get", "/nonexistent") + request = MockRequest(server, "post", "/status") result = validator.validate(request) @@ -47,3 +48,18 @@ def test_invalid_operation(self, factory, server, spec_path): assert isinstance(result.errors[0], InvalidOperation) assert result.body is None assert result.parameters == {} + + @pytest.mark.parametrize("server", servers) + @pytest.mark.parametrize("spec_path", spec_paths) + def test_invalid_path(self, factory, server, spec_path): + spec_dict = factory.spec_from_file(spec_path) + spec = create_spec(spec_dict) + validator = RequestValidator(spec) + request = MockRequest(server, "get", "/nonexistent") + + result = validator.validate(request) + + assert len(result.errors) == 1 + assert isinstance(result.errors[0], InvalidPath) + assert result.body is None + assert result.parameters == {} diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 0c3fa34d..820f0253 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -10,6 +10,7 @@ from openapi_core.schema.operations.exceptions import InvalidOperation from openapi_core.schema.parameters.exceptions import MissingRequiredParameter from openapi_core.schema.parameters.exceptions import InvalidParameterValue +from openapi_core.schema.paths.exceptions import InvalidPath from openapi_core.schema.request_bodies.exceptions import MissingRequestBody from openapi_core.schema.responses.exceptions import ( MissingResponseContent, InvalidResponse, @@ -55,11 +56,21 @@ def test_request_server_error(self, validator): assert result.body is None assert result.parameters == {} - def test_invalid_operation(self, validator): + def test_invalid_path(self, validator): request = MockRequest(self.host_url, 'get', '/v1') result = validator.validate(request) + assert len(result.errors) == 1 + assert type(result.errors[0]) == InvalidPath + assert result.body is None + assert result.parameters == {} + + def test_invalid_operation(self, validator): + request = MockRequest(self.host_url, 'patch', '/v1/pets') + + result = validator.validate(request) + assert len(result.errors) == 1 assert type(result.errors[0]) == InvalidOperation assert result.body is None From e8d98dfdf7d4b0947a0731eb0f72a1bc7f5b7cb1 Mon Sep 17 00:00:00 2001 From: Artur Maciag Date: Tue, 18 Jun 2019 16:35:45 +0100 Subject: [PATCH 3/3] TestPathItemParamsValidator linting fix --- tests/integration/test_validators.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_validators.py b/tests/integration/test_validators.py index 820f0253..d6e6a5a3 100644 --- a/tests/integration/test_validators.py +++ b/tests/integration/test_validators.py @@ -283,7 +283,10 @@ def test_request_missing_param(self, validator): assert result.parameters == {} def test_request_invalid_param(self, validator): - request = MockRequest('http://example.com', 'get', '/resource', args={'resId': 'invalid'}) + request = MockRequest( + 'http://example.com', 'get', '/resource', + args={'resId': 'invalid'}, + ) result = validator.validate(request) assert len(result.errors) == 1 @@ -292,7 +295,10 @@ def test_request_invalid_param(self, validator): assert result.parameters == {} def test_request_valid_param(self, validator): - request = MockRequest('http://example.com', 'get', '/resource', args={'resId': '10'}) + request = MockRequest( + 'http://example.com', 'get', '/resource', + args={'resId': '10'}, + ) result = validator.validate(request) assert len(result.errors) == 0