django-rest-framework/tests/test_decorators.py

287 lines
9.1 KiB
Python
Raw Normal View History

from __future__ import unicode_literals
2015-06-25 23:55:51 +03:00
import pytest
2012-09-14 19:07:07 +04:00
from django.test import TestCase
2015-06-25 23:55:51 +03:00
2012-09-27 00:47:19 +04:00
from rest_framework import status
2013-06-28 20:17:39 +04:00
from rest_framework.authentication import BasicAuthentication
2015-06-25 23:55:51 +03:00
from rest_framework.decorators import (
action, api_view, authentication_classes, detail_route, list_route,
parser_classes, permission_classes, renderer_classes, schema,
throttle_classes
2015-06-25 23:55:51 +03:00
)
2013-06-28 20:17:39 +04:00
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
2015-06-25 23:55:51 +03:00
from rest_framework.response import Response
from rest_framework.schemas import AutoSchema
2013-06-28 20:17:39 +04:00
from rest_framework.test import APIRequestFactory
2012-09-27 00:47:19 +04:00
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView
2012-09-14 19:07:07 +04:00
class DecoratorTestCase(TestCase):
def setUp(self):
2013-06-28 20:17:39 +04:00
self.factory = APIRequestFactory()
2012-09-14 19:07:07 +04:00
def _finalize_response(self, request, response, *args, **kwargs):
response.request = request
return APIView.finalize_response(self, request, response, *args, **kwargs)
def test_api_view_incorrect(self):
"""
If @api_view is not applied correct, we should raise an assertion.
"""
@api_view
def view(request):
return Response()
request = self.factory.get('/')
self.assertRaises(AssertionError, view, request)
def test_api_view_incorrect_arguments(self):
"""
If @api_view is missing arguments, we should raise an assertion.
"""
with self.assertRaises(AssertionError):
@api_view('GET')
def view(request):
return Response()
2012-09-14 19:07:07 +04:00
def test_calling_method(self):
@api_view(['GET'])
def view(request):
return Response({})
request = self.factory.get('/')
response = view(request)
assert response.status_code == status.HTTP_200_OK
2012-09-14 19:07:07 +04:00
request = self.factory.post('/')
response = view(request)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_calling_put_method(self):
@api_view(['GET', 'PUT'])
def view(request):
return Response({})
request = self.factory.put('/')
response = view(request)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
2012-09-14 19:07:07 +04:00
def test_calling_patch_method(self):
@api_view(['GET', 'PATCH'])
def view(request):
return Response({})
request = self.factory.patch('/')
response = view(request)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
2012-09-14 19:07:07 +04:00
def test_renderer_classes(self):
@api_view(['GET'])
@renderer_classes([JSONRenderer])
2012-09-14 19:07:07 +04:00
def view(request):
return Response({})
request = self.factory.get('/')
response = view(request)
assert isinstance(response.accepted_renderer, JSONRenderer)
2012-09-14 19:07:07 +04:00
def test_parser_classes(self):
@api_view(['GET'])
@parser_classes([JSONParser])
2012-09-14 19:07:07 +04:00
def view(request):
assert len(request.parsers) == 1
assert isinstance(request.parsers[0], JSONParser)
2012-09-14 19:07:07 +04:00
return Response({})
request = self.factory.get('/')
view(request)
2012-09-14 19:07:07 +04:00
def test_authentication_classes(self):
@api_view(['GET'])
2012-09-14 19:07:07 +04:00
@authentication_classes([BasicAuthentication])
def view(request):
assert len(request.authenticators) == 1
assert isinstance(request.authenticators[0], BasicAuthentication)
return Response({})
request = self.factory.get('/')
view(request)
def test_permission_classes(self):
2012-09-14 19:07:07 +04:00
@api_view(['GET'])
@permission_classes([IsAuthenticated])
2012-09-14 19:07:07 +04:00
def view(request):
return Response({})
request = self.factory.get('/')
2012-09-27 00:47:19 +04:00
response = view(request)
assert response.status_code == status.HTTP_403_FORBIDDEN
2012-09-14 19:07:07 +04:00
2012-09-27 00:47:19 +04:00
def test_throttle_classes(self):
class OncePerDayUserThrottle(UserRateThrottle):
rate = '1/day'
2012-09-14 19:07:07 +04:00
2012-09-27 00:47:19 +04:00
@api_view(['GET'])
@throttle_classes([OncePerDayUserThrottle])
def view(request):
return Response({})
2012-09-14 19:07:07 +04:00
2012-09-27 00:47:19 +04:00
request = self.factory.get('/')
response = view(request)
assert response.status_code == status.HTTP_200_OK
2012-09-14 19:07:07 +04:00
2012-09-27 00:47:19 +04:00
response = view(request)
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
def test_schema(self):
"""
Checks CustomSchema class is set on view
"""
class CustomSchema(AutoSchema):
pass
@api_view(['GET'])
@schema(CustomSchema())
def view(request):
return Response({})
assert isinstance(view.cls.schema, CustomSchema)
class ActionDecoratorTestCase(TestCase):
def test_defaults(self):
@action(detail=True)
def test_action(request):
"""Description"""
assert test_action.mapping == {'get': 'test_action'}
assert test_action.detail is True
assert test_action.name == 'Test action'
assert test_action.url_path == 'test_action'
assert test_action.url_name == 'test-action'
assert test_action.kwargs == {
'name': 'Test action',
'description': 'Description',
}
def test_detail_required(self):
with pytest.raises(AssertionError) as excinfo:
@action()
def test_action(request):
raise NotImplementedError
assert str(excinfo.value) == "@action() missing required argument: 'detail'"
def test_method_mapping_http_methods(self):
# All HTTP methods should be mappable
@action(detail=False, methods=[])
def test_action():
raise NotImplementedError
for name in APIView.http_method_names:
def method():
raise NotImplementedError
# Python 2.x compatibility - cast __name__ to str
method.__name__ = str(name)
getattr(test_action.mapping, name)(method)
# ensure the mapping returns the correct method name
for name in APIView.http_method_names:
assert test_action.mapping[name] == name
def test_method_mapping(self):
@action(detail=False)
def test_action(request):
raise NotImplementedError
@test_action.mapping.post
def test_action_post(request):
raise NotImplementedError
# The secondary handler methods should not have the action attributes
for name in ['mapping', 'detail', 'name', 'url_path', 'url_name', 'kwargs']:
assert hasattr(test_action, name) and not hasattr(test_action_post, name)
def test_method_mapping_already_mapped(self):
@action(detail=True)
def test_action(request):
raise NotImplementedError
msg = "Method 'get' has already been mapped to '.test_action'."
with self.assertRaisesMessage(AssertionError, msg):
@test_action.mapping.get
def test_action_get(request):
raise NotImplementedError
def test_method_mapping_overwrite(self):
@action(detail=True)
def test_action():
raise NotImplementedError
msg = ("Method mapping does not behave like the property decorator. You "
"cannot use the same method name for each mapping declaration.")
with self.assertRaisesMessage(AssertionError, msg):
@test_action.mapping.post
def test_action():
raise NotImplementedError
def test_detail_route_deprecation(self):
with pytest.warns(PendingDeprecationWarning) as record:
@detail_route()
def view(request):
raise NotImplementedError
assert len(record) == 1
assert str(record[0].message) == (
"`detail_route` is pending deprecation and will be removed in "
"3.10 in favor of `action`, which accepts a `detail` bool. Use "
"`@action(detail=True)` instead."
)
def test_list_route_deprecation(self):
with pytest.warns(PendingDeprecationWarning) as record:
@list_route()
def view(request):
raise NotImplementedError
assert len(record) == 1
assert str(record[0].message) == (
"`list_route` is pending deprecation and will be removed in "
"3.10 in favor of `action`, which accepts a `detail` bool. Use "
"`@action(detail=False)` instead."
)
def test_route_url_name_from_path(self):
# pre-3.8 behavior was to base the `url_name` off of the `url_path`
with pytest.warns(PendingDeprecationWarning):
@list_route(url_path='foo_bar')
def view(request):
raise NotImplementedError
assert view.url_path == 'foo_bar'
assert view.url_name == 'foo-bar'