Merge pull request #9 from p1c2u/feature/request-validator

Request validator
This commit is contained in:
A 2017-11-03 14:50:16 +00:00 committed by GitHub
commit 7249858dfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 625 additions and 205 deletions

View file

@ -51,10 +51,31 @@ Now you can use it to validate and unmarshal requests
.. code-block:: python
from openapi_core import request_parameters_factory, request_body_factory
from openapi_core.validators import RequestValidator
parameters = request_parameters_factory.create(request, spec)
body = request_body_factory.create(request, spec)
validator = RequestValidator(spec)
result = validator.validate(request)
# raise errors if request invalid
result.validate()
# get parameters
path_params = result.parameters['path']
query_params = result.parameters['query']
# get body
body = result.body
Request object should implement BaseOpenAPIRequest interface. You can use FlaskOpenAPIRequest a Flask/Werkzeug request wrapper implementation:
.. code-block:: python
from openapi_core.validators import RequestValidator
from openapi_core.wrappers import FlaskOpenAPIRequest
openapi_request = FlaskOpenAPIRequest(flask_request)
validator = RequestValidator(spec)
result = validator.validate(openapi_request)
Related projects
================

View file

@ -1,6 +1,7 @@
"""OpenAPI core module"""
from openapi_core.shortcuts import create_spec
from openapi_core.wrappers import RequestParametersFactory, RequestBodyFactory
from openapi_core.shortcuts import (
create_spec, validate_parameters, validate_body,
)
__author__ = 'Artur Maciąg'
__email__ = 'maciag.artur@gmail.com'
@ -8,7 +9,4 @@ __version__ = '0.2.2'
__url__ = 'https://github.com/p1c2u/openapi-core'
__license__ = 'BSD 3-Clause License'
__all__ = ['create_spec', 'request_parameters_factory', 'request_body_factory']
request_parameters_factory = RequestParametersFactory()
request_body_factory = RequestBodyFactory()
__all__ = ['create_spec', 'validate_parameters', 'validate_body']

View file

@ -9,23 +9,11 @@ class OpenAPIMappingError(OpenAPIError):
pass
class MissingParameterError(OpenAPIMappingError):
class OpenAPIServerError(OpenAPIMappingError):
pass
class MissingPropertyError(OpenAPIMappingError):
pass
class InvalidContentTypeError(OpenAPIMappingError):
pass
class InvalidOperationError(OpenAPIMappingError):
pass
class InvalidServerError(OpenAPIMappingError):
class OpenAPIOperationError(OpenAPIMappingError):
pass
@ -33,13 +21,53 @@ class InvalidValueType(OpenAPIMappingError):
pass
class OpenAPIParameterError(OpenAPIMappingError):
pass
class OpenAPIBodyError(OpenAPIMappingError):
pass
class InvalidServer(OpenAPIServerError):
pass
class InvalidOperation(OpenAPIOperationError):
pass
class EmptyValue(OpenAPIParameterError):
pass
class MissingParameter(OpenAPIParameterError):
pass
class InvalidParameterValue(OpenAPIParameterError):
pass
class MissingBody(OpenAPIBodyError):
pass
class InvalidMediaTypeValue(OpenAPIBodyError):
pass
class UndefinedSchemaProperty(OpenAPIBodyError):
pass
class MissingProperty(OpenAPIBodyError):
pass
class InvalidContentType(OpenAPIBodyError):
pass
class InvalidValue(OpenAPIMappingError):
pass
class EmptyValue(OpenAPIMappingError):
pass
class UndefinedSchemaProperty(OpenAPIMappingError):
pass

View file

@ -1,6 +1,8 @@
"""OpenAPI core mediaTypes module"""
from six import iteritems
from openapi_core.exceptions import InvalidValueType, InvalidMediaTypeValue
class MediaType(object):
"""Represents an OpenAPI MediaType."""
@ -13,7 +15,10 @@ class MediaType(object):
if not self.schema:
return value
return self.schema.unmarshal(value)
try:
return self.schema.unmarshal(value)
except InvalidValueType as exc:
raise InvalidMediaTypeValue(str(exc))
class MediaTypeGenerator(object):

View file

