django-rest-framework/tests/schemas/test_openapi.py

337 lines
12 KiB
Python
Raw Normal View History

import pytest
from django.conf.urls import url
from django.test import RequestFactory, TestCase, override_settings
from rest_framework import filters, generics, pagination, routers, serializers
from rest_framework.compat import uritemplate
from rest_framework.request import Request
from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator
from . import views
def create_request(path):
factory = RequestFactory()
request = Request(factory.get(path))
return request
def create_view(view_cls, method, request):
generator = SchemaGenerator()
view = generator.create_view(view_cls.as_view(), method, request)
return view
class TestBasics(TestCase):
def dummy_view(request):
pass
def test_filters(self):
classes = [filters.SearchFilter, filters.OrderingFilter]
for c in classes:
f = c()
assert f.get_schema_operation_parameters(self.dummy_view)
def test_pagination(self):
classes = [pagination.PageNumberPagination, pagination.LimitOffsetPagination, pagination.CursorPagination]
for c in classes:
f = c()
assert f.get_schema_operation_parameters(self.dummy_view)
class TestFieldMapping(TestCase):
def test_list_field_mapping(self):
inspector = AutoSchema()
cases = [
(serializers.ListField(), {'items': {}, 'type': 'array'}),
(serializers.ListField(child=serializers.BooleanField()), {'items': {'type': 'boolean'}, 'type': 'array'}),
(serializers.ListField(child=serializers.FloatField()), {'items': {'type': 'number'}, 'type': 'array'}),
(serializers.ListField(child=serializers.CharField()), {'items': {'type': 'string'}, 'type': 'array'}),
]
for field, mapping in cases:
with self.subTest(field=field):
assert inspector._map_field(field) == mapping
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
class TestOperationIntrospection(TestCase):
def test_path_without_parameters(self):
path = '/example/'
method = 'GET'
view = create_view(
views.ExampleListView,
method,
create_request(path)
)
inspector = AutoSchema()
inspector.view = view
operation = inspector.get_operation(path, method)
assert operation == {
'operationId': 'ListExamples',
'parameters': [],
'responses': {'200': {'content': {'application/json': {'schema': {}}}}},
}
def test_path_with_id_parameter(self):
path = '/example/{id}/'
method = 'GET'
view = create_view(
views.ExampleDetailView,
method,
create_request(path)
)
inspector = AutoSchema()
inspector.view = view
parameters = inspector._get_path_parameters(path, method)
assert parameters == [{
'description': '',
'in': 'path',
'name': 'id',
'required': True,
'schema': {
'type': 'string',
},
}]
def test_request_body(self):
path = '/'
method = 'POST'
class Serializer(serializers.Serializer):
text = serializers.CharField()
read_only = serializers.CharField(read_only=True)
class View(generics.GenericAPIView):
serializer_class = Serializer
view = create_view(
View,
method,
create_request(path)
)
inspector = AutoSchema()
inspector.view = view
request_body = inspector._get_request_body(path, method)
assert request_body['content']['application/json']['schema']['required'] == ['text']
assert list(request_body['content']['application/json']['schema']['properties'].keys()) == ['text']
def test_response_body_generation(self):
path = '/'
method = 'POST'
class Serializer(serializers.Serializer):
text = serializers.CharField()
write_only = serializers.CharField(write_only=True)
class View(generics.GenericAPIView):
serializer_class = Serializer
view = create_view(
View,
method,
create_request(path)
)
inspector = AutoSchema()
inspector.view = view
responses = inspector._get_responses(path, method)
assert responses['200']['content']['application/json']['schema']['required'] == ['text']
assert list(responses['200']['content']['application/json']['schema']['properties'].keys()) == ['text']
def test_response_body_nested_serializer(self):
path = '/'
method = 'POST'
class NestedSerializer(serializers.Serializer):
number = serializers.IntegerField()
class Serializer(serializers.Serializer):
text = serializers.CharField()
nested = NestedSerializer()
class View(generics.GenericAPIView):
serializer_class = Serializer
view = create_view(
View,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view
responses = inspector._get_responses(path, method)
schema = responses['200']['content']['application/json']['schema']
assert sorted(schema['required']) == ['nested', 'text']
assert sorted(list(schema['properties'].keys())) == ['nested', 'text']
assert schema['properties']['nested']['type'] == 'object'
assert list(schema['properties']['nested']['properties'].keys()) == ['number']
assert schema['properties']['nested']['required'] == ['number']
def test_operation_id_generation(self):
path = '/'
method = 'GET'
view = create_view(
views.ExampleGenericAPIView,
method,
create_request(path),
)
inspector = AutoSchema()
inspector.view = view
operationId = inspector._get_operation_id(path, method)
assert operationId == 'ListExamples'
def test_repeat_operation_ids(self):
router = routers.SimpleRouter()
router.register('account', views.ExampleGenericViewSet, basename="account")
urlpatterns = router.urls
generator = SchemaGenerator(patterns=urlpatterns)
request = create_request('/')
schema = generator.get_schema(request=request)
schema_str = str(schema)
print(schema_str)
assert schema_str.count("operationId") == 2
assert schema_str.count("newExample") == 1
assert schema_str.count("oldExample") == 1
@pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.')
@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema'})
class TestGenerator(TestCase):
def test_override_settings(self):
assert isinstance(views.ExampleListView.schema, AutoSchema)
def test_paths_construction(self):
"""Construction of the `paths` key."""
patterns = [
url(r'^example/?$', views.ExampleListView.as_view()),
]
generator = SchemaGenerator(patterns=patterns)
generator._initialise_endpoints()
paths = generator.get_paths()
assert '/example/' in paths
example_operations = paths['/example/']
assert len(example_operations) == 2
assert 'get' in example_operations
assert 'post' in example_operations
def test_prefixed_paths_construction(self):
"""Construction of the `paths` key maintains a common prefix."""
patterns = [
url(r'^v1/example/?$', views.ExampleListView.as_view()),
url(r'^v1/example/{pk}/?$', views.ExampleDetailView.as_view()),
]
generator = SchemaGenerator(patterns=patterns)
generator._initialise_endpoints()
paths = generator.get_paths()
assert '/v1/example/' in paths
assert '/v1/example/{id}/' in paths
def test_mount_url_prefixed_to_paths(self):
patterns = [
url(r'^example/?$', views.ExampleListView.as_view()),
url(r'^example/{pk}/?$', views.ExampleDetailView.as_view()),
]
generator = SchemaGenerator(patterns=patterns, url='/api')
generator._initialise_endpoints()
paths = generator.get_paths()
assert '/api/example/' in paths
assert '/api/example/{id}/' in paths
def test_schema_construction(self):
"""Construction of the top level dictionary."""
patterns = [
url(r'^example/?$', views.ExampleListView.as_view()),
]
generator = SchemaGenerator(patterns=patterns)
request = create_request('/')
schema = generator.get_schema(request=request)
assert 'openapi' in schema
assert 'paths' in schema
def test_serializer_datefield(self):
patterns = [
url(r'^example/?$', views.ExampleGenericViewSet.as_view({"get": "get"})),
]
generator = SchemaGenerator(patterns=patterns)
request = create_request('/')
schema = generator.get_schema(request=request)
response = schema['paths']['/example/']['get']['responses']
response_schema = response['200']['content']['application/json']['schema']['properties']
assert response_schema['date']['type'] == response_schema['datetime']['type'] == 'string'
assert response_schema['date']['format'] == 'date'
assert response_schema['datetime']['format'] == 'date-time'
def test_serializer_validators(self):
patterns = [
url(r'^example/?$', views.ExampleValdidatedAPIView.as_view()),
]
generator = SchemaGenerator(patterns=patterns)
request = create_request('/')
schema = generator.get_schema(request=request)
response = schema['paths']['/example/']['get']['responses']
response_schema = response['200']['content']['application/json']['schema']['properties']
assert response_schema['integer']['type'] == 'integer'
assert response_schema['integer']['maximum'] == 99
assert response_schema['integer']['minimum'] == -11
assert response_schema['string']['minLength'] == 2
assert response_schema['string']['maxLength'] == 10
assert response_schema['regex']['pattern'] == r'[ABC]12{3}'
assert response_schema['regex']['description'] == 'must have an A, B, or C followed by 1222'
assert response_schema['decimal1']['type'] == 'number'
assert response_schema['decimal1']['multipleOf'] == .01
assert response_schema['decimal1']['maximum'] == 10000
assert response_schema['decimal1']['minimum'] == -10000
assert response_schema['decimal2']['type'] == 'number'
assert response_schema['decimal2']['multipleOf'] == .0001
assert response_schema['email']['type'] == 'string'
assert response_schema['email']['format'] == 'email'
assert response_schema['email']['default'] == 'foo@bar.com'
assert response_schema['url']['type'] == 'string'
assert response_schema['url']['nullable'] is True
assert response_schema['url']['default'] == 'http://www.example.com'
assert response_schema['uuid']['type'] == 'string'
assert response_schema['uuid']['format'] == 'uuid'
assert response_schema['ip4']['type'] == 'string'
assert response_schema['ip4']['format'] == 'ipv4'
assert response_schema['ip6']['type'] == 'string'
assert response_schema['ip6']['format'] == 'ipv6'
assert response_schema['ip']['type'] == 'string'
assert 'format' not in response_schema['ip']