From 2ec02e473e4f6ba8b7d937d96dcaa498f599b9ae Mon Sep 17 00:00:00 2001 From: Brendan McCollam Date: Tue, 11 Jun 2019 22:29:16 +0100 Subject: [PATCH 1/5] Adds failing test Adds flask_wrapper.yaml spec --- .../integration/data/v3.0/flask_wrapper.yaml | 19 +++ tests/integration/test_wrappers.py | 110 ++++++++++-------- 2 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 tests/integration/data/v3.0/flask_wrapper.yaml diff --git a/tests/integration/data/v3.0/flask_wrapper.yaml b/tests/integration/data/v3.0/flask_wrapper.yaml new file mode 100644 index 0000000..0dcfdd3 --- /dev/null +++ b/tests/integration/data/v3.0/flask_wrapper.yaml @@ -0,0 +1,19 @@ +openapi: "3.0.0" +info: + title: Basic OpenAPI specification used with test_wrappers.TestFlaskOpenAPIIValidation + version: "0.1" +servers: + - url: 'http://localhost' +paths: + '/browse/{id}/': + parameters: + - name: id + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: integer + get: + responses: + default: + description: Return the resource. diff --git a/tests/integration/test_wrappers.py b/tests/integration/test_wrappers.py index ff0c157..9d47c06 100644 --- a/tests/integration/test_wrappers.py +++ b/tests/integration/test_wrappers.py @@ -8,48 +8,56 @@ from werkzeug.test import create_environ from openapi_core.wrappers.flask import ( FlaskOpenAPIRequest, FlaskOpenAPIResponse, ) +from openapi_core.shortcuts import create_spec +from openapi_core.validation.response.validators import ResponseValidator + + +@pytest.fixture +def environ_factory(): + return create_environ + + +@pytest.fixture +def map(): + return Map([ + # Static URLs + Rule('/', endpoint='static/index'), + Rule('/about', endpoint='static/about'), + Rule('/help', endpoint='static/help'), + # Knowledge Base + Subdomain('kb', [ + Rule('/', endpoint='kb/index'), + Rule('/browse/', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse'), + Rule('/browse//', endpoint='kb/browse') + ]) + ], default_subdomain='www') + + +@pytest.fixture +def request_factory(map, environ_factory): + server_name = 'localhost' + + def create_request(method, path, subdomain=None, query_string=None): + environ = environ_factory(query_string=query_string) + req = Request(environ) + urls = map.bind_to_environ( + environ, server_name=server_name, subdomain=subdomain) + req.url_rule, req.view_args = urls.match( + path, method, return_rule=True) + return req + return create_request + + +@pytest.fixture +def response_factory(): + def create_response(data, status_code=200): + return Response(data, status=status_code) + return create_response class TestFlaskOpenAPIRequest(object): - server_name = 'localhost' - - @pytest.fixture - def environ_factory(self): - return create_environ - - @pytest.fixture - def map(self): - return Map([ - # Static URLs - Rule('/', endpoint='static/index'), - Rule('/about', endpoint='static/about'), - Rule('/help', endpoint='static/help'), - # Knowledge Base - Subdomain('kb', [ - Rule('/', endpoint='kb/index'), - Rule('/browse/', endpoint='kb/browse'), - Rule('/browse//', endpoint='kb/browse'), - Rule('/browse//', endpoint='kb/browse') - ]) - ], default_subdomain='www') - - @pytest.fixture - def request_factory(self, map, environ_factory): - def create_request(method, path, subdomain=None, query_string=None): - environ = environ_factory(query_string=query_string) - req = Request(environ) - urls = map.bind_to_environ( - environ, server_name=self.server_name, subdomain=subdomain) - req.url_rule, req.view_args = urls.match( - path, method, return_rule=True) - return req - return create_request - - @pytest.fixture - def openapi_request(self, request): - return FlaskOpenAPIRequest(request) - def test_simple(self, request_factory, request): request = request_factory('GET', '/', subdomain='www') @@ -73,8 +81,7 @@ class TestFlaskOpenAPIRequest(object): assert openapi_request.mimetype == request.mimetype def test_multiple_values(self, request_factory, request): - request = request_factory( - 'GET', '/', subdomain='www', query_string='a=b&a=c') + request = request_factory('GET', '/', subdomain='www', query_string='a=b&a=c') openapi_request = FlaskOpenAPIRequest(request) @@ -122,12 +129,6 @@ class TestFlaskOpenAPIRequest(object): class TestFlaskOpenAPIResponse(object): - @pytest.fixture - def response_factory(self): - def create_response(data, status_code=200): - return Response(data, status=status_code) - return create_response - def test_invalid_server(self, response_factory): response = response_factory('Not Found', status_code=404) @@ -137,3 +138,20 @@ class TestFlaskOpenAPIResponse(object): assert openapi_response.data == response.data assert openapi_response.status_code == response._status_code assert openapi_response.mimetype == response.mimetype + + +class TestFlaskOpenAPIRequestValidation(object): + + specfile = 'data/v3.0/flask_wrapper.yaml' + + def test_response_validator_path_pattern( + self, + factory, + request_factory, + response_factory): + validator = ResponseValidator(create_spec(factory.spec_from_file(self.specfile))) + request = request_factory('GET', '/browse/12/', subdomain='kb') + openapi_request = FlaskOpenAPIRequest(request) + openapi_response = FlaskOpenAPIResponse(response_factory('Some item', status_code=200)) + result = validator.validate(openapi_request, openapi_response) + assert not result.errors From 3ef0c6adcfa74877245f586618c4592b308976cd Mon Sep 17 00:00:00 2001 From: Brendan McCollam Date: Tue, 11 Jun 2019 23:34:48 +0100 Subject: [PATCH 2/5] Convert Flask path variables to OpenAPI path parameters --- openapi_core/wrappers/flask.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openapi_core/wrappers/flask.py b/openapi_core/wrappers/flask.py index ab91665..14c330b 100644 --- a/openapi_core/wrappers/flask.py +++ b/openapi_core/wrappers/flask.py @@ -1,9 +1,16 @@ """OpenAPI core wrappers module""" +import re + from openapi_core.wrappers.base import BaseOpenAPIRequest, BaseOpenAPIResponse +# http://flask.pocoo.org/docs/1.0/quickstart/#variable-rules +PATH_PARAMETER_PATTERN = r'<(?:(?:string|int|float|path|uuid):)?(\w+)>' + class FlaskOpenAPIRequest(BaseOpenAPIRequest): + path_regex = re.compile(PATH_PARAMETER_PATTERN) + def __init__(self, request): self.request = request @@ -24,7 +31,7 @@ class FlaskOpenAPIRequest(BaseOpenAPIRequest): if self.request.url_rule is None: return self.path - return self.request.url_rule.rule + return self.path_regex.sub(r'{\1}', self.request.url_rule.rule) @property def parameters(self): From 934550324e513f266e9ad92242059d5888084b61 Mon Sep 17 00:00:00 2001 From: Brendan McCollam Date: Tue, 11 Jun 2019 23:51:03 +0100 Subject: [PATCH 3/5] Fix pre-existing test --- tests/integration/test_wrappers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/test_wrappers.py b/tests/integration/test_wrappers.py index 9d47c06..e6104f0 100644 --- a/tests/integration/test_wrappers.py +++ b/tests/integration/test_wrappers.py @@ -122,7 +122,7 @@ class TestFlaskOpenAPIRequest(object): assert openapi_request.host_url == request.host_url assert openapi_request.path == request.path assert openapi_request.method == request.method.lower() - assert openapi_request.path_pattern == request.url_rule.rule + assert openapi_request.path_pattern == '/browse/{id}/' assert openapi_request.body == request.data assert openapi_request.mimetype == request.mimetype From 2908015745524345d4111d477979b38390bd8ed4 Mon Sep 17 00:00:00 2001 From: Brendan McCollam Date: Wed, 12 Jun 2019 10:59:24 +0100 Subject: [PATCH 4/5] Adds test for request validator w/Flask wrapper formatting --- tests/integration/test_wrappers.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/tests/integration/test_wrappers.py b/tests/integration/test_wrappers.py index e6104f0..d64e721 100644 --- a/tests/integration/test_wrappers.py +++ b/tests/integration/test_wrappers.py @@ -1,15 +1,14 @@ -import pytest - from flask.wrappers import Request, Response from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict from werkzeug.routing import Map, Rule, Subdomain from werkzeug.test import create_environ -from openapi_core.wrappers.flask import ( - FlaskOpenAPIRequest, FlaskOpenAPIResponse, -) +import pytest from openapi_core.shortcuts import create_spec from openapi_core.validation.response.validators import ResponseValidator +from openapi_core.validation.request.validators import RequestValidator +from openapi_core.wrappers.flask import (FlaskOpenAPIRequest, + FlaskOpenAPIResponse) @pytest.fixture @@ -81,7 +80,8 @@ class TestFlaskOpenAPIRequest(object): assert openapi_request.mimetype == request.mimetype def test_multiple_values(self, request_factory, request): - request = request_factory('GET', '/', subdomain='www', query_string='a=b&a=c') + request = request_factory( + 'GET', '/', subdomain='www', query_string='a=b&a=c') openapi_request = FlaskOpenAPIRequest(request) @@ -140,18 +140,21 @@ class TestFlaskOpenAPIResponse(object): assert openapi_response.mimetype == response.mimetype -class TestFlaskOpenAPIRequestValidation(object): +class TestFlaskOpenAPIValidation(object): specfile = 'data/v3.0/flask_wrapper.yaml' - def test_response_validator_path_pattern( - self, - factory, - request_factory, - response_factory): + def test_response_validator_path_pattern(self, factory, request_factory, response_factory): validator = ResponseValidator(create_spec(factory.spec_from_file(self.specfile))) request = request_factory('GET', '/browse/12/', subdomain='kb') openapi_request = FlaskOpenAPIRequest(request) openapi_response = FlaskOpenAPIResponse(response_factory('Some item', status_code=200)) result = validator.validate(openapi_request, openapi_response) assert not result.errors + + def test_request_validator_path_pattern(self, factory, request_factory): + validator = RequestValidator(create_spec(factory.spec_from_file(self.specfile))) + request = request_factory('GET', '/browse/12/', subdomain='kb') + openapi_request = FlaskOpenAPIRequest(request) + result = validator.validate(openapi_request) + assert not result.errors From 309336430457e6fbab378cd774ead3a172b0cd42 Mon Sep 17 00:00:00 2001 From: Brendan McCollam Date: Wed, 12 Jun 2019 12:14:54 +0100 Subject: [PATCH 5/5] PEP8 --- tests/integration/test_wrappers.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/tests/integration/test_wrappers.py b/tests/integration/test_wrappers.py index d64e721..6c4b7d1 100644 --- a/tests/integration/test_wrappers.py +++ b/tests/integration/test_wrappers.py @@ -142,18 +142,25 @@ class TestFlaskOpenAPIResponse(object): class TestFlaskOpenAPIValidation(object): - specfile = 'data/v3.0/flask_wrapper.yaml' + @pytest.fixture + def flask_spec(self, factory): + specfile = 'data/v3.0/flask_wrapper.yaml' + return create_spec(factory.spec_from_file(specfile)) - def test_response_validator_path_pattern(self, factory, request_factory, response_factory): - validator = ResponseValidator(create_spec(factory.spec_from_file(self.specfile))) + def test_response_validator_path_pattern(self, + flask_spec, + request_factory, + response_factory): + validator = ResponseValidator(flask_spec) request = request_factory('GET', '/browse/12/', subdomain='kb') openapi_request = FlaskOpenAPIRequest(request) - openapi_response = FlaskOpenAPIResponse(response_factory('Some item', status_code=200)) + response = response_factory('Some item', status_code=200) + openapi_response = FlaskOpenAPIResponse(response) result = validator.validate(openapi_request, openapi_response) assert not result.errors - def test_request_validator_path_pattern(self, factory, request_factory): - validator = RequestValidator(create_spec(factory.spec_from_file(self.specfile))) + def test_request_validator_path_pattern(self, flask_spec, request_factory): + validator = RequestValidator(flask_spec) request = request_factory('GET', '/browse/12/', subdomain='kb') openapi_request = FlaskOpenAPIRequest(request) result = validator.validate(openapi_request)