@ -2,7 +2,9 @@
import logging
import warnings
from openapi_core.exceptions import EmptyValue
from openapi_core.exceptions import (
EmptyValue, InvalidValueType, InvalidParameterValue,
)
log = logging.getLogger(__name__)
@ -35,12 +37,15 @@ class Parameter(object):
if (self.location == "query" and value == "" and
not self.allow_empty_value):
raise EmptyValue(
"Value of {0} parameter cannot be empty.".format(self.name))
"Value of {0} parameter cannot be empty".format(self.name))
if not self.schema:
return value
return self.schema.unmarshal(value)
try:
return self.schema.unmarshal(value)
except InvalidValueType as exc:
raise InvalidParameterValue(str(exc))
class ParametersGenerator(object):

View file

@ -1,6 +1,7 @@
"""OpenAPI core requestBodies module"""
from functools import lru_cache
from openapi_core.exceptions import InvalidContentType
from openapi_core.media_types import MediaTypeGenerator
@ -12,7 +13,11 @@ class RequestBody(object):
self.required = required
def __getitem__(self, mimetype):
return self.content[mimetype]
try:
return self.content[mimetype]
except KeyError:
raise InvalidContentType(
"Invalid mime type `{0}`".format(mimetype))
class RequestBodyFactory(object):

View file

