Merge pull request #309 from p1c2u/feature/response-finder

Response finder
This commit is contained in:
A 2021-03-31 16:25:18 +01:00 committed by GitHub
commit 0865a4f54f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 88 additions and 66 deletions

View file

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
"""OpenAPI core operations models module"""
from openapi_core.schema.responses.exceptions import InvalidResponse
class Operation(object):
@ -29,18 +28,3 @@ class Operation(object):
def __getitem__(self, name):
return self.parameters[name]
def get_response(self, http_status='default'):
# @todo: move to Responses object
try:
return self.responses[http_status]
except KeyError:
# try range
http_status_range = '{0}XX'.format(http_status[0])
if http_status_range in self.responses:
return self.responses[http_status_range]
if 'default' not in self.responses:
raise InvalidResponse(http_status, self.responses)
return self.responses['default']

View file

@ -7,16 +7,6 @@ class OpenAPIResponseError(OpenAPIMappingError):
pass
@attr.s(hash=True)
class InvalidResponse(OpenAPIResponseError):
http_status = attr.ib()
responses = attr.ib()
def __str__(self):
return "Unknown response http status: {0}".format(
str(self.http_status))
@attr.s(hash=True)
class MissingResponseContent(OpenAPIResponseError):
response = attr.ib()

View file

@ -0,0 +1,18 @@
import attr
from openapi_core.exceptions import OpenAPIError
class ResponseFinderError(OpenAPIError):
"""Response finder error"""
@attr.s(hash=True)
class ResponseNotFound(ResponseFinderError):
"""Find response error"""
http_status = attr.ib()
responses = attr.ib()
def __str__(self):
return "Unknown response http status: {0}".format(
str(self.http_status))

View file

@ -0,0 +1,23 @@
from openapi_core.templating.responses.exceptions import ResponseNotFound
class ResponseFinder(object):
def __init__(self, responses):
self.responses = responses
def find(self, http_status='default'):
try:
return self.responses[http_status]
except KeyError:
pass
# try range
http_status_range = '{0}XX'.format(http_status[0])
if http_status_range in self.responses:
return self.responses[http_status_range]
if 'default' not in self.responses:
raise ResponseNotFound(http_status, self.responses)
return self.responses['default']

View file

@ -1,11 +1,10 @@
"""OpenAPI core validation response validators module"""
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.schema.responses.exceptions import (
InvalidResponse, MissingResponseContent,
)
from openapi_core.schema.responses.exceptions import MissingResponseContent
from openapi_core.templating.media_types.exceptions import MediaTypeFinderError
from openapi_core.templating.paths.exceptions import PathError
from openapi_core.templating.responses.exceptions import ResponseFinderError
from openapi_core.unmarshalling.schemas.enums import UnmarshalContext
from openapi_core.unmarshalling.schemas.exceptions import (
UnmarshalError, ValidateError,
@ -27,7 +26,7 @@ class ResponseValidator(BaseValidator):
operation_response = self._get_operation_response(
operation, response)
# don't process if operation errors
except InvalidResponse as exc:
except ResponseFinderError as exc:
return ResponseValidationResult(errors=[exc, ])
data, data_errors = self._get_data(response, operation_response)
@ -43,7 +42,9 @@ class ResponseValidator(BaseValidator):
)
def _get_operation_response(self, operation, response):
return operation.get_response(str(response.status_code))
from openapi_core.templating.responses.finders import ResponseFinder
finder = ResponseFinder(operation.responses)
return finder.find(str(response.status_code))
def _validate_data(self, request, response):
try:
@ -56,7 +57,7 @@ class ResponseValidator(BaseValidator):
operation_response = self._get_operation_response(
operation, response)
# don't process if operation errors
except InvalidResponse as exc:
except ResponseFinderError as exc:
return ResponseValidationResult(errors=[exc, ])
data, data_errors = self._get_data(response, operation_response)

View file

@ -6,7 +6,7 @@ class TestLinkSpec(object):
def test_no_param(self, factory):
spec_dict = factory.spec_from_file("data/v3.0/links.yaml")
spec = create_spec(spec_dict)
resp = spec['/status']['get'].get_response()
resp = spec['/status']['get'].responses['default']
assert len(resp.links) == 1
@ -20,7 +20,7 @@ class TestLinkSpec(object):
def test_param(self, factory):
spec_dict = factory.spec_from_file("data/v3.0/links.yaml")
spec = create_spec(spec_dict)
resp = spec['/status/{resourceId}']['get'].get_response()
resp = spec['/status/{resourceId}']['get'].responses['default']
assert len(resp.links) == 1

View file

@ -8,14 +8,13 @@ from openapi_core.deserializing.exceptions import DeserializeError
from openapi_core.extensions.models.models import BaseModel
from openapi_core.schema.parameters.exceptions import MissingRequiredParameter
from openapi_core.schema.request_bodies.exceptions import MissingRequestBody
from openapi_core.schema.responses.exceptions import (
MissingResponseContent, InvalidResponse,
)
from openapi_core.schema.responses.exceptions import MissingResponseContent
from openapi_core.shortcuts import create_spec
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
from openapi_core.templating.paths.exceptions import (
PathNotFound, OperationNotFound,
)
from openapi_core.templating.responses.exceptions import ResponseNotFound
from openapi_core.testing import MockRequest, MockResponse
from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue
from openapi_core.validation.exceptions import InvalidSecurity
@ -450,7 +449,7 @@ class TestResponseValidator(object):
result = validator.validate(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidResponse
assert type(result.errors[0]) == ResponseNotFound
assert result.data is None
assert result.headers == {}

View file

@ -17,31 +17,3 @@ class TestSchemas(object):
def test_iteritems(self, operation):
for name in operation.parameters:
assert operation[name] == operation.parameters[name]
class TestResponses(object):
@pytest.fixture
def operation(self):
responses = {
'200': mock.sentinel.response_200,
'299': mock.sentinel.response_299,
'2XX': mock.sentinel.response_2XX,
'default': mock.sentinel.response_default,
}
return Operation('get', '/path', responses, parameters={})
def test_default(self, operation):
response = operation.get_response()
assert response == operation.responses['default']
def test_range(self, operation):
response = operation.get_response('201')
assert response == operation.responses['2XX']
def test_exact(self, operation):
response = operation.get_response('200')
assert response == operation.responses['200']

View file

@ -0,0 +1,35 @@
import mock
import pytest
from openapi_core.templating.responses.finders import ResponseFinder
class TestResponses(object):
@pytest.fixture(scope='class')
def responses(self):
return {
'200': mock.sentinel.response_200,
'299': mock.sentinel.response_299,
'2XX': mock.sentinel.response_2XX,
'default': mock.sentinel.response_default,
}
@pytest.fixture(scope='class')
def finder(self, responses):
return ResponseFinder(responses)
def test_default(self, finder, responses):
response = finder.find()
assert response == responses['default']
def test_range(self, finder, responses):
response = finder.find('201')
assert response == responses['2XX']
def test_exact(self, finder, responses):
response = finder.find('200')
assert response == responses['200']