openapi-core/tests/integration/validation/test_validators.py

545 lines
17 KiB
Python
Raw Normal View History

2019-02-26 16:49:25 +00:00
from base64 import b64encode
2017-11-02 16:05:25 +00:00
import json
import pytest
2019-03-07 21:55:27 +00:00
from six import text_type
2017-11-02 16:05:25 +00:00
2020-02-03 10:59:27 +00:00
from openapi_core.casting.schemas.exceptions import CastError
from openapi_core.deserializing.exceptions import DeserializeError
2018-08-21 17:33:24 +00:00
from openapi_core.extensions.models.models import BaseModel
2018-04-18 10:39:03 +00:00
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,
)
2017-11-02 16:05:25 +00:00
from openapi_core.shortcuts import create_spec
2021-03-30 12:09:39 +00:00
from openapi_core.templating.media_types.exceptions import MediaTypeNotFound
2020-02-21 16:33:45 +00:00
from openapi_core.templating.paths.exceptions import (
PathNotFound, OperationNotFound,
)
2019-10-19 12:01:56 +00:00
from openapi_core.testing import MockRequest, MockResponse
2020-02-02 22:51:02 +00:00
from openapi_core.unmarshalling.schemas.exceptions import InvalidSchemaValue
from openapi_core.validation.exceptions import InvalidSecurity
from openapi_core.validation.request.datatypes import RequestParameters
2018-04-17 12:38:23 +00:00
from openapi_core.validation.request.validators import RequestValidator
from openapi_core.validation.response.validators import ResponseValidator
2017-11-02 16:05:25 +00:00
class TestRequestValidator(object):
host_url = 'http://petstore.swagger.io'
2019-03-07 21:55:27 +00:00
api_key = '12345'
2019-02-26 16:49:25 +00:00
@property
def api_key_encoded(self):
2019-03-07 21:55:27 +00:00
api_key_bytes = self.api_key.encode('utf8')
api_key_bytes_enc = b64encode(api_key_bytes)
return text_type(api_key_bytes_enc, 'utf8')
2019-02-26 16:49:25 +00:00
@pytest.fixture(scope='session')
2017-11-02 16:05:25 +00:00
def spec_dict(self, factory):
return factory.spec_from_file("data/v3.0/petstore.yaml")
@pytest.fixture(scope='session')
2017-11-02 16:05:25 +00:00
def spec(self, spec_dict):
return create_spec(spec_dict)
@pytest.fixture(scope='session')
2017-11-02 16:05:25 +00:00
def validator(self, spec):
2020-02-21 16:33:45 +00:00
return RequestValidator(spec, base_url=self.host_url)
2017-11-02 16:05:25 +00:00
def test_request_server_error(self, validator):
2017-11-03 11:18:48 +00:00
request = MockRequest('http://petstore.invalid.net/v1', 'get', '/')
2017-11-02 16:05:25 +00:00
result = validator.validate(request)
assert len(result.errors) == 1
2020-02-21 16:33:45 +00:00
assert type(result.errors[0]) == PathNotFound
2017-11-02 16:05:25 +00:00
assert result.body is None
2020-03-02 16:05:36 +00:00
assert result.parameters == RequestParameters()
2017-11-02 16:05:25 +00:00
2019-06-18 15:13:44 +00:00
def test_invalid_path(self, validator):
2017-11-03 11:18:48 +00:00
request = MockRequest(self.host_url, 'get', '/v1')
2017-11-02 16:05:25 +00:00
result = validator.validate(request)
2019-06-18 15:13:44 +00:00
assert len(result.errors) == 1
2020-02-21 16:33:45 +00:00
assert type(result.errors[0]) == PathNotFound
2019-06-18 15:13:44 +00:00
assert result.body is None
2020-03-02 16:05:36 +00:00
assert result.parameters == RequestParameters()
2019-06-18 15:13:44 +00:00
def test_invalid_operation(self, validator):
request = MockRequest(self.host_url, 'patch', '/v1/pets')
result = validator.validate(request)
2017-11-02 16:05:25 +00:00
assert len(result.errors) == 1
2020-02-21 16:33:45 +00:00
assert type(result.errors[0]) == OperationNotFound
2017-11-02 16:05:25 +00:00
assert result.body is None
2020-03-02 16:05:36 +00:00
assert result.parameters == RequestParameters()
2017-11-02 16:05:25 +00:00
def test_missing_parameter(self, validator):
2017-11-03 11:18:48 +00:00
request = MockRequest(self.host_url, 'get', '/v1/pets')
2017-11-02 16:05:25 +00:00
result = validator.validate(request)
2018-04-18 10:39:03 +00:00
assert type(result.errors[0]) == MissingRequiredParameter
2017-11-02 16:05:25 +00:00
assert result.body is None
assert result.parameters == RequestParameters(
query={
2017-11-02 16:05:25 +00:00
'page': 1,
'search': '',
},
)
2017-11-02 16:05:25 +00:00
def test_get_pets(self, validator):
args = {'limit': '10', 'ids': ['1', '2'], 'api_key': self.api_key}
2017-11-03 11:18:48 +00:00
request = MockRequest(
2017-11-02 16:05:25 +00:00
self.host_url, 'get', '/v1/pets',
path_pattern='/v1/pets', args=args,
2017-11-02 16:05:25 +00:00
)
result = validator.validate(request)
assert result.errors == []
assert result.body is None
assert result.parameters == RequestParameters(
query={
2017-11-02 16:05:25 +00:00
'limit': 10,
'page': 1,
'search': '',
'ids': [1, 2],
},
)
assert result.security == {
'api_key': self.api_key,
}
def test_get_pets_webob(self, validator):
from webob.multidict import GetDict
request = MockRequest(
self.host_url, 'get', '/v1/pets',
path_pattern='/v1/pets',
)
request.parameters.query = GetDict(
[('limit', '5'), ('ids', '1'), ('ids', '2')],
{}
)
result = validator.validate(request)
assert result.errors == []
assert result.body is None
assert result.parameters == RequestParameters(
query={
'limit': 5,
'page': 1,
'search': '',
'ids': [1, 2],
2017-11-02 16:05:25 +00:00
},
)
2017-11-02 16:05:25 +00:00
def test_missing_body(self, validator):
2018-07-09 11:10:05 +00:00
headers = {
2019-02-26 16:49:25 +00:00
'api_key': self.api_key_encoded,
2018-07-09 11:10:05 +00:00
}
cookies = {
'user': '123',
}
2017-11-03 11:18:48 +00:00
request = MockRequest(
2020-02-21 16:33:45 +00:00
'https://development.gigantic-server.com', 'post', '/v1/pets',
2017-11-02 16:05:25 +00:00
path_pattern='/v1/pets',
2018-07-09 11:10:05 +00:00
headers=headers, cookies=cookies,
2017-11-02 16:05:25 +00:00
)
result = validator.validate(request)
assert len(result.errors) == 1
2018-04-18 10:39:03 +00:00
assert type(result.errors[0]) == MissingRequestBody
2017-11-02 16:05:25 +00:00
assert result.body is None
assert result.parameters == RequestParameters(
header={
2019-02-26 16:49:25 +00:00
'api_key': self.api_key,
2018-07-09 11:10:05 +00:00
},
cookie={
2018-07-09 11:10:05 +00:00
'user': 123,
},
)
2017-11-02 16:05:25 +00:00
def test_invalid_content_type(self, validator):
2018-07-09 11:10:05 +00:00
headers = {
2019-02-26 16:49:25 +00:00
'api_key': self.api_key_encoded,
2018-07-09 11:10:05 +00:00
}
cookies = {
'user': '123',
}
2017-11-03 11:18:48 +00:00
request = MockRequest(
2020-02-21 16:33:45 +00:00
'https://development.gigantic-server.com', 'post', '/v1/pets',
2017-11-02 16:05:25 +00:00
path_pattern='/v1/pets', mimetype='text/csv',
2018-07-09 11:10:05 +00:00
headers=headers, cookies=cookies,
2017-11-02 16:05:25 +00:00
)
result = validator.validate(request)
assert len(result.errors) == 1
2021-03-30 12:09:39 +00:00
assert type(result.errors[0]) == MediaTypeNotFound
2017-11-02 16:05:25 +00:00
assert result.body is None
assert result.parameters == RequestParameters(
header={
2019-02-26 16:49:25 +00:00
'api_key': self.api_key,
2018-07-09 11:10:05 +00:00
},
cookie={
2018-07-09 11:10:05 +00:00
'user': 123,
},
)
2017-11-02 16:05:25 +00:00
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,
2019-03-22 01:51:47 +00:00
'position': 2,
2017-11-02 16:05:25 +00:00
'address': {
'street': pet_street,
'city': pet_city,
2018-05-25 15:32:09 +00:00
},
'ears': {
'healthy': True,
2017-11-02 16:05:25 +00:00
}
}
data = json.dumps(data_json)
2018-07-09 11:10:05 +00:00
headers = {
2019-02-26 16:49:25 +00:00
'api_key': self.api_key_encoded,
2018-07-09 11:10:05 +00:00
}
cookies = {
'user': '123',
}
2017-11-03 11:18:48 +00:00
request = MockRequest(
2020-02-21 16:33:45 +00:00
'https://development.gigantic-server.com', 'post', '/v1/pets',
2017-11-02 16:05:25 +00:00
path_pattern='/v1/pets', data=data,
2018-07-09 11:10:05 +00:00
headers=headers, cookies=cookies,
2017-11-02 16:05:25 +00:00
)
result = validator.validate(request)
assert result.errors == []
assert result.parameters == RequestParameters(
header={
2019-02-26 16:49:25 +00:00
'api_key': self.api_key,
2018-07-09 11:10:05 +00:00
},
cookie={
2018-07-09 11:10:05 +00:00
'user': 123,
},
)
2020-02-04 11:06:26 +00:00
assert result.security == {}
2017-11-02 16:05:25 +00:00
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_unauthorized(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 == [InvalidSecurity(), ]
assert result.body is None
2020-03-02 16:05:36 +00:00
assert result.parameters == RequestParameters()
assert result.security is None
2017-11-02 16:05:25 +00:00
def test_get_pet(self, validator):
2020-02-04 11:06:26 +00:00
authorization = 'Basic ' + self.api_key_encoded
headers = {
'Authorization': authorization,
}
2017-11-03 11:18:48 +00:00
request = MockRequest(
2017-11-02 16:05:25 +00:00
self.host_url, 'get', '/v1/pets/1',
path_pattern='/v1/pets/{petId}', view_args={'petId': '1'},
2020-02-04 11:06:26 +00:00
headers=headers,
2017-11-02 16:05:25 +00:00
)
result = validator.validate(request)
assert result.errors == []
assert result.body is None
assert result.parameters == RequestParameters(
path={
2017-11-02 16:05:25 +00:00
'petId': 1,
},
)
2020-02-04 11:06:26 +00:00
assert result.security == {
2020-03-23 13:20:08 +00:00
'petstore_auth': self.api_key_encoded,
2020-02-04 11:06:26 +00:00
}
2017-11-06 13:32:31 +00:00
class TestPathItemParamsValidator(object):
@pytest.fixture(scope='session')
def spec_dict(self):
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(scope='session')
def spec(self, spec_dict):
return create_spec(spec_dict)
@pytest.fixture(scope='session')
def validator(self, spec):
2020-02-21 16:33:45 +00:00
return RequestValidator(spec, base_url='http://example.com')
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 == RequestParameters()
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
2020-02-03 10:59:27 +00:00
assert type(result.errors[0]) == CastError
assert result.body is None
assert result.parameters == RequestParameters()
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 == RequestParameters(query={'resId': 10})
def test_request_override_param(self, spec_dict):
# override path parameter on operation
spec_dict["paths"]["/resource"]["get"]["parameters"] = [
{
# full valid parameter object required
"name": "resId",
"in": "query",
"required": False,
"schema": {
"type": "integer",
},
}
]
2020-02-21 16:33:45 +00:00
validator = RequestValidator(
create_spec(spec_dict), base_url='http://example.com')
request = MockRequest('http://example.com', 'get', '/resource')
result = validator.validate(request)
assert len(result.errors) == 0
assert result.body is None
assert result.parameters == RequestParameters()
def test_request_override_param_uniqueness(self, spec_dict):
# add parameter on operation with same name as on path but
# different location
spec_dict["paths"]["/resource"]["get"]["parameters"] = [
{
# full valid parameter object required
"name": "resId",
"in": "header",
"required": False,
"schema": {
"type": "integer",
},
}
]
2020-02-21 16:33:45 +00:00
validator = RequestValidator(
create_spec(spec_dict), base_url='http://example.com')
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 == RequestParameters()
2017-11-06 13:32:31 +00:00
class TestResponseValidator(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):
2020-02-21 16:33:45 +00:00
return ResponseValidator(spec, base_url=self.host_url)
2017-11-06 13:32:31 +00:00
def test_invalid_server(self, validator):
request = MockRequest('http://petstore.invalid.net/v1', 'get', '/')
2017-11-06 15:08:21 +00:00
response = MockResponse('Not Found', status_code=404)
2017-11-06 13:32:31 +00:00
result = validator.validate(request, response)
assert len(result.errors) == 1
2020-02-21 16:33:45 +00:00
assert type(result.errors[0]) == PathNotFound
2017-11-06 14:05:06 +00:00
assert result.data is None
2020-03-02 16:05:36 +00:00
assert result.headers == {}
2017-11-06 13:32:31 +00:00
def test_invalid_operation(self, validator):
2020-02-21 16:33:45 +00:00
request = MockRequest(self.host_url, 'patch', '/v1/pets')
2017-11-06 15:08:21 +00:00
response = MockResponse('Not Found', status_code=404)
2017-11-06 13:32:31 +00:00
result = validator.validate(request, response)
assert len(result.errors) == 1
2020-02-21 16:33:45 +00:00
assert type(result.errors[0]) == OperationNotFound
2017-11-06 14:05:06 +00:00
assert result.data is None
2020-03-02 16:05:36 +00:00
assert result.headers == {}
2017-11-06 13:32:31 +00:00
def test_invalid_response(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
2017-11-06 15:08:21 +00:00
response = MockResponse('Not Found', status_code=409)
2017-11-06 13:32:31 +00:00
result = validator.validate(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == InvalidResponse
2017-11-06 14:05:06 +00:00
assert result.data is None
2020-03-02 16:05:36 +00:00
assert result.headers == {}
2017-11-06 13:32:31 +00:00
2017-11-06 13:53:49 +00:00
def test_invalid_content_type(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
response = MockResponse('Not Found', mimetype='text/csv')
result = validator.validate(request, response)
assert len(result.errors) == 1
2021-03-30 12:09:39 +00:00
assert type(result.errors[0]) == MediaTypeNotFound
2017-11-06 14:05:06 +00:00
assert result.data is None
2017-11-06 13:53:49 +00:00
assert result.headers == {}
2017-11-06 13:32:31 +00:00
def test_missing_body(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
response = MockResponse(None)
result = validator.validate(request, response)
assert len(result.errors) == 1
2018-04-18 10:39:03 +00:00
assert type(result.errors[0]) == MissingResponseContent
2017-11-06 14:05:06 +00:00
assert result.data is None
2017-11-06 13:32:31 +00:00
assert result.headers == {}
2019-09-03 00:38:19 +00:00
def test_invalid_media_type(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
response = MockResponse("abcde")
result = validator.validate(request, response)
assert len(result.errors) == 1
assert type(result.errors[0]) == DeserializeError
2019-09-03 00:38:19 +00:00
assert result.data is None
assert result.headers == {}
2017-11-06 13:32:31 +00:00
def test_invalid_media_type_value(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
2019-02-24 02:28:45 +00:00
response = MockResponse("{}")
2017-11-06 13:32:31 +00:00
result = validator.validate(request, response)
assert len(result.errors) == 1
2020-02-02 22:51:02 +00:00
assert type(result.errors[0]) == InvalidSchemaValue
2017-11-06 14:05:06 +00:00
assert result.data is None
2017-11-06 13:32:31 +00:00
assert result.headers == {}
2018-04-23 18:50:29 +00:00
def test_invalid_value(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/tags')
response_json = {
'data': [
{
'id': 1,
'name': 'Sparky'
},
],
}
response_data = json.dumps(response_json)
response = MockResponse(response_data)
result = validator.validate(request, response)
assert len(result.errors) == 1
2020-02-02 22:51:02 +00:00
assert type(result.errors[0]) == InvalidSchemaValue
2018-04-23 18:50:29 +00:00
assert result.data is None
assert result.headers == {}
2017-11-06 13:32:31 +00:00
def test_get_pets(self, validator):
request = MockRequest(self.host_url, 'get', '/v1/pets')
response_json = {
'data': [
{
'id': 1,
2019-09-03 00:38:19 +00:00
'name': 'Sparky',
'ears': {
'healthy': True,
},
2017-11-06 13:32:31 +00:00
},
],
}
response_data = json.dumps(response_json)
response = MockResponse(response_data)
result = validator.validate(request, response)
assert result.errors == []
2018-08-21 17:33:24 +00:00
assert isinstance(result.data, BaseModel)
assert len(result.data.data) == 1
assert result.data.data[0].id == 1
assert result.data.data[0].name == 'Sparky'
2017-11-06 13:32:31 +00:00
assert result.headers == {}