@ -10,8 +10,7 @@ from json import loads
from six import iteritems
from openapi_core.exceptions import (
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
InvalidValue,
InvalidValueType, UndefinedSchemaProperty, MissingProperty, InvalidValue,
)
from openapi_core.models import ModelFactory
@ -59,7 +58,8 @@ class Schema(object):
if value is None:
if not self.nullable:
raise InvalidValueType(
"Failed to cast value of %s to %s", value, self.type,
"Failed to cast value of {0} to {1}".format(
value, self.type)
)
return self.default
@ -73,7 +73,7 @@ class Schema(object):
return cast_callable(value)
except ValueError:
raise InvalidValueType(
"Failed to cast value of %s to %s", value, self.type,
"Failed to cast value of {0} to {1}".format(value, self.type)
)
def unmarshal(self, value):
@ -88,7 +88,8 @@ class Schema(object):
if self.enum and casted not in self.enum:
raise InvalidValue(
"Value of %s not in enum choices: %s", value, str(self.enum),
"Value of {0} not in enum choices: {1}".format(
value, self.enum)
)
return casted
@ -115,7 +116,7 @@ class Schema(object):
prop_value = value[prop_name]
except KeyError:
if prop_name in self.required:
raise MissingPropertyError(
raise MissingProperty(
"Missing schema property {0}".format(prop_name))
if not prop.nullable and not prop.default:
continue

View file

@ -3,7 +3,9 @@ from jsonschema.validators import RefResolver
from openapi_spec_validator.validators import Dereferencer
from openapi_spec_validator import default_handlers
from openapi_core.exceptions import OpenAPIParameterError, OpenAPIBodyError
from openapi_core.specs import SpecFactory
from openapi_core.validators import RequestValidator
def create_spec(spec_dict, spec_url=''):
@ -12,3 +14,25 @@ def create_spec(spec_dict, spec_url=''):
dereferencer = Dereferencer(spec_resolver)
spec_factory = SpecFactory(dereferencer)
return spec_factory.create(spec_dict, spec_url=spec_url)
def validate_parameters(spec, request):
validator = RequestValidator(spec)
result = validator.validate(request)
try:
result.validate()
except OpenAPIBodyError:
return result.parameters
else:
return result.parameters
def validate_body(spec, request):
validator = RequestValidator(spec)
result = validator.validate(request)
try:
result.validate()
except OpenAPIParameterError:
return result.body
else:
return result.body

View file

@ -6,7 +6,7 @@ from functools import partialmethod, lru_cache
from openapi_spec_validator import openapi_v3_spec_validator
from openapi_core.components import ComponentsFactory
from openapi_core.exceptions import InvalidOperationError
from openapi_core.exceptions import InvalidOperation, InvalidServer
from openapi_core.infos import InfoFactory
from openapi_core.paths import PathsGenerator
from openapi_core.schemas import SchemaRegistry
@ -32,6 +32,14 @@ class Spec(object):
def default_url(self):
return self.servers[0].default_url
def get_server(self, full_url_pattern):
for spec_server in self.servers:
if spec_server.default_url in full_url_pattern:
return spec_server
raise InvalidServer(
"Invalid request server {0}".format(full_url_pattern))
def get_server_url(self, index=0):
return self.servers[index].default_url
@ -39,7 +47,7 @@ class Spec(object):
try:
return self.paths[path_pattern].operations[http_method]
except KeyError:
raise InvalidOperationError(
raise InvalidOperation(
"Unknown operation path {0} with method {1}".format(
path_pattern, http_method))

122
openapi_core/validators.py Normal file
View file

@ -0,0 +1,122 @@
"""OpenAPI core validators module"""
from six import iteritems
from openapi_core.exceptions import (
OpenAPIMappingError, MissingParameter, MissingBody,
)
class RequestParameters(dict):
valid_locations = ['path', 'query', 'headers', 'cookies']
def __getitem__(self, location):
self.validate_location(location)
return self.setdefault(location, {})
def __setitem__(self, location, value):
raise NotImplementedError
@classmethod
def validate_location(cls, location):
if location not in cls.valid_locations:
raise OpenAPIMappingError(
"Unknown parameter location: {0}".format(str(location)))
class BaseValidationResult(object):
def __init__(self, errors):
self.errors = errors
def validate(self):
for error in self.errors:
raise error
class RequestValidationResult(BaseValidationResult):
def __init__(self, errors, body=None, parameters=None):
super(RequestValidationResult, self).__init__(errors)
self.body = body
self.parameters = parameters or RequestParameters()
class RequestValidator(object):
def __init__(self, spec):
self.spec = spec
def validate(self, request):
errors = []
body = None
parameters = RequestParameters()
try:
server = self.spec.get_server(request.full_url_pattern)
# don't process if server errors
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
operation_pattern = request.full_url_pattern.replace(
server.default_url, '')
try:
operation = self.spec.get_operation(
operation_pattern, request.method)
# don't process if operation errors
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
for param_name, param in iteritems(operation.parameters):
try:
raw_value = self._get_raw_value(request, param)
except MissingParameter as exc:
if param.required:
errors.append(exc)
if not param.schema or param.schema.default is None:
continue
raw_value = param.schema.default
try:
value = param.unmarshal(raw_value)
except OpenAPIMappingError as exc:
errors.append(exc)
else:
parameters[param.location][param_name] = value
if operation.request_body is not None:
try:
media_type = operation.request_body[request.mimetype]
except OpenAPIMappingError as exc:
errors.append(exc)
else:
try:
raw_body = self._get_raw_body(request)
except MissingBody as exc:
if operation.request_body.required:
errors.append(exc)
else:
try:
body = media_type.unmarshal(raw_body)
except OpenAPIMappingError as exc:
errors.append(exc)
return RequestValidationResult(errors, body, parameters)
def _get_raw_value(self, request, param):
try:
return request.parameters[param.location][param.name]
except KeyError:
raise MissingParameter(
"Missing required `{0}` parameter".format(param.name))
def _get_raw_body(self, request):
if not request.body:
raise MissingBody("Missing required request body")
return request.body

View file

@ -1,110 +1,9 @@
"""OpenAPI core wrappers module"""
from six import iteritems
import warnings
from six.moves.urllib.parse import urljoin
from openapi_core.exceptions import (
OpenAPIMappingError, MissingParameterError, InvalidContentTypeError,
InvalidServerError,
)
SPEC_LOCATION_TO_REQUEST_LOCATION_MAPPING = {
'path': 'view_args',
'query': 'args',
'headers': 'headers',
'cookies': 'cookies',
}
class RequestParameters(dict):
valid_locations = ['path', 'query', 'headers', 'cookies']
def __getitem__(self, location):
self.validate_location(location)
return self.setdefault(location, {})
def __setitem__(self, location, value):
raise NotImplementedError
@classmethod
def validate_location(cls, location):
if location not in cls.valid_locations:
raise OpenAPIMappingError(
"Unknown parameter location: {0}".format(str(location)))
class BaseRequestFactory(object):
def get_operation(self, request, spec):
server = self._get_server(request, spec)
operation_pattern = request.full_url_pattern.replace(
server.default_url, '')
method = request.method.lower()
return spec.get_operation(operation_pattern, method)
def _get_server(self, request, spec):
for server in spec.servers:
if server.default_url in request.full_url_pattern:
return server
raise InvalidServerError(
"Invalid request server {0}".format(request.full_url_pattern))
class RequestParametersFactory(BaseRequestFactory):
def __init__(self, attr_mapping=SPEC_LOCATION_TO_REQUEST_LOCATION_MAPPING):
self.attr_mapping = attr_mapping
def create(self, request, spec):
operation = self.get_operation(request, spec)
params = RequestParameters()
for param_name, param in iteritems(operation.parameters):
try:
raw_value = self._get_raw_value(request, param)
except MissingParameterError:
if param.required:
raise
if not param.schema or param.schema.default is None:
continue
raw_value = param.schema.default
value = param.unmarshal(raw_value)
params[param.location][param_name] = value
return params
def _get_raw_value(self, request, param):
request_location = self.attr_mapping[param.location]
request_attr = getattr(request, request_location)
try:
return request_attr[param.name]
except KeyError:
raise MissingParameterError(
"Missing required `{0}` parameter".format(param.name))
class RequestBodyFactory(BaseRequestFactory):
def create(self, request, spec):
operation = self.get_operation(request, spec)
if operation.request_body is None:
return None
try:
media_type = operation.request_body[request.mimetype]
except KeyError:
raise InvalidContentTypeError(
"Invalid media type `{0}`".format(request.mimetype))
return media_type.unmarshal(request.data)
from openapi_core.shortcuts import validate_parameters, validate_body
class BaseOpenAPIRequest(object):
@ -114,12 +13,8 @@ class BaseOpenAPIRequest(object):
path_pattern = NotImplemented
method = NotImplemented
args = NotImplemented
view_args = NotImplemented
headers = NotImplemented
cookies = NotImplemented
data = NotImplemented
parameters = NotImplemented
body = NotImplemented
mimetype = NotImplemented
@ -127,8 +22,85 @@ class BaseOpenAPIRequest(object):
def full_url_pattern(self):
return urljoin(self.host_url, self.path_pattern)
def get_parameters(self, spec):
return RequestParametersFactory().create(self, spec)
def get_body(self, spec):
return RequestBodyFactory().create(self, spec)
warnings.warn(
"`get_body` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
return validate_body(spec, self)
def get_parameters(self, spec):
warnings.warn(
"`get_parameters` method is deprecated. "
"Use RequestValidator instead.",
DeprecationWarning,
)
# backward compatibility
return validate_parameters(spec, self)
class MockRequest(BaseOpenAPIRequest):
def __init__(
self, host_url, method, path, path_pattern=None, args=None,
view_args=None, headers=None, cookies=None, data=None,
mimetype='application/json'):
self.host_url = host_url
self.path = path
self.path_pattern = path_pattern or path
self.method = method.lower()
self.parameters = {
'path': view_args or {},
'query': args or {},
'headers': headers or {},
'cookies': cookies or {},
}
self.body = data or ''
self.mimetype = mimetype
class FlaskOpenAPIRequest(BaseOpenAPIRequest):
def __init__(self, request):
self.request = request
@property
def host_url(self):
return self.request.host_url
@property
def path(self):
return self.request.path
@property
def method(self):
return self.request.method.lower()
@property
def path_pattern(self):
if self.request.url_rule is None:
return self.path
return self.request.url_rule.rule
@property
def parameters(self):
return {
'path': self.request.view_args,
'query': self.request.args,
'headers': self.request.headers,
'cookies': self.request.cookies,
}
@property
def body(self):
return self.request.data
@property
def mimetype(self):
return self.request.mimetype

View file

@ -3,3 +3,4 @@ pytest
pytest-pep8
pytest-flakes
pytest-cov
flask

View file

@ -73,6 +73,7 @@ paths:
tags:
- pets
requestBody:
required: true
content:
application/json:
schema:

View file

@ -3,9 +3,9 @@ import pytest
from six import iteritems
from openapi_core.exceptions import (
MissingParameterError, InvalidContentTypeError, InvalidServerError,
InvalidValueType, UndefinedSchemaProperty, MissingPropertyError,
EmptyValue,
MissingParameter, InvalidContentType, InvalidServer,
UndefinedSchemaProperty, MissingProperty,
EmptyValue, InvalidMediaTypeValue, InvalidParameterValue,
)
from openapi_core.media_types import MediaType
from openapi_core.operations import Operation
@ -14,27 +14,7 @@ from openapi_core.request_bodies import RequestBody
from openapi_core.schemas import Schema
from openapi_core.servers import Server, ServerVariable
from openapi_core.shortcuts import create_spec
from openapi_core.wrappers import BaseOpenAPIRequest
class RequestMock(BaseOpenAPIRequest):
def __init__(
self, host_url, method, path, path_pattern=None, args=None,
view_args=None, headers=None, cookies=None, data=None,
mimetype='application/json'):
self.host_url = host_url
self.path = path
self.path_pattern = path_pattern or path
self.method = method
self.args = args or {}
self.view_args = view_args or {}
self.headers = headers or {}
self.cookies = cookies or {}
self.data = data or ''
self.mimetype = mimetype
from openapi_core.wrappers import MockRequest
class TestPetstore(object):
@ -125,7 +105,7 @@ class TestPetstore(object):
'ids': ['12', '13'],
}
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
@ -150,12 +130,12 @@ class TestPetstore(object):
'limit': 'twenty',
}
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidParameterValue):
request.get_parameters(spec)
body = request.get_body(spec)
@ -165,12 +145,12 @@ class TestPetstore(object):
def test_get_pets_raises_missing_required_param(self, spec):
host_url = 'http://petstore.swagger.io/v1'
path_pattern = '/v1/pets'
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern,
)
with pytest.raises(MissingParameterError):
with pytest.raises(MissingParameter):
request.get_parameters(spec)
body = request.get_body(spec)
@ -184,7 +164,7 @@ class TestPetstore(object):
'limit': '',
}
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
@ -202,7 +182,7 @@ class TestPetstore(object):
'limit': None,
}
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets',
path_pattern=path_pattern, args=query_params,
)
@ -239,7 +219,7 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
@ -267,7 +247,7 @@ class TestPetstore(object):
data_json = {}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
@ -276,7 +256,7 @@ class TestPetstore(object):
assert parameters == {}
with pytest.raises(MissingPropertyError):
with pytest.raises(MissingProperty):
request.get_body(spec)
def test_post_pets_extra_body_properties(self, spec, spec_dict):
@ -290,7 +270,7 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
@ -311,7 +291,7 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
@ -342,7 +322,7 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data,
)
@ -351,7 +331,7 @@ class TestPetstore(object):
assert parameters == {}
with pytest.raises(InvalidValueType):
with pytest.raises(InvalidMediaTypeValue):
request.get_body(spec)
def test_post_pets_raises_invalid_mimetype(self, spec):
@ -363,7 +343,7 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data, mimetype='text/html',
)
@ -372,7 +352,7 @@ class TestPetstore(object):
assert parameters == {}
with pytest.raises(InvalidContentTypeError):
with pytest.raises(InvalidContentType):
request.get_body(spec)
def test_post_pets_raises_invalid_server_error(self, spec):
@ -384,15 +364,15 @@ class TestPetstore(object):
}
data = json.dumps(data_json)
request = RequestMock(
request = MockRequest(
host_url, 'POST', '/pets',
path_pattern=path_pattern, data=data, mimetype='text/html',
)
with pytest.raises(InvalidServerError):
with pytest.raises(InvalidServer):
request.get_parameters(spec)
with pytest.raises(InvalidServerError):
with pytest.raises(InvalidServer):
request.get_body(spec)
def test_get_pet(self, spec):
@ -401,7 +381,7 @@ class TestPetstore(object):
view_args = {
'petId': '1',
}
request = RequestMock(
request = MockRequest(
host_url, 'GET', '/pets/1',
path_pattern=path_pattern, view_args=view_args,
)

View file

@ -0,0 +1,157 @@
import json
import pytest
from openapi_core.exceptions import (
InvalidServer, InvalidOperation, MissingParameter,
MissingBody, InvalidContentType,
)
from openapi_core.shortcuts import create_spec
from openapi_core.validators import RequestValidator
from openapi_core.wrappers import MockRequest
class TestRequestValidator(object):
host_url = 'http://petstore.swagger.io'
@pytest.fixture
def spec_dict(self, factory):
return factory.spec_from_file("data/v3.0/petstore.yaml")
@pytest.fixture
def spec(self, spec_dict):
return create_spec(spec_dict)
@pytest.fixture
def validator(self, spec):
return RequestValidator(spec)
def test_request_server_error(self, validator):
request = MockRequest('http://petstore.invalid.net/v1', 'get', '/')
result = validator.validate(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidServer
assert result.body is None
assert result.parameters == {}
def test_invalid_operation(self, validator):
request = MockRequest(self.host_url, 'get', '/v1')
result = validator.validate(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidOperation
assert result.body is None
assert result.parameters == {}
def test_missing_parameter(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
result = validator.validate(request)
assert type(result.errors[0]) == MissingParameter
assert result.body is None
assert result.parameters == {
'query': {
'page': 1,
'search': '',
},
}
def test_get_pets(self, validator):
request = MockRequest(
self.host_url, 'get', '/v1/pets',
path_pattern='/v1/pets', args={'limit': '10'},
)
result = validator.validate(request)
assert result.errors == []
assert result.body is None
assert result.parameters == {
'query': {
'limit': 10,
'page': 1,
'search': '',
},
}
def test_missing_body(self, validator):
request = MockRequest(
self.host_url, 'post', '/v1/pets',
path_pattern='/v1/pets',
)
result = validator.validate(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == MissingBody
assert result.body is None
assert result.parameters == {}
def test_invalid_content_type(self, validator):
request = MockRequest(
self.host_url, 'post', '/v1/pets',
path_pattern='/v1/pets', mimetype='text/csv',
)
result = validator.validate(request)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidContentType
assert result.body is None
assert result.parameters == {}
def test_post_pets(self, validator, spec_dict):
pet_name = 'Cat'
pet_tag = 'cats'
pet_street = 'Piekna'
pet_city = 'Warsaw'
data_json = {
'name': pet_name,
'tag': pet_tag,
'position': '2',
'address': {
'street': pet_street,
'city': pet_city,
}
}
data = json.dumps(data_json)
request = MockRequest(
self.host_url, 'post', '/v1/pets',
path_pattern='/v1/pets', data=data,
)
result = validator.validate(request)
assert result.errors == []
assert result.parameters == {}
schemas = spec_dict['components']['schemas']
pet_model = schemas['PetCreate']['x-model']
address_model = schemas['Address']['x-model']
assert result.body.__class__.__name__ == pet_model
assert result.body.name == pet_name
assert result.body.tag == pet_tag
assert result.body.position == 2
assert result.body.address.__class__.__name__ == address_model
assert result.body.address.street == pet_street
assert result.body.address.city == pet_city
def test_get_pet(self, validator):
request = MockRequest(
self.host_url, 'get', '/v1/pets/1',
path_pattern='/v1/pets/{petId}', view_args={'petId': '1'},
)
result = validator.validate(request)
assert result.errors == []
assert result.body is None
assert result.parameters == {
'path': {
'petId': 1,
},
}

View file

@ -0,0 +1,92 @@
import pytest
from flask.wrappers import Request
from werkzeug.datastructures import EnvironHeaders, ImmutableMultiDict
from werkzeug.routing import Map, Rule, Subdomain
from werkzeug.test import create_environ
from openapi_core.wrappers import FlaskOpenAPIRequest
class TestFlaskOpenAPIRequest(object):
server_name = 'localhost'
@pytest.fixture
def environ(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/<int:id>/', endpoint='kb/browse'),
Rule('/browse/<int:id>/<int:page>', endpoint='kb/browse')
])
], default_subdomain='www')
@pytest.fixture
def request_factory(self, map, environ):
def create_request(method, path, subdomain=None):
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, environ, request):
request = request_factory('GET', '/', subdomain='www')
openapi_request = FlaskOpenAPIRequest(request)
path = {}
query = ImmutableMultiDict([])
headers = EnvironHeaders(environ)
cookies = {}
assert openapi_request.parameters == {
'path': path,
'query': query,
'headers': headers,
'cookies': cookies,
}
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.path
assert openapi_request.body == request.data
assert openapi_request.mimetype == request.mimetype
def test_url_rule(self, request_factory, environ, request):
request = request_factory('GET', '/browse/12/', subdomain='kb')
openapi_request = FlaskOpenAPIRequest(request)
path = {'id': 12}
query = ImmutableMultiDict([])
headers = EnvironHeaders(environ)
cookies = {}
assert openapi_request.parameters == {
'path': path,
'query': query,
'headers': headers,
'cookies': cookies,
}
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.body == request.data
assert openapi_request.mimetype == request.mimetype

View file

@ -1,7 +1,7 @@
import mock
import pytest
from openapi_core.exceptions import InvalidOperationError
from openapi_core.exceptions import InvalidOperation
from openapi_core.paths import Path
from openapi_core.specs import Spec
@ -42,9 +42,9 @@ class TestSpecs(object):
assert operation == mock.sentinel.path1_get
def test_invalid_path(self, spec):
with pytest.raises(InvalidOperationError):
with pytest.raises(InvalidOperation):
spec.get_operation('/path3', 'get')
def test_invalid_method(self, spec):
with pytest.raises(InvalidOperationError):
with pytest.raises(InvalidOperation):
spec.get_operation('/path1', 'post')