first fixes to openapi3 tests

This commit is contained in:
Thorsten Franzel 2019-12-13 11:59:34 +01:00
parent 9486df8d04
commit d24a15f166
2 changed files with 174 additions and 136 deletions

View File

@ -329,7 +329,7 @@ class AutoSchema(ViewInspector):
if isinstance(field, serializers.ManyRelatedField): if isinstance(field, serializers.ManyRelatedField):
return { return {
'type': 'array', 'type': 'array',
'items': self._map_field(field.child_relation) 'items': self._map_field(method, field.child_relation)
} }
if isinstance(field, serializers.PrimaryKeyRelatedField): if isinstance(field, serializers.PrimaryKeyRelatedField):
model = getattr(field.queryset, 'model', None) model = getattr(field.queryset, 'model', None)
@ -361,7 +361,7 @@ class AutoSchema(ViewInspector):
'items': {}, 'items': {},
} }
if not isinstance(field.child, _UnvalidatedField): if not isinstance(field.child, _UnvalidatedField):
map_field = self._map_field(field.child) map_field = self._map_field(method, field.child)
items = { items = {
"type": map_field.get('type') "type": map_field.get('type')
} }

View File

@ -8,7 +8,7 @@ from rest_framework.compat import uritemplate
from rest_framework.parsers import JSONParser, MultiPartParser from rest_framework.parsers import JSONParser, MultiPartParser
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator, ComponentRegistry
from . import views from . import views
@ -57,7 +57,7 @@ class TestFieldMapping(TestCase):
] ]
for field, mapping in cases: for field, mapping in cases:
with self.subTest(field=field): with self.subTest(field=field):
assert inspector._map_field(field) == mapping assert inspector._map_field('GET', field) == mapping
def test_lazy_string_field(self): def test_lazy_string_field(self):
class Serializer(serializers.Serializer): class Serializer(serializers.Serializer):
@ -65,7 +65,7 @@ class TestFieldMapping(TestCase):
inspector = AutoSchema() inspector = AutoSchema()
data = inspector._map_serializer(Serializer()) data = inspector._map_serializer('GET', Serializer())
assert isinstance(data['properties']['text']['description'], str), "description must be str" assert isinstance(data['properties']['text']['description'], str), "description must be str"
@ -83,26 +83,32 @@ class TestOperationIntrospection(TestCase):
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(ComponentRegistry())
operation = inspector.get_operation(path, method) operation = inspector.get_operation(path, method)
assert operation == { assert operation == {
'operationId': 'listDocStringExamples', 'operationId': 'example_list',
'description': 'A description of my GET operation.', 'description': 'get: A description of my GET operation.\npost: A description of my POST operation.',
'parameters': [], 'parameters': [],
'tags': [''],
'security': [{'cookieAuth': []}, {'basicAuth': []}, {}],
'responses': { 'responses': {
'200': { '200': {
'description': '',
'content': { 'content': {
'application/json': { 'application/json': {
'schema': { 'schema': {
'type': 'array', 'type': 'array',
'items': {}, 'items': {
}, 'type': 'object',
}, 'description': 'Unspecified response body'
}
}
}, },
}, },
'description': ''
}, },
} }
}
def test_path_with_id_parameter(self): def test_path_with_id_parameter(self):
path = '/example/{id}/' path = '/example/{id}/'
@ -114,131 +120,156 @@ class TestOperationIntrospection(TestCase):
create_request(path) create_request(path)
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.init(ComponentRegistry())
inspector.view = view inspector.view = view
operation = inspector.get_operation(path, method) operation = inspector.get_operation(path, method)
assert operation == { assert operation == {
'operationId': 'RetrieveDocStringExampleDetail', 'operationId': 'example_retrieve',
'description': 'A description of my GET operation.', 'description': '\n\nA description of my GET operation.',
'parameters': [{ 'parameters': [
'description': '', {
'in': 'path',
'name': 'id', 'name': 'id',
'in': 'path',
'required': True, 'required': True,
'description': '',
'schema': { 'schema': {
'type': 'string', 'type': 'string'
}, }
}], }
],
'tags': [''],
'security': [{'cookieAuth': []}, {'basicAuth': []}, {}],
'responses': { 'responses': {
'200': { '200': {
'description': '',
'content': { 'content': {
'application/json': { 'application/json': {
'schema': { 'schema': {
'type': 'object',
'description': 'Unspecified response body'
}
}
}, },
}, 'description': ''
}, }
}, }
},
} }
def test_request_body(self): def test_request_body(self):
path = '/' path = '/'
method = 'POST' method = 'POST'
class Serializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
read_only = serializers.CharField(read_only=True) read_only = serializers.CharField(read_only=True)
class View(generics.GenericAPIView): class View(generics.CreateAPIView):
serializer_class = Serializer serializer_class = ExampleSerializer
view = create_view( view = create_view(
View, View,
method, method,
create_request(path) create_request(path)
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
request_body = inspector._get_request_body(path, method) schema = registry.schemas['Example']
assert request_body['content']['application/json']['schema']['required'] == ['text'] assert schema['required'] == ['text']
assert list(request_body['content']['application/json']['schema']['properties'].keys()) == ['text'] assert schema['properties']['read_only']['readOnly'] is True
def test_empty_required(self): def test_empty_required(self):
path = '/' path = '/'
method = 'POST' method = 'POST'
class Serializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
read_only = serializers.CharField(read_only=True) read_only = serializers.CharField(read_only=True)
write_only = serializers.CharField(write_only=True, required=False) write_only = serializers.CharField(write_only=True, required=False)
class View(generics.GenericAPIView): class View(generics.CreateAPIView):
serializer_class = Serializer serializer_class = ExampleSerializer
view = create_view( view = create_view(
View, View,
method, method,
create_request(path) create_request(path)
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
request_body = inspector._get_request_body(path, method) schema = registry.schemas['Example']
# there should be no empty 'required' property, see #6834 # there should be no empty 'required' property, see #6834
assert 'required' not in request_body['content']['application/json']['schema'] assert 'required' not in schema
for response in inspector._get_responses(path, method).values():
assert 'required' not in response['content']['application/json']['schema']
def test_empty_required_with_patch_method(self): def test_empty_required_with_patch_method(self):
path = '/' path = '/'
method = 'PATCH' method = 'PATCH'
class Serializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
read_only = serializers.CharField(read_only=True) read_only = serializers.CharField(read_only=True)
write_only = serializers.CharField(write_only=True, required=False) write_only = serializers.CharField(write_only=True, required=False)
class View(generics.GenericAPIView): class View(generics.UpdateAPIView):
serializer_class = Serializer serializer_class = ExampleSerializer
view = create_view( view = create_view(
View, View,
method, method,
create_request(path) create_request(path)
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
request_body = inspector._get_request_body(path, method) schema = registry.schemas['PatchedExample']
# there should be no empty 'required' property, see #6834 # there should be no empty 'required' property, see #6834
assert 'required' not in request_body['content']['application/json']['schema'] assert 'required' not in schema
for response in inspector._get_responses(path, method).values(): for field_schema in schema['properties']:
assert 'required' not in response['content']['application/json']['schema'] assert 'required' not in field_schema
def test_response_body_generation(self): def test_response_body_generation(self):
path = '/' path = '/'
method = 'POST' method = 'POST'
class Serializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
write_only = serializers.CharField(write_only=True) write_only = serializers.CharField(write_only=True)
class View(generics.GenericAPIView): class View(generics.CreateAPIView):
serializer_class = Serializer serializer_class = ExampleSerializer
view = create_view( view = create_view(
View, View,
method, method,
create_request(path) create_request(path)
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
assert responses['200']['content']['application/json']['schema']['required'] == ['text']
assert list(responses['200']['content']['application/json']['schema']['properties'].keys()) == ['text'] assert operation['responses'] == {
assert 'description' in responses['200'] '200': {
'content': {
'application/json': {
'schema': {'$ref': '#/components/schemas/Example'}
}
},
'description': ''
}
}
assert registry.schemas['Example']['required'] == ['text', 'write_only']
assert list(registry.schemas['Example']['properties'].keys()) == ['text', 'write_only']
def test_response_body_nested_serializer(self): def test_response_body_nested_serializer(self):
path = '/' path = '/'
@ -247,28 +278,32 @@ class TestOperationIntrospection(TestCase):
class NestedSerializer(serializers.Serializer): class NestedSerializer(serializers.Serializer):
number = serializers.IntegerField() number = serializers.IntegerField()
class Serializer(serializers.Serializer): class ExampleSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
nested = NestedSerializer() nested = NestedSerializer()
class View(generics.GenericAPIView): class View(generics.CreateAPIView):
serializer_class = Serializer serializer_class = ExampleSerializer
view = create_view( view = create_view(
View, View,
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
schema = responses['200']['content']['application/json']['schema'] example_schema = registry.schemas['Example']
assert sorted(schema['required']) == ['nested', 'text'] nested_schema = registry.schemas['Nested']
assert sorted(list(schema['properties'].keys())) == ['nested', 'text']
assert schema['properties']['nested']['type'] == 'object' assert sorted(example_schema['required']) == ['nested', 'text']
assert list(schema['properties']['nested']['properties'].keys()) == ['number'] assert sorted(list(example_schema['properties'].keys())) == ['nested', 'text']
assert schema['properties']['nested']['required'] == ['number'] assert example_schema['properties']['nested']['type'] == 'object'
assert list(nested_schema['properties'].keys()) == ['number']
assert nested_schema['required'] == ['number']
def test_list_response_body_generation(self): def test_list_response_body_generation(self):
"""Test that an array schema is returned for list views.""" """Test that an array schema is returned for list views."""
@ -278,7 +313,7 @@ class TestOperationIntrospection(TestCase):
class ItemSerializer(serializers.Serializer): class ItemSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
class View(generics.GenericAPIView): class View(generics.ListAPIView):
serializer_class = ItemSerializer serializer_class = ItemSerializer
view = create_view( view = create_view(
@ -286,29 +321,25 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
assert responses == {
assert operation['responses'] == {
'200': { '200': {
'description': '',
'content': { 'content': {
'application/json': { 'application/json': {
'schema': { 'schema': {
'type': 'array', 'type': 'array',
'items': { 'items': {'$ref': '#/components/schemas/Item'},
'properties': { }
'text': { }
'type': 'string',
},
},
'required': ['text'],
},
},
},
},
}, },
'description': ''
}
} }
def test_paginated_list_response_body_generation(self): def test_paginated_list_response_body_generation(self):
@ -326,7 +357,7 @@ class TestOperationIntrospection(TestCase):
class ItemSerializer(serializers.Serializer): class ItemSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
class View(generics.GenericAPIView): class View(generics.ListAPIView):
serializer_class = ItemSerializer serializer_class = ItemSerializer
pagination_class = Pagination pagination_class = Pagination
@ -337,9 +368,10 @@ class TestOperationIntrospection(TestCase):
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(ComponentRegistry())
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
assert responses == { assert operation['responses'] == {
'200': { '200': {
'description': '', 'description': '',
'content': { 'content': {
@ -348,14 +380,7 @@ class TestOperationIntrospection(TestCase):
'type': 'object', 'type': 'object',
'item': { 'item': {
'type': 'array', 'type': 'array',
'items': { 'items': {'$ref': '#/components/schemas/Item'},
'properties': {
'text': {
'type': 'string',
},
},
'required': ['text'],
},
}, },
}, },
}, },
@ -378,11 +403,12 @@ class TestOperationIntrospection(TestCase):
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(ComponentRegistry())
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
assert responses == { assert operation['responses'] == {
'204': { '204': {
'description': '', 'description': 'No response body',
}, },
} }
@ -402,19 +428,20 @@ class TestOperationIntrospection(TestCase):
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(ComponentRegistry())
request_body = inspector._get_request_body(path, method) operation = inspector.get_operation(path, method)
content = operation['requestBody']['content']
assert len(request_body['content'].keys()) == 2 assert len(content.keys()) == 2
assert 'multipart/form-data' in request_body['content'] assert 'multipart/form-data' in content
assert 'application/json' in request_body['content'] assert 'application/json' in content
def test_renderer_mapping(self): def test_renderer_mapping(self):
"""Test that view's renderers are mapped to OA media types""" """Test that view's renderers are mapped to OA media types"""
path = '/{id}/' path = '/{id}/'
method = 'GET' method = 'GET'
class View(generics.CreateAPIView): class View(generics.ListCreateAPIView):
serializer_class = views.ExampleSerializer serializer_class = views.ExampleSerializer
renderer_classes = [JSONRenderer] renderer_classes = [JSONRenderer]
@ -423,13 +450,15 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
# TODO this should be changed once the multiple response # TODO this should be changed once the multiple response
# schema support is there # schema support is there
success_response = responses['200'] success_response = operation['responses']['200']
assert len(success_response['content'].keys()) == 1 assert len(success_response['content'].keys()) == 1
assert 'application/json' in success_response['content'] assert 'application/json' in success_response['content']
@ -449,13 +478,15 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
request_body = inspector._get_request_body(path, method) operation = inspector.get_operation(path, method)
mp_media = request_body['content']['multipart/form-data']
attachment = mp_media['schema']['properties']['attachment'] assert 'multipart/form-data' in operation['requestBody']['content']
assert attachment['format'] == 'binary' assert registry.schemas['Item']['properties']['attachment']['format'] == 'binary'
def test_retrieve_response_body_generation(self): def test_retrieve_response_body_generation(self):
""" """
@ -476,7 +507,7 @@ class TestOperationIntrospection(TestCase):
class ItemSerializer(serializers.Serializer): class ItemSerializer(serializers.Serializer):
text = serializers.CharField() text = serializers.CharField()
class View(generics.GenericAPIView): class View(generics.RetrieveAPIView):
serializer_class = ItemSerializer serializer_class = ItemSerializer
pagination_class = Pagination pagination_class = Pagination
@ -485,26 +516,30 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
responses = inspector._get_responses(path, method) operation = inspector.get_operation(path, method)
assert responses == {
assert operation['responses'] == {
'200': { '200': {
'description': '',
'content': { 'content': {
'application/json': { 'application/json': {
'schema': { 'schema': {'$ref': '#/components/schemas/Item'}
}
},
'description': ''
}
}
assert registry.schemas['Item'] == {
'properties': { 'properties': {
'text': { 'text': {
'type': 'string', 'type': 'string',
}, },
}, },
'required': ['text'], 'required': ['text'],
},
},
},
},
} }
def test_operation_id_generation(self): def test_operation_id_generation(self):
@ -518,9 +553,10 @@ class TestOperationIntrospection(TestCase):
) )
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(ComponentRegistry())
operationId = inspector._get_operation_id(path, method) operationId = inspector._get_operation_id(path, method)
assert operationId == 'listExamples' assert operationId == 'list'
def test_repeat_operation_ids(self): def test_repeat_operation_ids(self):
router = routers.SimpleRouter() router = routers.SimpleRouter()
@ -532,10 +568,9 @@ class TestOperationIntrospection(TestCase):
request = create_request('/') request = create_request('/')
schema = generator.get_schema(request=request) schema = generator.get_schema(request=request)
schema_str = str(schema) schema_str = str(schema)
print(schema_str)
assert schema_str.count("operationId") == 2 assert schema_str.count("operationId") == 2
assert schema_str.count("newExample") == 1 assert schema_str.count("account_new_retrieve") == 1
assert schema_str.count("oldExample") == 1 assert schema_str.count("account_old_retrieve") == 1
def test_serializer_datefield(self): def test_serializer_datefield(self):
path = '/' path = '/'
@ -545,12 +580,13 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
responses = inspector._get_responses(path, method) properties = registry.schemas['Example']['properties']
response_schema = responses['200']['content']['application/json']['schema']
properties = response_schema['items']['properties']
assert properties['date']['type'] == properties['datetime']['type'] == 'string' assert properties['date']['type'] == properties['datetime']['type'] == 'string'
assert properties['date']['format'] == 'date' assert properties['date']['format'] == 'date'
assert properties['datetime']['format'] == 'date-time' assert properties['datetime']['format'] == 'date-time'
@ -563,12 +599,13 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
responses = inspector._get_responses(path, method) properties = registry.schemas['Example']['properties']
response_schema = responses['200']['content']['application/json']['schema']
properties = response_schema['items']['properties']
assert properties['hstore']['type'] == 'object' assert properties['hstore']['type'] == 'object'
def test_serializer_callable_default(self): def test_serializer_callable_default(self):
@ -595,12 +632,13 @@ class TestOperationIntrospection(TestCase):
method, method,
create_request(path), create_request(path),
) )
registry = ComponentRegistry()
inspector = AutoSchema() inspector = AutoSchema()
inspector.view = view inspector.view = view
inspector.init(registry)
inspector.get_operation(path, method)
responses = inspector._get_responses(path, method) properties = registry.schemas['ExampleValidated']['properties']
response_schema = responses['200']['content']['application/json']['schema']
properties = response_schema['items']['properties']
assert properties['integer']['type'] == 'integer' assert properties['integer']['type'] == 'integer'
assert properties['integer']['maximum'] == 99 assert properties['integer']['maximum'] == 99
@ -659,7 +697,7 @@ class TestGenerator(TestCase):
generator = SchemaGenerator(patterns=patterns) generator = SchemaGenerator(patterns=patterns)
generator._initialise_endpoints() generator._initialise_endpoints()
paths = generator.get_paths() paths = generator.parse()
assert '/example/' in paths assert '/example/' in paths
example_operations = paths['/example/'] example_operations = paths['/example/']
@ -676,7 +714,7 @@ class TestGenerator(TestCase):
generator = SchemaGenerator(patterns=patterns) generator = SchemaGenerator(patterns=patterns)
generator._initialise_endpoints() generator._initialise_endpoints()
paths = generator.get_paths() paths = generator.parse()
assert '/v1/example/' in paths assert '/v1/example/' in paths
assert '/v1/example/{id}/' in paths assert '/v1/example/{id}/' in paths
@ -689,7 +727,7 @@ class TestGenerator(TestCase):
generator = SchemaGenerator(patterns=patterns, url='/api') generator = SchemaGenerator(patterns=patterns, url='/api')
generator._initialise_endpoints() generator._initialise_endpoints()
paths = generator.get_paths() paths = generator.parse()
assert '/api/example/' in paths assert '/api/example/' in paths
assert '/api/example/{id}/' in paths assert '/api/example/{id}/' in paths
@ -732,4 +770,4 @@ class TestGenerator(TestCase):
schema = generator.get_schema(request=request) schema = generator.get_schema(request=request)
assert schema['info']['title'] == '' assert schema['info']['title'] == ''
assert schema['info']['version'] == '' assert schema['info']['version'] == '0.0.0'