Search code examples
python-3.xdjangounit-testingdjango-2.2

Django unittest handle PermissionDenied


As docs says https://docs.djangoproject.com/en/2.2/topics/testing/tools/#exceptions;

The only exceptions that are not visible to the test client are Http404, PermissionDenied, SystemExit, and SuspiciousOperation. Django catches these exceptions internally and converts them into the appropriate HTTP response codes. In these cases, you can check response.status_code in your test.

I handle a custom a login mixin for raising an error to user logged in but has no access to that page.

class LoginQuizMarkerMixin(LoginRequiredMixin):

    def dispatch(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            if not self.request.user.has_perm('quizzes.view_sittings'):
                raise PermissionDenied("You don't have any access to this page.")
        return super().dispatch(*args, **kwargs)

I my unitest, it working fine.

class TestQuestionMarking(TestCase):

    def setUp(self):
        self.c1 = Category.objects.create_category(name='elderberries')
        self.student = User.objects.create_user(username='student',
                                                email='[email protected]',
                                                password='top_secret')
        self.teacher = User.objects.create_user(username='teacher',
                                                email='[email protected]',
                                                password='use_d@_force')
        self.teacher.user_permissions.add(Permission.objects.get(codename='view_sittings'))

        ....


    def test_paper_marking_list_view(self):
        # should be 302 (redirection)
        marking_url = reverse('quizzes:quiz_marking')
        response = self.client.get(marking_url)
        print('response1', response)

        # should be 403 (permission denied)
        self.client.login(username='student', password='top_secret')
        response = self.client.get(marking_url)
        print('response2', response)

        # should be 200 (allowed)
        self.client.login(username='teacher', password='use_d@_force')
        response = self.client.get(marking_url)
        print('response3', response)

But for somehow, it the error of PermissionDenied it showed an error like this:

docker-compose -f local.yml run --rm django python manage.py test labs.tests.quizzes.test_views.TestQuestionMarking.test_paper_marking_list_view


response1 <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/accounts/login/?next=/quizzes/marking/">


WARNING 2021-02-04 00:28:44,270 log 1 139681767683904 [log.py:222] Forbidden (Permission denied): /quizzes/marking/
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/usr/local/lib/python3.8/contextlib.py", line 75, in inner
    return func(*args, **kwds)
  File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 71, in view
    return self.dispatch(request, *args, **kwargs)
  File "/app/my_app/quizzes/views.py", line 37, in dispatch
    raise PermissionDenied("You don't have any access to this page.")
django.core.exceptions.PermissionDenied: You don't have any access to this page.
response2 <HttpResponseForbidden status_code=403, "text/html; charset=utf-8">


response3 <TemplateResponse status_code=200, "text/html; charset=utf-8">

And what I expected is still capured as an error, not showing up like above. And should be like this below:

response1 <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/accounts/login/?next=/quizzes/marking/">

response2 <HttpResponseForbidden status_code=403, "text/html; charset=utf-8">

response3 <TemplateResponse status_code=200, "text/html; charset=utf-8">

But, in another test when I try to change it with raise Http404, it worked fine:

class LoginQuizMarkerMixin(LoginRequiredMixin):

    def dispatch(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            if not self.request.user.has_perm('quizzes.view_sittings'):
                # raise PermissionDenied("You don't have any access to this page.")
                raise Http404
        return super().dispatch(*args, **kwargs)

The console output:

response1 <HttpResponseRedirect status_code=302, "text/html; charset=utf-8", url="/accounts/login/?next=/quizzes/marking/">
WARNING 2021-02-04 13:39:05,368 log 1 139683990402880 [log.py:222] Not Found: /quizzes/marking/
response2 <HttpResponseNotFound status_code=404, "text/html; charset=utf-8">
response3 <TemplateResponse status_code=200, "text/html; charset=utf-8">

Solution

  • I fixed this by disabling my logging inside settings for test mode.

    docker-compose -f local.yml run --rm django python manage.py test --settings=config.settings.test
    

    and inside my config/settings/test.py;

    LOGGING = {}
    

    Related issue: How can I disable logging while running unit tests in Python Django?