sorryMike
sorryMike

Reputation: 789

django-rest testing views with custom authentication

I try to test view that has custom authentication, mainly because the main auth is based on external login-logout system, utilizing Redis as db for storing sessions.

Auth class is checking session id from the request, whether it is the same as in Redis - if yes, succeed.

My custom authentication.py looks like:

from django.utils.six import BytesIO

from rest_framework import authentication
from rest_framework import exceptions

from rest_framework.parsers import JSONParser

import redis


class RedisAuthentication(authentication.BaseAuthentication):
    def authenticate(self, request):

    print(request.META)
    token = request.META['HTTP_X_AUTH_TOKEN']
    redis_host = "REDIS_IP_ADRESS"
    redis_db = redis.StrictRedis(host=redis_host)
    user_data = redis_db.get("user_feature:{}".format(token))
    if user_data is None:
        raise exceptions.AuthenticationFailed('No such user or session expired')

    try:
        stream = BytesIO(user_data)  # Decode byte type
        data = JSONParser(stream)  # Parse bytes class and return dict
        current_user_id = data['currentUserId']
        request.session['user_id'] = current_user_id
    except Exception as e:
        print(e)

    return (user_data, None)

and my views.py looks like:

@api_view(['GET', 'POST'])
@authentication_classes((RedisAuthentication, ))
def task_list(request):
    if request.method == 'GET':
        paginator = PageNumberPagination()
        task_list = Task.objects.all()
        result_page = paginator.paginate_queryset(task_list, request)
        serializer = TaskSerializer(result_page, many=True)
        return paginator.get_paginated_response(serializer.data)

    elif request.method == 'POST':
        serializer = PostTaskSerializer(data=request.data)
        if serializer.is_valid():
            user_id = request.session.get('user_id')
            serializer.save(owner_id=user_id)
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Manual tests pass, but my current pytests failed after adding authentication.py, and have no clue how I can fix it properly - tried with forcing auth, but no succeed. I'm thinking that one of solution will be use fakeredis for simulate real redis. Question is, how that kind of test should looks like?

Example of test you could find here:

@pytest.mark.webtest
class TestListView(TestCase):
    def setUp(self):
        self.client = APIClient()
    def test_view_url_accessible_by_name(self):
        response = self.client.get(
            reverse('task_list')
        )
        assert response.status_code == status.HTTP_200_OK

@pytest.mark.webtest
class TestCreateTask(TestCase):
    def setUp(self):
        self.client = APIClient()
        self.user = User.objects.create_user(username='admin', email='xx', password='xx')
    def test_create(self):
        data = {some_data}
        self.client.login(username='xx', password='xx')
        response = self.client.post(
            reverse('task_list'),
            data,
            format='json')
        assert response.status_code == status.HTTP_201_CREATED
        self.client.logout()

Thanks in advance for any help!

Upvotes: 2

Views: 1856

Answers (1)

sorryMike
sorryMike

Reputation: 789

I managed to mock whole redis auth using mock.patch decorator - https://docs.python.org/3.5/library/unittest.mock-examples.html#patch-decorators.

When you put import patch to mock.patch decorator, do not insert absolute module path where redis code is stored, but insert the path where redis code was imported as a module and used.

My test looks like that now:

@mock.patch('api.views.RedisAuthentication.authenticate')
def test_view_url_accessible_by_name(self, mock_redis_auth):

    data = {"foo": 1, "currentUserId": 2, "bar": 3}
    mock_redis_auth.return_value = (data, None)

    response = self.client.get(
        reverse('task_list'),
        HTTP_X_AUTH_TOKEN='foo'
    )
    assert response.status_code == status.HTTP_200_OK

Upvotes: 1

Related Questions