diff --git a/tests/schemas/__init__.py b/tests/schemas/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/schemas/test_coreapi.py b/tests/schemas/test_coreapi.py deleted file mode 100644 index a97b02fe1..000000000 --- a/tests/schemas/test_coreapi.py +++ /dev/null @@ -1,1486 +0,0 @@ -import unittest - -import pytest -from django.core.exceptions import PermissionDenied -from django.http import Http404 -from django.test import TestCase, override_settings -from django.urls import include, path - -from rest_framework import ( - RemovedInDRF317Warning, filters, generics, pagination, permissions, - serializers -) -from rest_framework.compat import coreapi, coreschema -from rest_framework.decorators import action, api_view, schema -from rest_framework.filters import ( - BaseFilterBackend, OrderingFilter, SearchFilter -) -from rest_framework.pagination import ( - BasePagination, CursorPagination, LimitOffsetPagination, - PageNumberPagination -) -from rest_framework.request import Request -from rest_framework.routers import DefaultRouter, SimpleRouter -from rest_framework.schemas import ( - AutoSchema, ManualSchema, SchemaGenerator, get_schema_view -) -from rest_framework.schemas.coreapi import field_to_schema, is_enabled -from rest_framework.schemas.generators import EndpointEnumerator -from rest_framework.schemas.utils import is_list_view -from rest_framework.test import APIClient, APIRequestFactory -from rest_framework.utils import formatting -from rest_framework.views import APIView -from rest_framework.viewsets import GenericViewSet, ModelViewSet - -from ..models import BasicModel, ForeignKeySource, ManyToManySource -from . import views - -factory = APIRequestFactory() - - -class MockUser: - def is_authenticated(self): - return True - - -class ExamplePagination(pagination.PageNumberPagination): - page_size = 100 - page_size_query_param = 'page_size' - - -class EmptySerializer(serializers.Serializer): - pass - - -class ExampleSerializer(serializers.Serializer): - a = serializers.CharField(required=True, help_text='A field description') - b = serializers.CharField(required=False) - read_only = serializers.CharField(read_only=True) - hidden = serializers.HiddenField(default='hello') - - -class AnotherSerializerWithDictField(serializers.Serializer): - a = serializers.DictField() - - -class AnotherSerializerWithListFields(serializers.Serializer): - a = serializers.ListField(child=serializers.IntegerField()) - b = serializers.ListSerializer(child=serializers.CharField()) - - -class AnotherSerializer(serializers.Serializer): - c = serializers.CharField(required=True) - d = serializers.CharField(required=False) - - -class ExampleViewSet(ModelViewSet): - pagination_class = ExamplePagination - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - filter_backends = [filters.OrderingFilter] - serializer_class = ExampleSerializer - - @action(methods=['post'], detail=True, serializer_class=AnotherSerializer) - def custom_action(self, request, pk): - """ - A description of custom action. - """ - raise NotImplementedError - - @action(methods=['post'], detail=True, serializer_class=AnotherSerializerWithDictField) - def custom_action_with_dict_field(self, request, pk): - """ - A custom action using a dict field in the serializer. - """ - raise NotImplementedError - - @action(methods=['post'], detail=True, serializer_class=AnotherSerializerWithListFields) - def custom_action_with_list_fields(self, request, pk): - """ - A custom action using both list field and list serializer in the serializer. - """ - raise NotImplementedError - - @action(detail=False) - def custom_list_action(self, request): - raise NotImplementedError - - @action(methods=['post', 'get'], detail=False, serializer_class=EmptySerializer) - def custom_list_action_multiple_methods(self, request): - """Custom description.""" - raise NotImplementedError - - @custom_list_action_multiple_methods.mapping.delete - def custom_list_action_multiple_methods_delete(self, request): - """Deletion description.""" - raise NotImplementedError - - @action(detail=False, schema=None) - def excluded_action(self, request): - pass - - def get_serializer(self, *args, **kwargs): - assert self.request - assert self.action - return super().get_serializer(*args, **kwargs) - - @action(methods=['get', 'post'], detail=False) - def documented_custom_action(self, request): - """ - get: - A description of the get method on the custom action. - - post: - A description of the post method on the custom action. - """ - pass - - @documented_custom_action.mapping.put - def put_documented_custom_action(self, request, *args, **kwargs): - """ - A description of the put method on the custom action from mapping. - """ - pass - - -with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}): - if coreapi: - schema_view = get_schema_view(title='Example API') - else: - def schema_view(request): - pass - -router = DefaultRouter() -router.register('example', ExampleViewSet, basename='example') -urlpatterns = [ - path('', schema_view), - path('', include(router.urls)) -] - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(ROOT_URLCONF=__name__, REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestRouterGeneratedSchema(TestCase): - def test_anonymous_request(self): - client = APIClient() - response = client.get('/', HTTP_ACCEPT='application/coreapi+json') - assert response.status_code == 200 - expected = coreapi.Document( - url='http://testserver/', - title='Example API', - content={ - 'example': { - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[ - coreapi.Field('page', required=False, location='query', schema=coreschema.Integer(title='Page', description='A page number within the paginated result set.')), - coreapi.Field('page_size', required=False, location='query', schema=coreschema.Integer(title='Page size', description='Number of results to return per page.')), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'custom_list_action': coreapi.Link( - url='/example/custom_list_action/', - action='get' - ), - 'custom_list_action_multiple_methods': { - 'read': coreapi.Link( - url='/example/custom_list_action_multiple_methods/', - action='get', - description='Custom description.', - ) - }, - 'documented_custom_action': { - 'read': coreapi.Link( - url='/example/documented_custom_action/', - action='get', - description='A description of the get method on the custom action.', - ) - }, - 'read': coreapi.Link( - url='/example/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ) - } - } - ) - assert response.data == expected - - def test_authenticated_request(self): - client = APIClient() - client.force_authenticate(MockUser()) - response = client.get('/', HTTP_ACCEPT='application/coreapi+json') - assert response.status_code == 200 - expected = coreapi.Document( - url='http://testserver/', - title='Example API', - content={ - 'example': { - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[ - coreapi.Field('page', required=False, location='query', schema=coreschema.Integer(title='Page', description='A page number within the paginated result set.')), - coreapi.Field('page_size', required=False, location='query', schema=coreschema.Integer(title='Page size', description='Number of results to return per page.')), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'create': coreapi.Link( - url='/example/', - action='post', - encoding='application/json', - fields=[ - coreapi.Field('a', required=True, location='form', schema=coreschema.String(title='A', description='A field description')), - coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B')) - ] - ), - 'read': coreapi.Link( - url='/example/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'custom_action': coreapi.Link( - url='/example/{id}/custom_action/', - action='post', - encoding='application/json', - description='A description of custom action.', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('c', required=True, location='form', schema=coreschema.String(title='C')), - coreapi.Field('d', required=False, location='form', schema=coreschema.String(title='D')), - ] - ), - 'custom_action_with_dict_field': coreapi.Link( - url='/example/{id}/custom_action_with_dict_field/', - action='post', - encoding='application/json', - description='A custom action using a dict field in the serializer.', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('a', required=True, location='form', schema=coreschema.Object(title='A')), - ] - ), - 'custom_action_with_list_fields': coreapi.Link( - url='/example/{id}/custom_action_with_list_fields/', - action='post', - encoding='application/json', - description='A custom action using both list field and list serializer in the serializer.', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('a', required=True, location='form', schema=coreschema.Array(title='A', items=coreschema.Integer())), - coreapi.Field('b', required=True, location='form', schema=coreschema.Array(title='B', items=coreschema.String())), - ] - ), - 'custom_list_action': coreapi.Link( - url='/example/custom_list_action/', - action='get' - ), - 'custom_list_action_multiple_methods': { - 'read': coreapi.Link( - url='/example/custom_list_action_multiple_methods/', - action='get', - description='Custom description.', - ), - 'create': coreapi.Link( - url='/example/custom_list_action_multiple_methods/', - action='post', - description='Custom description.', - ), - 'delete': coreapi.Link( - url='/example/custom_list_action_multiple_methods/', - action='delete', - description='Deletion description.', - ), - }, - 'documented_custom_action': { - 'read': coreapi.Link( - url='/example/documented_custom_action/', - action='get', - description='A description of the get method on the custom action.', - ), - 'create': coreapi.Link( - url='/example/documented_custom_action/', - action='post', - description='A description of the post method on the custom action.', - encoding='application/json', - fields=[ - coreapi.Field('a', required=True, location='form', schema=coreschema.String(title='A', description='A field description')), - coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B')) - ] - ), - 'update': coreapi.Link( - url='/example/documented_custom_action/', - action='put', - description='A description of the put method on the custom action from mapping.', - encoding='application/json', - fields=[ - coreapi.Field('a', required=True, location='form', schema=coreschema.String(title='A', description='A field description')), - coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B')) - ] - ), - }, - 'update': coreapi.Link( - url='/example/{id}/', - action='put', - encoding='application/json', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('a', required=True, location='form', schema=coreschema.String(title='A', description=('A field description'))), - coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B')), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'partial_update': coreapi.Link( - url='/example/{id}/', - action='patch', - encoding='application/json', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('a', required=False, location='form', schema=coreschema.String(title='A', description='A field description')), - coreapi.Field('b', required=False, location='form', schema=coreschema.String(title='B')), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'delete': coreapi.Link( - url='/example/{id}/', - action='delete', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ) - } - } - ) - assert response.data == expected - - -class DenyAllUsingHttp404(permissions.BasePermission): - - def has_permission(self, request, view): - raise Http404() - - def has_object_permission(self, request, view, obj): - raise Http404() - - -class DenyAllUsingPermissionDenied(permissions.BasePermission): - - def has_permission(self, request, view): - raise PermissionDenied() - - def has_object_permission(self, request, view, obj): - raise PermissionDenied() - - -class Http404ExampleViewSet(ExampleViewSet): - permission_classes = [DenyAllUsingHttp404] - - -class PermissionDeniedExampleViewSet(ExampleViewSet): - permission_classes = [DenyAllUsingPermissionDenied] - - -class MethodLimitedViewSet(ExampleViewSet): - permission_classes = [] - http_method_names = ['get', 'head', 'options'] - - -class ExampleListView(APIView): - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - pass - - def post(self, request, *args, **kwargs): - pass - - -class ExampleDetailView(APIView): - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - pass - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGenerator(TestCase): - def setUp(self): - self.patterns = [ - path('example/', views.ExampleListView.as_view()), - path('example//', views.ExampleDetailView.as_view()), - path('example//sub/', views.ExampleDetailView.as_view()), - ] - - def test_schema_for_regular_views(self): - """ - Ensure that schema generation works for APIView classes. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'create': coreapi.Link( - url='/example/', - action='post', - fields=[] - ), - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[] - ), - 'read': coreapi.Link( - url='/example/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ), - 'sub': { - 'list': coreapi.Link( - url='/example/{id}/sub/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ) - } - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorDjango2(TestCase): - def setUp(self): - self.patterns = [ - path('example/', views.ExampleListView.as_view()), - path('example//', views.ExampleDetailView.as_view()), - path('example//sub/', views.ExampleDetailView.as_view()), - ] - - def test_schema_for_regular_views(self): - """ - Ensure that schema generation works for APIView classes. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'create': coreapi.Link( - url='/example/', - action='post', - fields=[] - ), - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[] - ), - 'read': coreapi.Link( - url='/example/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ), - 'sub': { - 'list': coreapi.Link( - url='/example/{id}/sub/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ) - } - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorNotAtRoot(TestCase): - def setUp(self): - self.patterns = [ - path('api/v1/example/', views.ExampleListView.as_view()), - path('api/v1/example//', views.ExampleDetailView.as_view()), - path('api/v1/example//sub/', views.ExampleDetailView.as_view()), - ] - - def test_schema_for_regular_views(self): - """ - Ensure that schema generation with an API that is not at the URL - root continues to use correct structure for link keys. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'create': coreapi.Link( - url='/api/v1/example/', - action='post', - fields=[] - ), - 'list': coreapi.Link( - url='/api/v1/example/', - action='get', - fields=[] - ), - 'read': coreapi.Link( - url='/api/v1/example/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ), - 'sub': { - 'list': coreapi.Link( - url='/api/v1/example/{id}/sub/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()) - ] - ) - } - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorWithMethodLimitedViewSets(TestCase): - def setUp(self): - router = DefaultRouter() - router.register('example1', MethodLimitedViewSet, basename='example1') - self.patterns = [ - path('', include(router.urls)) - ] - - def test_schema_for_regular_views(self): - """ - Ensure that schema generation works for ViewSet classes - with method limitation by Django CBV's http_method_names attribute - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - request = factory.get('/example1/') - schema = generator.get_schema(Request(request)) - - expected = coreapi.Document( - url='http://testserver/example1/', - title='Example API', - content={ - 'example1': { - 'list': coreapi.Link( - url='/example1/', - action='get', - fields=[ - coreapi.Field('page', required=False, location='query', schema=coreschema.Integer(title='Page', description='A page number within the paginated result set.')), - coreapi.Field('page_size', required=False, location='query', schema=coreschema.Integer(title='Page size', description='Number of results to return per page.')), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ), - 'custom_list_action': coreapi.Link( - url='/example1/custom_list_action/', - action='get' - ), - 'custom_list_action_multiple_methods': { - 'read': coreapi.Link( - url='/example1/custom_list_action_multiple_methods/', - action='get', - description='Custom description.', - ) - }, - 'documented_custom_action': { - 'read': coreapi.Link( - url='/example1/documented_custom_action/', - action='get', - description='A description of the get method on the custom action.', - ), - }, - 'read': coreapi.Link( - url='/example1/{id}/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - coreapi.Field('ordering', required=False, location='query', schema=coreschema.String(title='Ordering', description='Which field to use when ordering the results.')) - ] - ) - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorWithRestrictedViewSets(TestCase): - def setUp(self): - router = DefaultRouter() - router.register('example1', Http404ExampleViewSet, basename='example1') - router.register('example2', PermissionDeniedExampleViewSet, basename='example2') - self.patterns = [ - path('example/', views.ExampleListView.as_view()), - path('', include(router.urls)) - ] - - def test_schema_for_regular_views(self): - """ - Ensure that schema generation works for ViewSet classes - with permission classes raising exceptions. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - request = factory.get('/') - schema = generator.get_schema(Request(request)) - expected = coreapi.Document( - url='http://testserver/', - title='Example API', - content={ - 'example': { - 'list': coreapi.Link( - url='/example/', - action='get', - fields=[] - ), - }, - } - ) - assert schema == expected - - -class ForeignKeySourceSerializer(serializers.ModelSerializer): - class Meta: - model = ForeignKeySource - fields = ('id', 'name', 'target') - - -class ForeignKeySourceView(generics.CreateAPIView): - queryset = ForeignKeySource.objects.all() - serializer_class = ForeignKeySourceSerializer - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorWithForeignKey(TestCase): - def setUp(self): - self.patterns = [ - path('example/', ForeignKeySourceView.as_view()), - ] - - def test_schema_for_regular_views(self): - """ - Ensure that AutoField foreign keys are output as Integer. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'create': coreapi.Link( - url='/example/', - action='post', - encoding='application/json', - fields=[ - coreapi.Field('name', required=True, location='form', schema=coreschema.String(title='Name')), - coreapi.Field('target', required=True, location='form', schema=coreschema.Integer(description='Target', title='Target')), - ] - ) - } - } - ) - assert schema == expected - - -class ManyToManySourceSerializer(serializers.ModelSerializer): - class Meta: - model = ManyToManySource - fields = ('id', 'name', 'targets') - - -class ManyToManySourceView(generics.CreateAPIView): - queryset = ManyToManySource.objects.all() - serializer_class = ManyToManySourceSerializer - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorWithManyToMany(TestCase): - def setUp(self): - self.patterns = [ - path('example/', ManyToManySourceView.as_view()), - ] - - def test_schema_for_regular_views(self): - """ - Ensure that AutoField many to many fields are output as Integer. - """ - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'create': coreapi.Link( - url='/example/', - action='post', - encoding='application/json', - fields=[ - coreapi.Field('name', required=True, location='form', schema=coreschema.String(title='Name')), - coreapi.Field('targets', required=True, location='form', schema=coreschema.Array(title='Targets', items=coreschema.Integer())), - ] - ) - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestSchemaGeneratorActionKeysViewSets(TestCase): - def test_action_not_coerced_for_get_and_head(self): - """ - Ensure that action name is preserved when action map contains "head". - """ - class CustomViewSet(GenericViewSet): - serializer_class = EmptySerializer - - @action(methods=['get', 'head'], detail=True) - def custom_read(self, request, pk): - raise NotImplementedError - - @action(methods=['put', 'patch'], detail=True) - def custom_mixed_update(self, request, pk): - raise NotImplementedError - - self.router = DefaultRouter() - self.router.register('example', CustomViewSet, basename='example') - self.patterns = [ - path('', include(self.router.urls)) - ] - - generator = SchemaGenerator(title='Example API', patterns=self.patterns) - schema = generator.get_schema() - - expected = coreapi.Document( - url='', - title='Example API', - content={ - 'example': { - 'custom_read': coreapi.Link( - url='/example/{id}/custom_read/', - action='get', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - ] - ), - 'custom_mixed_update': { - 'update': coreapi.Link( - url='/example/{id}/custom_mixed_update/', - action='put', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - ] - ), - 'partial_update': coreapi.Link( - url='/example/{id}/custom_mixed_update/', - action='patch', - fields=[ - coreapi.Field('id', required=True, location='path', schema=coreschema.String()), - ] - ) - } - } - } - ) - assert schema == expected - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class Test4605Regression(TestCase): - def test_4605_regression(self): - generator = SchemaGenerator() - prefix = generator.determine_path_prefix([ - '/api/v1/items/', - '/auth/convert-token/' - ]) - assert prefix == '/' - - -class CustomViewInspector(AutoSchema): - """A dummy AutoSchema subclass""" - pass - - -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestAutoSchema(TestCase): - - def test_apiview_schema_descriptor(self): - view = APIView() - assert hasattr(view, 'schema') - assert isinstance(view.schema, AutoSchema) - - def test_set_custom_inspector_class_on_view(self): - class CustomView(APIView): - schema = CustomViewInspector() - - view = CustomView() - assert isinstance(view.schema, CustomViewInspector) - - def test_set_custom_inspector_class_via_settings(self): - with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'tests.schemas.test_coreapi.CustomViewInspector'}): - view = APIView() - assert isinstance(view.schema, CustomViewInspector) - - def test_get_link_requires_instance(self): - descriptor = APIView.schema # Accessed from class - with pytest.raises(AssertionError): - descriptor.get_link(None, None, None) # ???: Do the dummy arguments require a tighter assert? - - @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') - def test_update_fields(self): - """ - That updating fields by-name helper is correct - - Recall: `update_fields(fields, update_with)` - """ - schema = AutoSchema() - fields = [] - - # Adds a field... - fields = schema.update_fields(fields, [ - coreapi.Field( - "my_field", - required=True, - location="path", - schema=coreschema.String() - ), - ]) - - assert len(fields) == 1 - assert fields[0].name == "my_field" - - # Replaces a field... - fields = schema.update_fields(fields, [ - coreapi.Field( - "my_field", - required=False, - location="path", - schema=coreschema.String() - ), - ]) - - assert len(fields) == 1 - assert fields[0].required is False - - @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') - def test_get_manual_fields(self): - """That get_manual_fields is applied during get_link""" - - class CustomView(APIView): - schema = AutoSchema(manual_fields=[ - coreapi.Field( - "my_extra_field", - required=True, - location="path", - schema=coreschema.String() - ), - ]) - - view = CustomView() - link = view.schema.get_link('/a/url/{id}/', 'GET', '') - fields = link.fields - - assert len(fields) == 2 - assert "my_extra_field" in [f.name for f in fields] - - @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') - def test_viewset_action_with_schema(self): - class CustomViewSet(GenericViewSet): - @action(detail=True, schema=AutoSchema(manual_fields=[ - coreapi.Field( - "my_extra_field", - required=True, - location="path", - schema=coreschema.String() - ), - ])) - def extra_action(self, pk, **kwargs): - pass - - router = SimpleRouter() - router.register(r'detail', CustomViewSet, basename='detail') - - generator = SchemaGenerator() - view = generator.create_view(router.urls[0].callback, 'GET') - link = view.schema.get_link('/a/url/{id}/', 'GET', '') - fields = link.fields - - assert len(fields) == 2 - assert "my_extra_field" in [f.name for f in fields] - - @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') - def test_viewset_action_with_null_schema(self): - class CustomViewSet(GenericViewSet): - @action(detail=True, schema=None) - def extra_action(self, pk, **kwargs): - pass - - router = SimpleRouter() - router.register(r'detail', CustomViewSet, basename='detail') - - generator = SchemaGenerator() - view = generator.create_view(router.urls[0].callback, 'GET') - assert view.schema is None - - @pytest.mark.skipif(not coreapi, reason='coreapi is not installed') - def test_view_with_manual_schema(self): - - path = '/example' - method = 'get' - base_url = None - - fields = [ - coreapi.Field( - "first_field", - required=True, - location="path", - schema=coreschema.String() - ), - coreapi.Field( - "second_field", - required=True, - location="path", - schema=coreschema.String() - ), - coreapi.Field( - "third_field", - required=True, - location="path", - schema=coreschema.String() - ), - ] - description = "A test endpoint" - - class CustomView(APIView): - """ - ManualSchema takes list of fields for endpoint. - - Provides url and action, which are always dynamic - """ - schema = ManualSchema(fields, description) - - expected = coreapi.Link( - url=path, - action=method, - fields=fields, - description=description - ) - - view = CustomView() - link = view.schema.get_link(path, method, base_url) - assert link == expected - - @unittest.skipUnless(coreschema, 'coreschema is not installed') - def test_field_to_schema(self): - label = 'Test label' - help_text = 'This is a helpful test text' - - cases = [ - # tuples are ([field], [expected schema]) - # TODO: Add remaining cases - ( - serializers.BooleanField(label=label, help_text=help_text), - coreschema.Boolean(title=label, description=help_text) - ), - ( - serializers.DecimalField(1000, 1000, label=label, help_text=help_text), - coreschema.Number(title=label, description=help_text) - ), - ( - serializers.FloatField(label=label, help_text=help_text), - coreschema.Number(title=label, description=help_text) - ), - ( - serializers.IntegerField(label=label, help_text=help_text), - coreschema.Integer(title=label, description=help_text) - ), - ( - serializers.DateField(label=label, help_text=help_text), - coreschema.String(title=label, description=help_text, format='date') - ), - ( - serializers.DateTimeField(label=label, help_text=help_text), - coreschema.String(title=label, description=help_text, format='date-time') - ), - ( - serializers.JSONField(label=label, help_text=help_text), - coreschema.Object(title=label, description=help_text) - ), - ] - - for case in cases: - self.assertEqual(field_to_schema(case[0]), case[1]) - - -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -def test_docstring_is_not_stripped_by_get_description(): - class ExampleDocstringAPIView(APIView): - """ - === title - - * item a - * item a-a - * item a-b - * item b - - - item 1 - - item 2 - - code block begin - code - code - code - code block end - - the end - """ - - def get(self, *args, **kwargs): - pass - - def post(self, request, *args, **kwargs): - pass - - view = ExampleDocstringAPIView() - schema = view.schema - descr = schema.get_description('example', 'get') - # the first and last character are '\n' correctly removed by get_description - assert descr == formatting.dedent(ExampleDocstringAPIView.__doc__[1:][:-1]) - - -# Views for SchemaGenerationExclusionTests -with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}): - class ExcludedAPIView(APIView): - schema = None - - def get(self, request, *args, **kwargs): - pass - - @api_view(['GET']) - @schema(None) - def excluded_fbv(request): - pass - - @api_view(['GET']) - def included_fbv(request): - pass - - -@unittest.skipUnless(coreapi, 'coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class SchemaGenerationExclusionTests(TestCase): - def setUp(self): - self.patterns = [ - path('excluded-cbv/', ExcludedAPIView.as_view()), - path('excluded-fbv/', excluded_fbv), - path('included-fbv/', included_fbv), - ] - - def test_schema_generator_excludes_correctly(self): - """Schema should not include excluded views""" - generator = SchemaGenerator(title='Exclusions', patterns=self.patterns) - schema = generator.get_schema() - expected = coreapi.Document( - url='', - title='Exclusions', - content={ - 'included-fbv': { - 'list': coreapi.Link(url='/included-fbv/', action='get') - } - } - ) - - assert len(schema.data) == 1 - assert 'included-fbv' in schema.data - assert schema == expected - - def test_endpoint_enumerator_excludes_correctly(self): - """It is responsibility of EndpointEnumerator to exclude views""" - inspector = EndpointEnumerator(self.patterns) - endpoints = inspector.get_api_endpoints() - - assert len(endpoints) == 1 - path, method, callback = endpoints[0] - assert path == '/included-fbv/' - - def test_should_include_endpoint_excludes_correctly(self): - """This is the specific method that should handle the exclusion""" - inspector = EndpointEnumerator(self.patterns) - - # Not pretty. Mimics internals of EndpointEnumerator to put should_include_endpoint under test - pairs = [(inspector.get_path_from_regex(pattern.pattern.regex.pattern), pattern.callback) - for pattern in self.patterns] - - should_include = [ - inspector.should_include_endpoint(*pair) for pair in pairs - ] - - expected = [False, False, True] - - assert should_include == expected - - -class BasicModelSerializer(serializers.ModelSerializer): - class Meta: - model = BasicModel - fields = "__all__" - - -class NamingCollisionView(generics.RetrieveUpdateDestroyAPIView): - queryset = BasicModel.objects.all() - serializer_class = BasicModelSerializer - - -class BasicNamingCollisionView(generics.RetrieveAPIView): - queryset = BasicModel.objects.all() - - -class NamingCollisionViewSet(GenericViewSet): - """ - Example via: https://stackoverflow.com/questions/43778668/django-rest-framwork-occured-typeerror-link-object-does-not-support-item-ass/ - """ - permission_classes = () - - @action(detail=False) - def detail(self, request): - return {} - - @action(detail=False, url_path='detail/export') - def detail_export(self, request): - return {} - - -naming_collisions_router = SimpleRouter() -naming_collisions_router.register(r'collision', NamingCollisionViewSet, basename="collision") - - -@pytest.mark.skipif(not coreapi, reason='coreapi is not installed') -@override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) -class TestURLNamingCollisions(TestCase): - """ - Ref: https://github.com/encode/django-rest-framework/issues/4704 - """ - def test_manually_routing_nested_routes(self): - @api_view(["GET"]) - def simple_fbv(request): - pass - - patterns = [ - path('test', simple_fbv), - path('test/list/', simple_fbv), - ] - - generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) - schema = generator.get_schema() - - expected = coreapi.Document( - url='', - title='Naming Colisions', - content={ - 'test': { - 'list': { - 'list': coreapi.Link(url='/test/list/', action='get') - }, - 'list_0': coreapi.Link(url='/test', action='get') - } - } - ) - - assert expected == schema - - def _verify_cbv_links(self, loc, url, methods=None, suffixes=None): - if methods is None: - methods = ('read', 'update', 'partial_update', 'delete') - if suffixes is None: - suffixes = (None for m in methods) - - for method, suffix in zip(methods, suffixes): - if suffix is not None: - key = f'{method}_{suffix}' - else: - key = method - assert loc[key].url == url - - def test_manually_routing_generic_view(self): - patterns = [ - path('test', NamingCollisionView.as_view()), - path('test/retrieve/', NamingCollisionView.as_view()), - path('test/update/', NamingCollisionView.as_view()), - - # Fails with method names: - path('test/get/', NamingCollisionView.as_view()), - path('test/put/', NamingCollisionView.as_view()), - path('test/delete/', NamingCollisionView.as_view()), - ] - - generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) - - schema = generator.get_schema() - - self._verify_cbv_links(schema['test']['delete'], '/test/delete/') - self._verify_cbv_links(schema['test']['put'], '/test/put/') - self._verify_cbv_links(schema['test']['get'], '/test/get/') - self._verify_cbv_links(schema['test']['update'], '/test/update/') - self._verify_cbv_links(schema['test']['retrieve'], '/test/retrieve/') - self._verify_cbv_links(schema['test'], '/test', suffixes=(None, '0', None, '0')) - - def test_from_router(self): - patterns = [ - path('from-router', include(naming_collisions_router.urls)), - ] - - generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) - schema = generator.get_schema() - - # not important here - desc_0 = schema['detail']['detail_export'].description - desc_1 = schema['detail_0'].description - - expected = coreapi.Document( - url='', - title='Naming Colisions', - content={ - 'detail': { - 'detail_export': coreapi.Link( - url='/from-routercollision/detail/export/', - action='get', - description=desc_0) - }, - 'detail_0': coreapi.Link( - url='/from-routercollision/detail/', - action='get', - description=desc_1 - ) - } - ) - - assert schema == expected - - def test_url_under_same_key_not_replaced(self): - patterns = [ - path('example//', BasicNamingCollisionView.as_view()), - path('example//', BasicNamingCollisionView.as_view()), - ] - - generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) - schema = generator.get_schema() - - assert schema['example']['read'].url == '/example/{id}/' - assert schema['example']['read_0'].url == '/example/{slug}/' - - def test_url_under_same_key_not_replaced_another(self): - - @api_view(["GET"]) - def simple_fbv(request): - pass - - patterns = [ - path('test/list/', simple_fbv), - path('test//list/', simple_fbv), - ] - - generator = SchemaGenerator(title='Naming Colisions', patterns=patterns) - schema = generator.get_schema() - - assert schema['test']['list']['list'].url == '/test/list/' - assert schema['test']['list']['list_0'].url == '/test/{id}/list/' - - -def test_is_list_view_recognises_retrieve_view_subclasses(): - class TestView(generics.RetrieveAPIView): - pass - - path = '/looks/like/a/list/view/' - method = 'get' - view = TestView() - - is_list = is_list_view(path, method, view) - assert not is_list, "RetrieveAPIView subclasses should not be classified as list views." - - -def test_head_and_options_methods_are_excluded(): - """ - Regression test for #5528 - https://github.com/encode/django-rest-framework/issues/5528 - - Viewset OPTIONS actions were not being correctly excluded - - Initial cases here shown to be working as expected. - """ - - @api_view(['options', 'get']) - def fbv(request): - pass - - inspector = EndpointEnumerator() - - path = '/a/path/' - callback = fbv - - assert inspector.should_include_endpoint(path, callback) - assert inspector.get_allowed_methods(callback) == ["GET"] - - class AnAPIView(APIView): - - def get(self, request, *args, **kwargs): - pass - - def options(self, request, *args, **kwargs): - pass - - callback = AnAPIView.as_view() - - assert inspector.should_include_endpoint(path, callback) - assert inspector.get_allowed_methods(callback) == ["GET"] - - class AViewSet(ModelViewSet): - - @action(methods=['options', 'get'], detail=True) - def custom_action(self, request, pk): - pass - - callback = AViewSet.as_view({ - "options": "custom_action", - "get": "custom_action" - }) - - assert inspector.should_include_endpoint(path, callback) - assert inspector.get_allowed_methods(callback) == ["GET"] - - -class MockAPIView(APIView): - filter_backends = [filters.OrderingFilter] - - def _test(self, method): - view = self.MockAPIView() - fields = view.schema.get_filter_fields('', method) - field_names = [f.name for f in fields] - - return 'ordering' in field_names - - def test_get(self): - assert self._test('get') - - def test_GET(self): - assert self._test('GET') - - def test_put(self): - assert self._test('put') - - def test_PUT(self): - assert self._test('PUT') - - def test_patch(self): - assert self._test('patch') - - def test_PATCH(self): - assert self._test('PATCH') - - def test_delete(self): - assert self._test('delete') - - def test_DELETE(self): - assert self._test('DELETE') - - def test_post(self): - assert not self._test('post') - - def test_POST(self): - assert not self._test('POST') - - def test_foo(self): - assert not self._test('foo') - - def test_FOO(self): - assert not self._test('FOO') - - -@pytest.mark.skipif(not coreapi, reason='coreapi is not installed') -def test_schema_handles_exception(): - schema_view = get_schema_view(permission_classes=[DenyAllUsingPermissionDenied]) - request = factory.get('/') - response = schema_view(request) - response.render() - assert response.status_code == 403 - assert b"You do not have permission to perform this action." in response.content - - -@pytest.mark.skipif(not coreapi, reason='coreapi is not installed') -def test_coreapi_deprecation(): - with pytest.warns(RemovedInDRF317Warning): - SchemaGenerator() - - with pytest.warns(RemovedInDRF317Warning): - AutoSchema() - - with pytest.warns(RemovedInDRF317Warning): - ManualSchema({}) - - with pytest.warns(RemovedInDRF317Warning): - deprecated_filter = OrderingFilter() - deprecated_filter.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - deprecated_filter = BaseFilterBackend() - deprecated_filter.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - deprecated_filter = SearchFilter() - deprecated_filter.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - paginator = BasePagination() - paginator.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - paginator = PageNumberPagination() - paginator.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - paginator = LimitOffsetPagination() - paginator.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - paginator = CursorPagination() - paginator.get_schema_fields({}) - - with pytest.warns(RemovedInDRF317Warning): - is_enabled() diff --git a/tests/schemas/test_get_schema_view.py b/tests/schemas/test_get_schema_view.py deleted file mode 100644 index f582c6495..000000000 --- a/tests/schemas/test_get_schema_view.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest -from django.test import TestCase, override_settings - -from rest_framework import renderers -from rest_framework.schemas import coreapi, get_schema_view, openapi - - -class GetSchemaViewTests(TestCase): - """For the get_schema_view() helper.""" - def test_openapi(self): - schema_view = get_schema_view(title="With OpenAPI") - assert isinstance(schema_view.initkwargs['schema_generator'], openapi.SchemaGenerator) - assert renderers.OpenAPIRenderer in schema_view.cls().renderer_classes - - @pytest.mark.skipif(not coreapi.coreapi, reason='coreapi is not installed') - def test_coreapi(self): - with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}): - schema_view = get_schema_view(title="With CoreAPI") - assert isinstance(schema_view.initkwargs['schema_generator'], coreapi.SchemaGenerator) - assert renderers.CoreAPIOpenAPIRenderer in schema_view.cls().renderer_classes diff --git a/tests/schemas/test_managementcommand.py b/tests/schemas/test_managementcommand.py deleted file mode 100644 index fa1b75fbf..000000000 --- a/tests/schemas/test_managementcommand.py +++ /dev/null @@ -1,154 +0,0 @@ -import io -import os -import tempfile - -import pytest -from django.core.management import call_command -from django.test import TestCase -from django.test.utils import override_settings -from django.urls import path - -from rest_framework.compat import coreapi, uritemplate, yaml -from rest_framework.management.commands import generateschema -from rest_framework.utils import formatting, json -from rest_framework.views import APIView - - -class FooView(APIView): - def get(self, request): - pass - - -urlpatterns = [ - path('', FooView.as_view()) -] - - -class CustomSchemaGenerator: - SCHEMA = {"key": "value"} - - def __init__(self, *args, **kwargs): - pass - - def get_schema(self, **kwargs): - return self.SCHEMA - - -@override_settings(ROOT_URLCONF=__name__) -@pytest.mark.skipif(not uritemplate, reason='uritemplate is not installed') -class GenerateSchemaTests(TestCase): - """Tests for management command generateschema.""" - - def setUp(self): - self.out = io.StringIO() - - def test_command_detects_schema_generation_mode(self): - """Switching between CoreAPI & OpenAPI""" - command = generateschema.Command() - assert command.get_mode() == generateschema.OPENAPI_MODE - with override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}): - assert command.get_mode() == generateschema.COREAPI_MODE - - @pytest.mark.skipif(yaml is None, reason='PyYAML is required.') - def test_renders_default_schema_with_custom_title_url_and_description(self): - call_command('generateschema', - '--title=ExampleAPI', - '--url=http://api.example.com', - '--description=Example description', - stdout=self.out) - # Check valid YAML was output. - schema = yaml.safe_load(self.out.getvalue()) - assert schema['openapi'] == '3.0.2' - - def test_renders_openapi_json_schema(self): - call_command('generateschema', - '--format=openapi-json', - stdout=self.out) - # Check valid JSON was output. - out_json = json.loads(self.out.getvalue()) - assert out_json['openapi'] == '3.0.2' - - def test_accepts_custom_schema_generator(self): - call_command('generateschema', - f'--generator_class={__name__}.{CustomSchemaGenerator.__name__}', - stdout=self.out) - out_json = yaml.safe_load(self.out.getvalue()) - assert out_json == CustomSchemaGenerator.SCHEMA - - def test_writes_schema_to_file_on_parameter(self): - fd, path = tempfile.mkstemp() - try: - call_command('generateschema', f'--file={path}', stdout=self.out) - # nothing on stdout - assert not self.out.getvalue() - - call_command('generateschema', stdout=self.out) - expected_out = self.out.getvalue() - # file output identical to stdout output - with os.fdopen(fd) as fh: - assert expected_out and fh.read() == expected_out - finally: - os.remove(path) - - @pytest.mark.skipif(yaml is None, reason='PyYAML is required.') - @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') - @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) - def test_coreapi_renders_default_schema_with_custom_title_url_and_description(self): - expected_out = """info: - description: Example description - title: ExampleAPI - version: '' - openapi: 3.0.0 - paths: - /: - get: - operationId: list - servers: - - url: http://api.example.com/ - """ - call_command('generateschema', - '--title=ExampleAPI', - '--url=http://api.example.com', - '--description=Example description', - stdout=self.out) - - self.assertIn(formatting.dedent(expected_out), self.out.getvalue()) - - @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') - @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) - def test_coreapi_renders_openapi_json_schema(self): - expected_out = { - "openapi": "3.0.0", - "info": { - "version": "", - "title": "", - "description": "" - }, - "servers": [ - { - "url": "" - } - ], - "paths": { - "/": { - "get": { - "operationId": "list" - } - } - } - } - call_command('generateschema', - '--format=openapi-json', - stdout=self.out) - out_json = json.loads(self.out.getvalue()) - - self.assertDictEqual(out_json, expected_out) - - @pytest.mark.skipif(coreapi is None, reason='coreapi is required.') - @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema'}) - def test_renders_corejson_schema(self): - expected_out = """{"_type":"document","":{"list":{"_type":"link","url":"/","action":"get"}}}""" - call_command('generateschema', - '--format=corejson', - stdout=self.out) - self.assertIn(expected_out, self.out.getvalue()) diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py deleted file mode 100644 index a168cb466..000000000 --- a/tests/schemas/test_openapi.py +++ /dev/null @@ -1,1366 +0,0 @@ -import uuid -import warnings - -import pytest -from django.db import models -from django.test import RequestFactory, TestCase, override_settings -from django.urls import path -from django.utils.safestring import SafeString -from django.utils.translation import gettext_lazy as _ - -from rest_framework import filters, generics, pagination, routers, serializers -from rest_framework.authtoken.views import obtain_auth_token -from rest_framework.compat import uritemplate -from rest_framework.parsers import JSONParser, MultiPartParser -from rest_framework.renderers import ( - BaseRenderer, BrowsableAPIRenderer, JSONOpenAPIRenderer, JSONRenderer, - OpenAPIRenderer -) -from rest_framework.request import Request -from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator - -from ..models import BasicModel -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): - uuid1 = uuid.uuid4() - uuid2 = uuid.uuid4() - 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'}), - (serializers.ListField(child=serializers.IntegerField(max_value=4294967295)), - {'items': {'type': 'integer', 'maximum': 4294967295, 'format': 'int64'}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[('a', 'Choice A'), ('b', 'Choice B')])), - {'items': {'enum': ['a', 'b'], 'type': 'string'}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[(1, 'One'), (2, 'Two')])), - {'items': {'enum': [1, 2], 'type': 'integer'}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[(1.1, 'First'), (2.2, 'Second')])), - {'items': {'enum': [1.1, 2.2], 'type': 'number'}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[(True, 'true'), (False, 'false')])), - {'items': {'enum': [True, False], 'type': 'boolean'}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[(uuid1, 'uuid1'), (uuid2, 'uuid2')])), - {'items': {'enum': [uuid1, uuid2]}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[(1, 'One'), ('a', 'Choice A')])), - {'items': {'enum': [1, 'a']}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[ - (1, 'One'), ('a', 'Choice A'), (1.1, 'First'), (1.1, 'First'), (1, 'One'), ('a', 'Choice A'), (1, 'One') - ])), - {'items': {'enum': [1, 'a', 1.1]}, 'type': 'array'}), - (serializers.ListField(child=serializers.ChoiceField(choices=[ - (1, 'One'), (2, 'Two'), (3, 'Three'), (2, 'Two'), (3, 'Three'), (1, 'One'), - ])), - {'items': {'enum': [1, 2, 3], 'type': 'integer'}, 'type': 'array'}), - (serializers.IntegerField(min_value=2147483648), - {'type': 'integer', 'minimum': 2147483648, 'format': 'int64'}), - ] - for field, mapping in cases: - with self.subTest(field=field): - assert inspector.map_field(field) == mapping - - def test_lazy_string_field(self): - class ItemSerializer(serializers.Serializer): - text = serializers.CharField(help_text=_('lazy string')) - - inspector = AutoSchema() - - data = inspector.map_serializer(ItemSerializer()) - assert isinstance(data['properties']['text']['description'], str), "description must be str" - - def test_boolean_default_field(self): - class Serializer(serializers.Serializer): - default_true = serializers.BooleanField(default=True) - default_false = serializers.BooleanField(default=False) - without_default = serializers.BooleanField() - - inspector = AutoSchema() - - data = inspector.map_serializer(Serializer()) - assert data['properties']['default_true']['default'] is True, "default must be true" - assert data['properties']['default_false']['default'] is False, "default must be false" - assert 'default' not in data['properties']['without_default'], "default must not be defined" - - def test_custom_field_name(self): - class CustomSchema(AutoSchema): - def get_field_name(self, field): - return 'custom_' + field.field_name - - class Serializer(serializers.Serializer): - text_field = serializers.CharField() - - inspector = CustomSchema() - - data = inspector.map_serializer(Serializer()) - assert 'custom_text_field' in data['properties'] - assert 'text_field' not in data['properties'] - - def test_nullable_fields(self): - class Model(models.Model): - rw_field = models.CharField(null=True) - ro_field = models.CharField(null=True) - - class Serializer(serializers.ModelSerializer): - class Meta: - model = Model - fields = ["rw_field", "ro_field"] - read_only_fields = ["ro_field"] - - inspector = AutoSchema() - - data = inspector.map_serializer(Serializer()) - assert data['properties']['rw_field']['nullable'], "rw_field nullable must be true" - assert data['properties']['ro_field']['nullable'], "ro_field nullable must be true" - assert data['properties']['ro_field']['readOnly'], "ro_field read_only must be true" - - def test_primary_key_related_field(self): - class PrimaryKeyRelatedFieldSerializer(serializers.Serializer): - basic = serializers.PrimaryKeyRelatedField(queryset=BasicModel.objects.all()) - uuid = serializers.PrimaryKeyRelatedField(queryset=BasicModel.objects.all(), - pk_field=serializers.UUIDField()) - char = serializers.PrimaryKeyRelatedField(queryset=BasicModel.objects.all(), - pk_field=serializers.CharField()) - - serializer = PrimaryKeyRelatedFieldSerializer() - inspector = AutoSchema() - - data = inspector.map_serializer(serializer=serializer) - assert data['properties']['basic']['type'] == "integer" - assert data['properties']['uuid']['format'] == "uuid" - assert data['properties']['char']['type'] == "string" - - -@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.DocStringExampleListView, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - operation = inspector.get_operation(path, method) - assert operation == { - 'operationId': 'listDocStringExamples', - 'description': 'A description of my GET operation.', - 'parameters': [], - 'tags': ['example'], - 'responses': { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - 'type': 'array', - 'items': {}, - }, - }, - }, - }, - }, - } - - def test_path_with_id_parameter(self): - path = '/example/{id}/' - method = 'GET' - - view = create_view( - views.DocStringExampleDetailView, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - operation = inspector.get_operation(path, method) - assert operation == { - 'operationId': 'retrieveDocStringExampleDetail', - 'description': 'A description of my GET operation.', - 'parameters': [{ - 'description': '', - 'in': 'path', - 'name': 'id', - 'required': True, - 'schema': { - 'type': 'string', - }, - }], - 'tags': ['example'], - 'responses': { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - }, - }, - }, - }, - }, - } - - def test_request_body(self): - path = '/' - method = 'POST' - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - read_only = serializers.CharField(read_only=True) - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - request_body = inspector.get_request_body(path, method) - print(request_body) - assert request_body['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' - - components = inspector.get_components(path, method) - assert components['Item']['required'] == ['text'] - assert sorted(list(components['Item']['properties'].keys())) == ['read_only', 'text'] - - def test_invalid_serializer_class_name(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 - - serializer = inspector.get_serializer(path, method) - - with pytest.raises(Exception) as exc: - inspector.get_component_name(serializer) - assert "is an invalid class name for schema generation" in str(exc.value) - - def test_empty_required(self): - path = '/' - method = 'POST' - - class ItemSerializer(serializers.Serializer): - read_only = serializers.CharField(read_only=True) - write_only = serializers.CharField(write_only=True, required=False) - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Item'] - # there should be no empty 'required' property, see #6834 - assert 'required' not in component - - for response in inspector.get_responses(path, method).values(): - assert 'required' not in component - - def test_empty_required_with_patch_method(self): - path = '/' - method = 'PATCH' - - class ItemSerializer(serializers.Serializer): - read_only = serializers.CharField(read_only=True) - write_only = serializers.CharField(write_only=True, required=False) - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Item'] - # there should be no empty 'required' property, see #6834 - assert 'required' not in component - for response in inspector.get_responses(path, method).values(): - assert 'required' not in component - - def test_response_body_generation(self): - path = '/' - method = 'POST' - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - write_only = serializers.CharField(write_only=True) - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path) - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses['201']['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' - - components = inspector.get_components(path, method) - assert sorted(components['Item']['required']) == ['text', 'write_only'] - assert sorted(list(components['Item']['properties'].keys())) == ['text', 'write_only'] - assert 'description' in responses['201'] - - def test_response_body_nested_serializer(self): - path = '/' - method = 'POST' - - class NestedSerializer(serializers.Serializer): - number = serializers.IntegerField() - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - nested = NestedSerializer() - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses['201']['content']['application/json']['schema']['$ref'] == '#/components/schemas/Item' - components = inspector.get_components(path, method) - assert components['Item'] - - schema = components['Item'] - 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_response_body_partial_serializer(self): - path = '/' - method = 'GET' - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.partial = True - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses == { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - 'type': 'array', - 'items': { - '$ref': '#/components/schemas/Item' - }, - }, - }, - }, - }, - } - components = inspector.get_components(path, method) - assert components == { - 'Item': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - } - } - - def test_list_response_body_generation(self): - """Test that an array schema is returned for list views.""" - path = '/' - method = 'GET' - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses == { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - 'type': 'array', - 'items': { - '$ref': '#/components/schemas/Item' - }, - }, - }, - }, - }, - } - components = inspector.get_components(path, method) - assert components == { - 'Item': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], - } - } - - def test_paginated_list_response_body_generation(self): - """Test that pagination properties are added for a paginated list view.""" - path = '/' - method = 'GET' - - class Pagination(pagination.BasePagination): - def get_paginated_response_schema(self, schema): - return { - 'type': 'object', - 'item': schema, - } - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - pagination_class = Pagination - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses == { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - 'type': 'object', - 'item': { - 'type': 'array', - 'items': { - '$ref': '#/components/schemas/Item' - }, - }, - }, - }, - }, - }, - } - components = inspector.get_components(path, method) - assert components == { - 'Item': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], - } - } - - def test_delete_response_body_generation(self): - """Test that a view's delete method generates a proper response body schema.""" - path = '/{id}/' - method = 'DELETE' - - class View(generics.DestroyAPIView): - serializer_class = views.ExampleSerializer - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses == { - '204': { - 'description': '', - }, - } - - def test_parser_mapping(self): - """Test that view's parsers are mapped to OA media types""" - path = '/{id}/' - method = 'POST' - - class View(generics.CreateAPIView): - serializer_class = views.ExampleSerializer - parser_classes = [JSONParser, MultiPartParser] - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - request_body = inspector.get_request_body(path, method) - - assert len(request_body['content'].keys()) == 2 - assert 'multipart/form-data' in request_body['content'] - assert 'application/json' in request_body['content'] - - def test_renderer_mapping(self): - """Test that view's renderers are mapped to OA media types""" - path = '/{id}/' - method = 'GET' - - class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): - media_type = 'image/jpeg' # that's a wild API renderer - - class TextRenderer(BaseRenderer): - media_type = 'text/plain' - format = 'text' - - class View(generics.CreateAPIView): - serializer_class = views.ExampleSerializer - renderer_classes = [JSONRenderer, TextRenderer, BrowsableAPIRenderer, CustomBrowsableAPIRenderer] - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - # TODO this should be changed once the multiple response - # schema support is there - success_response = responses['200'] - - # Check that the API renderers aren't included, but custom renderers are - assert set(success_response['content']) == {'application/json', 'text/plain'} - - def test_openapi_yaml_rendering_without_aliases(self): - renderer = OpenAPIRenderer() - - reused_object = {'test': 'test'} - data = { - 'o1': reused_object, - 'o2': reused_object, - } - assert ( - renderer.render(data) == b'o1:\n test: test\no2:\n test: test\n' or - renderer.render(data) == b'o2:\n test: test\no1:\n test: test\n' # py <= 3.5 - ) - - def test_openapi_yaml_safestring_render(self): - renderer = OpenAPIRenderer() - data = {'o1': SafeString('test')} - assert renderer.render(data) == b'o1: test\n' - - def test_serializer_filefield(self): - path = '/{id}/' - method = 'POST' - - class ItemSerializer(serializers.Serializer): - attachment = serializers.FileField() - - class View(generics.CreateAPIView): - serializer_class = ItemSerializer - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Item'] - properties = component['properties'] - assert properties['attachment']['format'] == 'binary' - - def test_retrieve_response_body_generation(self): - """ - Test that a list of properties is returned for retrieve item views. - - Pagination properties should not be added as the view represents a single item. - """ - path = '/{id}/' - method = 'GET' - - class Pagination(pagination.BasePagination): - def get_paginated_response_schema(self, schema): - return { - 'type': 'object', - 'item': schema, - } - - class ItemSerializer(serializers.Serializer): - text = serializers.CharField() - - class View(generics.GenericAPIView): - serializer_class = ItemSerializer - pagination_class = Pagination - - view = create_view( - View, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - responses = inspector.get_responses(path, method) - assert responses == { - '200': { - 'description': '', - 'content': { - 'application/json': { - 'schema': { - '$ref': '#/components/schemas/Item' - }, - }, - }, - }, - } - - components = inspector.get_components(path, method) - assert components == { - 'Item': { - 'type': 'object', - 'properties': { - 'text': { - 'type': 'string', - }, - }, - 'required': ['text'], - } - } - - 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_operation_id_custom_operation_id_base(self): - path = '/' - method = 'GET' - - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema(operation_id_base="Ulysse") - inspector.view = view - - operationId = inspector.get_operation_id(path, method) - assert operationId == 'listUlysses' - - def test_operation_id_custom_name(self): - path = '/' - method = 'GET' - - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema(operation_id_base='Ulysse') - inspector.view = view - - operationId = inspector.get_operation_id(path, method) - assert operationId == 'listUlysses' - - def test_operation_id_plural(self): - path = '/' - method = 'GET' - - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema(operation_id_base='City') - inspector.view = view - - operationId = inspector.get_operation_id(path, method) - assert operationId == 'listCities' - - def test_operation_id_override_get(self): - class CustomSchema(AutoSchema): - def get_operation_id(self, path, method): - return 'myCustomOperationId' - - path = '/' - method = 'GET' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = CustomSchema() - inspector.view = view - - operationId = inspector.get_operation_id(path, method) - assert operationId == 'myCustomOperationId' - - def test_operation_id_override_base(self): - class CustomSchema(AutoSchema): - def get_operation_id_base(self, path, method, action): - return 'Item' - - path = '/' - method = 'GET' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = CustomSchema() - inspector.view = view - - operationId = inspector.get_operation_id(path, method) - assert operationId == 'listItem' - - def test_different_request_response_objects(self): - class RequestSerializer(serializers.Serializer): - text = serializers.CharField() - - class ResponseSerializer(serializers.Serializer): - text = serializers.BooleanField() - - class CustomSchema(AutoSchema): - def get_request_serializer(self, path, method): - return RequestSerializer() - - def get_response_serializer(self, path, method): - return ResponseSerializer() - - path = '/' - method = 'POST' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = CustomSchema() - inspector.view = view - - components = inspector.get_components(path, method) - assert components == { - 'Request': { - 'properties': { - 'text': { - 'type': 'string' - } - }, - 'required': ['text'], - 'type': 'object' - }, - 'Response': { - 'properties': { - 'text': { - 'type': 'boolean' - } - }, - 'required': ['text'], - 'type': 'object' - } - } - - operation = inspector.get_operation(path, method) - assert operation == { - 'operationId': 'createExample', - 'description': '', - 'parameters': [], - 'requestBody': { - 'content': { - 'application/json': { - 'schema': { - '$ref': '#/components/schemas/Request' - } - }, - 'application/x-www-form-urlencoded': { - 'schema': { - '$ref': '#/components/schemas/Request' - } - }, - 'multipart/form-data': { - 'schema': { - '$ref': '#/components/schemas/Request' - } - } - } - }, - 'responses': { - '201': { - 'content': { - 'application/json': { - 'schema': { - '$ref': '#/components/schemas/Response' - } - } - }, - 'description': '' - } - }, - 'tags': [''] - } - - 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 - - def test_duplicate_operation_id(self): - patterns = [ - path('duplicate1/', views.ExampleOperationIdDuplicate1.as_view()), - path('duplicate2/', views.ExampleOperationIdDuplicate2.as_view()), - ] - - generator = SchemaGenerator(patterns=patterns) - request = create_request('/') - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - generator.get_schema(request=request) - - assert len(w) == 1 - assert issubclass(w[-1].category, UserWarning) - print(str(w[-1].message)) - assert 'You have a duplicated operationId' in str(w[-1].message) - - def test_operation_id_viewset(self): - router = routers.SimpleRouter() - router.register('account', views.ExampleViewSet, basename="account") - urlpatterns = router.urls - - generator = SchemaGenerator(patterns=urlpatterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - print(schema) - assert schema['paths']['/account/']['get']['operationId'] == 'listExampleViewSets' - assert schema['paths']['/account/']['post']['operationId'] == 'createExampleViewSet' - assert schema['paths']['/account/{id}/']['get']['operationId'] == 'retrieveExampleViewSet' - assert schema['paths']['/account/{id}/']['put']['operationId'] == 'updateExampleViewSet' - assert schema['paths']['/account/{id}/']['patch']['operationId'] == 'partialUpdateExampleViewSet' - assert schema['paths']['/account/{id}/']['delete']['operationId'] == 'destroyExampleViewSet' - - def test_serializer_datefield(self): - path = '/' - method = 'GET' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Example'] - properties = component['properties'] - assert properties['date']['type'] == properties['datetime']['type'] == 'string' - assert properties['date']['format'] == 'date' - assert properties['datetime']['format'] == 'date-time' - - def test_serializer_hstorefield(self): - path = '/' - method = 'GET' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Example'] - properties = component['properties'] - assert properties['hstore']['type'] == 'object' - - def test_serializer_callable_default(self): - path = '/' - method = 'GET' - view = create_view( - views.ExampleGenericAPIView, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['Example'] - properties = component['properties'] - assert 'default' not in properties['uuid_field'] - - def test_serializer_validators(self): - path = '/' - method = 'GET' - view = create_view( - views.ExampleValidatedAPIView, - method, - create_request(path), - ) - inspector = AutoSchema() - inspector.view = view - - components = inspector.get_components(path, method) - component = components['ExampleValidated'] - properties = component['properties'] - - assert properties['integer']['type'] == 'integer' - assert properties['integer']['maximum'] == 99 - assert properties['integer']['minimum'] == -11 - - assert properties['string']['minLength'] == 2 - assert properties['string']['maxLength'] == 10 - - assert properties['lst']['minItems'] == 2 - assert properties['lst']['maxItems'] == 10 - - assert properties['regex']['pattern'] == r'[ABC]12{3}' - assert properties['regex']['description'] == 'must have an A, B, or C followed by 1222' - - assert properties['decimal1']['type'] == 'number' - assert properties['decimal1']['multipleOf'] == .01 - assert properties['decimal1']['maximum'] == 10000 - assert properties['decimal1']['minimum'] == -10000 - - assert properties['decimal2']['type'] == 'number' - assert properties['decimal2']['multipleOf'] == .0001 - - assert properties['decimal3'] == { - 'type': 'string', 'format': 'decimal', 'maximum': 1000000, 'minimum': -1000000, 'multipleOf': 0.01 - } - assert properties['decimal4'] == { - 'type': 'string', 'format': 'decimal', 'maximum': 1000000, 'minimum': -1000000, 'multipleOf': 0.01 - } - assert properties['decimal5'] == { - 'type': 'string', 'format': 'decimal', 'maximum': 10000, 'minimum': -10000, 'multipleOf': 0.01 - } - - assert properties['email']['type'] == 'string' - assert properties['email']['format'] == 'email' - assert properties['email']['default'] == 'foo@bar.com' - - assert properties['url']['type'] == 'string' - assert properties['url']['nullable'] is True - assert properties['url']['default'] == 'http://www.example.com' - assert '\\Z' not in properties['url']['pattern'] - - assert properties['uuid']['type'] == 'string' - assert properties['uuid']['format'] == 'uuid' - - assert properties['ip4']['type'] == 'string' - assert properties['ip4']['format'] == 'ipv4' - - assert properties['ip6']['type'] == 'string' - assert properties['ip6']['format'] == 'ipv6' - - assert properties['ip']['type'] == 'string' - assert 'format' not in properties['ip'] - - def test_overridden_tags(self): - class ExampleStringTagsViewSet(views.ExampleGenericAPIView): - schema = AutoSchema(tags=['example1', 'example2']) - - url_patterns = [ - path('test/', ExampleStringTagsViewSet.as_view()), - ] - generator = SchemaGenerator(patterns=url_patterns) - schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/test/']['get']['tags'] == ['example1', 'example2'] - - def test_overridden_get_tags_method(self): - class MySchema(AutoSchema): - def get_tags(self, path, method): - if path.endswith('/new/'): - tags = ['tag1', 'tag2'] - elif path.endswith('/old/'): - tags = ['tag2', 'tag3'] - else: - tags = ['tag4', 'tag5'] - - return tags - - class ExampleStringTagsViewSet(views.ExampleGenericViewSet): - schema = MySchema() - - router = routers.SimpleRouter() - router.register('example', ExampleStringTagsViewSet, basename="example") - generator = SchemaGenerator(patterns=router.urls) - schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/example/new/']['get']['tags'] == ['tag1', 'tag2'] - assert schema['paths']['/example/old/']['get']['tags'] == ['tag2', 'tag3'] - - def test_auto_generated_apiview_tags(self): - class RestaurantAPIView(views.ExampleGenericAPIView): - schema = AutoSchema(operation_id_base="restaurant") - pass - - class BranchAPIView(views.ExampleGenericAPIView): - pass - - url_patterns = [ - path('any-dash_underscore/', RestaurantAPIView.as_view()), - path('restaurants/branches/', BranchAPIView.as_view()) - ] - generator = SchemaGenerator(patterns=url_patterns) - schema = generator.get_schema(request=create_request('/')) - assert schema['paths']['/any-dash_underscore/']['get']['tags'] == ['any-dash-underscore'] - assert schema['paths']['/restaurants/branches/']['get']['tags'] == ['restaurants'] - - -@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 = [ - path('example/', views.ExampleListView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - generator._initialise_endpoints() - - paths = generator.get_schema()["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 = [ - path('v1/example/', views.ExampleListView.as_view()), - path('v1/example/{pk}/', views.ExampleDetailView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - generator._initialise_endpoints() - - paths = generator.get_schema()["paths"] - - assert '/v1/example/' in paths - assert '/v1/example/{id}/' in paths - - def test_mount_url_prefixed_to_paths(self): - patterns = [ - path('example/', views.ExampleListView.as_view()), - path('example/{pk}/', views.ExampleDetailView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns, url='/api') - generator._initialise_endpoints() - - paths = generator.get_schema()["paths"] - - assert '/api/example/' in paths - assert '/api/example/{id}/' in paths - - def test_schema_construction(self): - """Construction of the top level dictionary.""" - patterns = [ - path('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_schema_rendering_to_json(self): - patterns = [ - path('example/', views.ExampleGenericAPIView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - ret = JSONOpenAPIRenderer().render(schema) - - assert b'"openapi": "' in ret - assert b'"default": "0.0"' in ret - - def test_schema_rendering_to_yaml(self): - patterns = [ - path('example/', views.ExampleGenericAPIView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - ret = OpenAPIRenderer().render(schema) - assert b"openapi: " in ret - assert b"default: '0.0'" in ret - - def test_schema_rendering_timedelta_to_yaml_with_validator(self): - - patterns = [ - path('example/', views.ExampleValidatedAPIView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - ret = OpenAPIRenderer().render(schema) - assert b"openapi: " in ret - assert b"duration:\n type: string\n minimum: \'10.0\'\n" in ret - - def test_schema_with_no_paths(self): - patterns = [] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - - assert schema['paths'] == {} - - def test_schema_information(self): - """Construction of the top level dictionary.""" - patterns = [ - path('example/', views.ExampleListView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns, title='My title', version='1.2.3', description='My description') - - request = create_request('/') - schema = generator.get_schema(request=request) - - assert schema['info']['title'] == 'My title' - assert schema['info']['version'] == '1.2.3' - assert schema['info']['description'] == 'My description' - - def test_schema_information_empty(self): - """Construction of the top level dictionary.""" - patterns = [ - path('example/', views.ExampleListView.as_view()), - ] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - - assert schema['info']['title'] == '' - assert schema['info']['version'] == '' - - def test_serializer_model(self): - """Construction of the top level dictionary.""" - patterns = [ - path('example/', views.ExampleGenericAPIViewModel.as_view()), - ] - - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - - print(schema) - - assert 'components' in schema - assert 'schemas' in schema['components'] - assert 'ExampleModel' in schema['components']['schemas'] - - def test_authtoken_serializer(self): - patterns = [ - path('api-token-auth/', obtain_auth_token) - ] - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - - print(schema) - - route = schema['paths']['/api-token-auth/']['post'] - body_schema = route['requestBody']['content']['application/json']['schema'] - - assert body_schema == { - '$ref': '#/components/schemas/AuthToken' - } - assert schema['components']['schemas']['AuthToken'] == { - 'type': 'object', - 'properties': { - 'username': {'type': 'string', 'writeOnly': True}, - 'password': {'type': 'string', 'writeOnly': True}, - 'token': {'type': 'string', 'readOnly': True}, - }, - 'required': ['username', 'password'] - } - - def test_component_name(self): - patterns = [ - path('example/', views.ExampleAutoSchemaComponentName.as_view()), - ] - - generator = SchemaGenerator(patterns=patterns) - - request = create_request('/') - schema = generator.get_schema(request=request) - - print(schema) - assert 'components' in schema - assert 'schemas' in schema['components'] - assert 'Ulysses' in schema['components']['schemas'] - - def test_duplicate_component_name(self): - patterns = [ - path('duplicate1/', views.ExampleAutoSchemaDuplicate1.as_view()), - path('duplicate2/', views.ExampleAutoSchemaDuplicate2.as_view()), - ] - - generator = SchemaGenerator(patterns=patterns) - request = create_request('/') - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - schema = generator.get_schema(request=request) - - assert len(w) == 1 - assert issubclass(w[-1].category, UserWarning) - assert 'has been overridden with a different value.' in str(w[-1].message) - - assert 'components' in schema - assert 'schemas' in schema['components'] - assert 'Duplicate' in schema['components']['schemas'] - - def test_component_should_not_be_generated_for_delete_method(self): - class ExampleView(generics.DestroyAPIView): - schema = AutoSchema(operation_id_base='example') - - url_patterns = [ - path('example/', ExampleView.as_view()), - ] - generator = SchemaGenerator(patterns=url_patterns) - schema = generator.get_schema(request=create_request('/')) - assert 'components' not in schema - assert 'content' not in schema['paths']['/example/']['delete']['responses']['204'] diff --git a/tests/schemas/views.py b/tests/schemas/views.py deleted file mode 100644 index c08208bf2..000000000 --- a/tests/schemas/views.py +++ /dev/null @@ -1,250 +0,0 @@ -import uuid -from datetime import timedelta - -from django.core.validators import ( - DecimalValidator, MaxLengthValidator, MaxValueValidator, - MinLengthValidator, MinValueValidator, RegexValidator -) -from django.db import models - -from rest_framework import generics, permissions, serializers -from rest_framework.decorators import action -from rest_framework.response import Response -from rest_framework.schemas.openapi import AutoSchema -from rest_framework.views import APIView -from rest_framework.viewsets import GenericViewSet, ViewSet - - -class ExampleListView(APIView): - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - pass - - def post(self, request, *args, **kwargs): - pass - - -class ExampleDetailView(APIView): - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - pass - - -class DocStringExampleListView(APIView): - """ - get: A description of my GET operation. - post: A description of my POST operation. - """ - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - pass - - def post(self, request, *args, **kwargs): - pass - - -class DocStringExampleDetailView(APIView): - permission_classes = [permissions.IsAuthenticatedOrReadOnly] - - def get(self, *args, **kwargs): - """ - A description of my GET operation. - """ - pass - - -# Generics. -class ExampleSerializer(serializers.Serializer): - date = serializers.DateField() - datetime = serializers.DateTimeField() - duration = serializers.DurationField(default=timedelta()) - hstore = serializers.HStoreField() - uuid_field = serializers.UUIDField(default=uuid.uuid4) - - -class ExampleGenericAPIView(generics.GenericAPIView): - serializer_class = ExampleSerializer - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - -class ExampleGenericViewSet(GenericViewSet): - serializer_class = ExampleSerializer - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - @action(detail=False) - def new(self, *args, **kwargs): - pass - - @action(detail=False) - def old(self, *args, **kwargs): - pass - - -# Validators and/or equivalent Field attributes. -class ExampleValidatedSerializer(serializers.Serializer): - integer = serializers.IntegerField( - validators=( - MaxValueValidator(limit_value=99), - MinValueValidator(limit_value=-11), - ) - ) - string = serializers.CharField( - validators=( - MaxLengthValidator(limit_value=10), - MinLengthValidator(limit_value=2), - ) - ) - regex = serializers.CharField( - validators=( - RegexValidator(regex=r'[ABC]12{3}'), - ), - help_text='must have an A, B, or C followed by 1222' - ) - lst = serializers.ListField( - validators=( - MaxLengthValidator(limit_value=10), - MinLengthValidator(limit_value=2), - ) - ) - decimal1 = serializers.DecimalField(max_digits=6, decimal_places=2, coerce_to_string=False) - decimal2 = serializers.DecimalField(max_digits=5, decimal_places=0, coerce_to_string=False, - validators=(DecimalValidator(max_digits=17, decimal_places=4),)) - decimal3 = serializers.DecimalField(max_digits=8, decimal_places=2, coerce_to_string=True) - decimal4 = serializers.DecimalField(max_digits=8, decimal_places=2, coerce_to_string=True, - validators=(DecimalValidator(max_digits=17, decimal_places=4),)) - decimal5 = serializers.DecimalField(max_digits=6, decimal_places=2) - email = serializers.EmailField(default='foo@bar.com') - url = serializers.URLField(default='http://www.example.com', allow_null=True) - uuid = serializers.UUIDField() - ip4 = serializers.IPAddressField(protocol='ipv4') - ip6 = serializers.IPAddressField(protocol='ipv6') - ip = serializers.IPAddressField() - duration = serializers.DurationField( - validators=( - MinValueValidator(timedelta(seconds=10)), - ) - ) - - -class ExampleValidatedAPIView(generics.GenericAPIView): - serializer_class = ExampleValidatedSerializer - - def get(self, *args, **kwargs): - serializer = self.get_serializer(integer=33, string='hello', regex='foo', decimal1=3.55, - decimal2=5.33, email='a@b.co', - url='http://localhost', uuid=uuid.uuid4(), ip4='127.0.0.1', ip6='::1', - ip='192.168.1.1') - return Response(serializer.data) - - -# Serializer with model. -class OpenAPIExample(models.Model): - first_name = models.CharField(max_length=30) - - -class ExampleSerializerModel(serializers.Serializer): - date = serializers.DateField() - datetime = serializers.DateTimeField() - hstore = serializers.HStoreField() - uuid_field = serializers.UUIDField(default=uuid.uuid4) - - class Meta: - model = OpenAPIExample - - -class ExampleOperationIdDuplicate1(generics.GenericAPIView): - serializer_class = ExampleSerializerModel - - def get(self, *args, **kwargs): - pass - - -class ExampleOperationIdDuplicate2(generics.GenericAPIView): - serializer_class = ExampleSerializerModel - - def get(self, *args, **kwargs): - pass - - -class ExampleGenericAPIViewModel(generics.GenericAPIView): - serializer_class = ExampleSerializerModel - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - -class ExampleAutoSchemaComponentName(generics.GenericAPIView): - serializer_class = ExampleSerializerModel - schema = AutoSchema(component_name="Ulysses") - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - -class ExampleAutoSchemaDuplicate1(generics.GenericAPIView): - serializer_class = ExampleValidatedSerializer - schema = AutoSchema(component_name="Duplicate") - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - -class ExampleAutoSchemaDuplicate2(generics.GenericAPIView): - serializer_class = ExampleSerializerModel - schema = AutoSchema(component_name="Duplicate") - - def get(self, *args, **kwargs): - from datetime import datetime - now = datetime.now() - - serializer = self.get_serializer(data=now.date(), datetime=now) - return Response(serializer.data) - - -class ExampleViewSet(ViewSet): - serializer_class = ExampleSerializerModel - - def list(self, request): - pass - - def create(self, request): - pass - - def retrieve(self, request, pk=None): - pass - - def update(self, request, pk=None): - pass - - def partial_update(self, request, pk=None): - pass - - def destroy(self, request, pk=None): - pass