from __future__ import unicode_literals

import pytest
from django.test import TestCase

from rest_framework import status
from rest_framework.authentication import BasicAuthentication
from rest_framework.decorators import (
    action, api_view, authentication_classes, detail_route, list_route,
    parser_classes, permission_classes, renderer_classes, schema,
    throttle_classes
)
from rest_framework.parsers import JSONParser
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.schemas import AutoSchema
from rest_framework.test import APIRequestFactory
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView


class DecoratorTestCase(TestCase):

    def setUp(self):
        self.factory = APIRequestFactory()

    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()

    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

        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

    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

    def test_renderer_classes(self):

        @api_view(['GET'])
        @renderer_classes([JSONRenderer])
        def view(request):
            return Response({})

        request = self.factory.get('/')
        response = view(request)
        assert isinstance(response.accepted_renderer, JSONRenderer)

    def test_parser_classes(self):

        @api_view(['GET'])
        @parser_classes([JSONParser])
        def view(request):
            assert len(request.parsers) == 1
            assert isinstance(request.parsers[0], JSONParser)
            return Response({})

        request = self.factory.get('/')
        view(request)

    def test_authentication_classes(self):

        @api_view(['GET'])
        @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):

        @api_view(['GET'])
        @permission_classes([IsAuthenticated])
        def view(request):
            return Response({})

        request = self.factory.get('/')
        response = view(request)
        assert response.status_code == status.HTTP_403_FORBIDDEN

    def test_throttle_classes(self):
        class OncePerDayUserThrottle(UserRateThrottle):
            rate = '1/day'

        @api_view(['GET'])
        @throttle_classes([OncePerDayUserThrottle])
        def view(request):
            return Response({})

        request = self.factory.get('/')
        response = view(request)
        assert response.status_code == status.HTTP_200_OK

        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.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_view_name_kwargs(self):
        """
        'name' and 'suffix' are mutually exclusive kwargs used for generating
        a view's display name.
        """
        # by default, generate name from method
        @action(detail=True)
        def test_action(request):
            raise NotImplementedError

        assert test_action.kwargs == {
            'description': None,
            'name': 'Test action',
        }

        # name kwarg supersedes name generation
        @action(detail=True, name='test name')
        def test_action(request):
            raise NotImplementedError

        assert test_action.kwargs == {
            'description': None,
            'name': 'test name',
        }

        # suffix kwarg supersedes name generation
        @action(detail=True, suffix='Suffix')
        def test_action(request):
            raise NotImplementedError

        assert test_action.kwargs == {
            'description': None,
            'suffix': 'Suffix',
        }

        # name + suffix is a conflict.
        with pytest.raises(TypeError) as excinfo:
            action(detail=True, name='test name', suffix='Suffix')

        assert str(excinfo.value) == "`name` and `suffix` are mutually exclusive arguments."

    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', '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(DeprecationWarning) as record:
            @detail_route()
            def view(request):
                raise NotImplementedError

        assert len(record) == 1
        assert str(record[0].message) == (
            "`detail_route` is deprecated 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(DeprecationWarning) as record:
            @list_route()
            def view(request):
                raise NotImplementedError

        assert len(record) == 1
        assert str(record[0].message) == (
            "`list_route` is deprecated 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(DeprecationWarning):
            @list_route(url_path='foo_bar')
            def view(request):
                raise NotImplementedError

        assert view.url_path == 'foo_bar'
        assert view.url_name == 'foo-bar'

[((26, 23, 26, 42), 'rest_framework.test.APIRequestFactory', 'APIRequestFactory', ({}, {}), '()', False, 'from rest_framework.test import APIRequestFactory\n'), ((30, 15, 30, 82), 'rest_framework.views.APIView.finalize_response', 'APIView.finalize_response', ({(30, 41, 30, 45): 'self', (30, 47, 30, 54): 'request', (30, 56, 30, 64): 'response', (30, 66, 30, 71): '*args'}, {}), '(self, request, response, *args, **kwargs)', False, 'from rest_framework.views import APIView\n'), ((56, 9, 56, 26), 'rest_framework.decorators.api_view', 'api_view', ({(56, 18, 56, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((70, 9, 70, 33), 'rest_framework.decorators.api_view', 'api_view', ({(70, 18, 70, 32): "['GET', 'PUT']"}, {}), "(['GET', 'PUT'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((84, 9, 84, 35), 'rest_framework.decorators.api_view', 'api_view', ({(84, 18, 84, 34): "['GET', 'PATCH']"}, {}), "(['GET', 'PATCH'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((98, 9, 98, 26), 'rest_framework.decorators.api_view', 'api_view', ({(98, 18, 98, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((99, 9, 99, 41), 'rest_framework.decorators.renderer_classes', 'renderer_classes', ({(99, 26, 99, 40): '[JSONRenderer]'}, {}), '([JSONRenderer])', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((109, 9, 109, 26), 'rest_framework.decorators.api_view', 'api_view', ({(109, 18, 109, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((110, 9, 110, 37), 'rest_framework.decorators.parser_classes', 'parser_classes', ({(110, 24, 110, 36): '[JSONParser]'}, {}), '([JSONParser])', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((121, 9, 121, 26), 'rest_framework.decorators.api_view', 'api_view', ({(121, 18, 121, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((122, 9, 122, 54), 'rest_framework.decorators.authentication_classes', 'authentication_classes', ({(122, 32, 122, 53): '[BasicAuthentication]'}, {}), '([BasicAuthentication])', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((133, 9, 133, 26), 'rest_framework.decorators.api_view', 'api_view', ({(133, 18, 133, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((134, 9, 134, 46), 'rest_framework.decorators.permission_classes', 'permission_classes', ({(134, 28, 134, 45): '[IsAuthenticated]'}, {}), '([IsAuthenticated])', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((146, 9, 146, 26), 'rest_framework.decorators.api_view', 'api_view', ({(146, 18, 146, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((147, 9, 147, 51), 'rest_framework.decorators.throttle_classes', 'throttle_classes', ({(147, 26, 147, 50): '[OncePerDayUserThrottle]'}, {}), '([OncePerDayUserThrottle])', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((165, 9, 165, 26), 'rest_framework.decorators.api_view', 'api_view', ({(165, 18, 165, 25): "['GET']"}, {}), "(['GET'])", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((176, 9, 176, 28), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((199, 9, 199, 41), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((221, 9, 221, 28), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((231, 9, 231, 46), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((241, 9, 241, 45), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((257, 9, 257, 29), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((270, 9, 270, 28), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((281, 9, 281, 28), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((39, 19, 39, 29), 'rest_framework.response.Response', 'Response', ({}, {}), '()', False, 'from rest_framework.response import Response\n'), ((50, 13, 50, 28), 'rest_framework.decorators.api_view', 'api_view', ({(50, 22, 50, 27): '"""GET"""'}, {}), "('GET')", False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((58, 19, 58, 31), 'rest_framework.response.Response', 'Response', ({(58, 28, 58, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((72, 19, 72, 31), 'rest_framework.response.Response', 'Response', ({(72, 28, 72, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((86, 19, 86, 31), 'rest_framework.response.Response', 'Response', ({(86, 28, 86, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((101, 19, 101, 31), 'rest_framework.response.Response', 'Response', ({(101, 28, 101, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((114, 19, 114, 31), 'rest_framework.response.Response', 'Response', ({(114, 28, 114, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((126, 19, 126, 31), 'rest_framework.response.Response', 'Response', ({(126, 28, 126, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((136, 19, 136, 31), 'rest_framework.response.Response', 'Response', ({(136, 28, 136, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((149, 19, 149, 31), 'rest_framework.response.Response', 'Response', ({(149, 28, 149, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((168, 19, 168, 31), 'rest_framework.response.Response', 'Response', ({(168, 28, 168, 30): '{}'}, {}), '({})', False, 'from rest_framework.response import Response\n'), ((190, 13, 190, 42), 'pytest.raises', 'pytest.raises', ({(190, 27, 190, 41): 'AssertionError'}, {}), '(AssertionError)', False, 'import pytest\n'), ((191, 13, 191, 21), 'rest_framework.decorators.action', 'action', ({}, {}), '()', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((251, 13, 251, 37), 'pytest.raises', 'pytest.raises', ({(251, 27, 251, 36): 'TypeError'}, {}), '(TypeError)', False, 'import pytest\n'), ((252, 12, 252, 66), 'rest_framework.decorators.action', 'action', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((293, 13, 293, 45), 'pytest.warns', 'pytest.warns', ({(293, 26, 293, 44): 'DeprecationWarning'}, {}), '(DeprecationWarning)', False, 'import pytest\n'), ((294, 13, 294, 27), 'rest_framework.decorators.detail_route', 'detail_route', ({}, {}), '()', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((306, 13, 306, 45), 'pytest.warns', 'pytest.warns', ({(306, 26, 306, 44): 'DeprecationWarning'}, {}), '(DeprecationWarning)', False, 'import pytest\n'), ((307, 13, 307, 25), 'rest_framework.decorators.list_route', 'list_route', ({}, {}), '()', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((320, 13, 320, 45), 'pytest.warns', 'pytest.warns', ({(320, 26, 320, 44): 'DeprecationWarning'}, {}), '(DeprecationWarning)', False, 'import pytest\n'), ((321, 13, 321, 43), 'rest_framework.decorators.list_route', 'list_route', (), '', False, 'from rest_framework.decorators import action, api_view, authentication_classes, detail_route, list_route, parser_classes, permission_classes, renderer_classes, schema, throttle_classes\n'), ((52, 23, 52, 33), 'rest_framework.response.Response', 'Response', ({}, {}), '()', False, 'from rest_framework.response import Response\n')]