After struggling mightily with this issue, I've come asking for a bit of help. I'm writing a test for a Django Rest Framework view, testing whether or not I can access the data whilst authenticated, and not. However, even when I'm authenticated, I still get 401 UNAUTHORIZED every time. Here's my tests:
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory, APIClient
from apps.core import models, admin
from apps.rest import views
class TestAPIViews(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.client = APIClient()
self.user = User.objects.create_user('testuser', email='testuser#test.com', password='testing')
self.user.save()
token = Token.objects.create(user=self.user)
token.save()
def _require_login(self):
self.client.login(username='testuser', password='testing')
def test_ListAccounts_not_authenticated(self):
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 401,
'Expected Response Code 401, received {0} instead.'.format(response.status_code))
def test_ListAccounts_authenticated(self):
self.client._require_login()
print(self.user.is_authenticated()) # returns True
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 200,
'Expected Response Code 200, received {0} instead.'.format(response.status_code))
And here is the code for my DRF View:
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.db.models import Q
from apps.core import serializers, models
from apps.rest.permissions import IsAccountOwner
from rest_framework.views import APIView
from rest_framework import status, authentication, generics
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.core import serializers, models
'''
Auth Mixin for Account Admin Access
'''
class AuthMixin:
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication)
permission_classes = (IsAuthenticated,)
class GenericList(AuthMixin, generics.ListAPIView):
def get_queryset(self):
# Stubbed out - doing nothing special right now
qs = self.model.objects.filter()
return qs
class ListAccounts(GenericList):
model = models.Account
serializer_class = serializers.AccountSerializer
As one can see, I'm calling login in the test_ListAccounts_authenticated, and then printing out whether or not I'm authenticated (Which returns True), but I get a 401 UNAUTHORIZED Error no matter what. Anything I'm missing? Thanks in advance.
Instead of calling self.factory.get(), call self.client.get().
I'm guessing self.client.login has no effect on self.factory.
Related
How can I verify the incoming webhook from Shopify? Shopify provides a python implementation (of Flask), but how can I do it in Django/DRF?
Set these two variables in the settings.py file
# settings.py
SHOPIFY_HMAC_HEADER = "HTTP_X_SHOPIFY_HMAC_SHA256"
SHOPIFY_API_SECRET = "5f6b6_my_secret"
Then, create a verify webhook function that accepts the Django request as it's parameter
# utils.py
import base64
import hashlib
import hmac
from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest
def verify_shopify_webhook(request: WSGIRequest):
shopify_hmac_header = request.META.get(settings.SHOPIFY_HMAC_HEADER)
encoded_secret = settings.SHOPIFY_API_SECRET.encode("utf-8")
digest = hmac.new(
encoded_secret,
request.body,
digestmod=hashlib.sha256,
).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, shopify_hmac_header.encode("utf-8"))
Then, create a view that accepts the incoming webhook and use the verify_shopify_webhook(...) function to verify the request.
# views.py
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .utils import verify_shopify_webhook
#method_decorator(csrf_exempt, name="dispatch")
class ShopifyWebhookView(View):
def post(self, request, *args, **kwargs):
verified = verify_shopify_webhook(request=request)
return HttpResponse(status=200 if verified else 403)
If you're using Django REST Framework, you can also use APIView as
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .utils import verify_shopify_webhook
class ShopifyWebhookView(APIView):
def post(self, request, *args, **kwargs):
verified = verify_shopify_webhook(request=request)
return Response(status=200 if verified else 403)
I am trying to write a test which goes through the signup/login workflow, and then attempts to change the status of a user, which requires them to be logged in. I verified that the first 2 POST requests work (the user is indeed created and then gets a valid auth token after logging in), however I cannot seem to pass in said token in the headers for the 3rd and final POST request. I also checked that the auth_headers variable is indeed set with the correct token, but I keep getting back a 401 status code.
Thanks in advance!
tests.py
from email.headerregistry import ContentTypeHeader
from urllib import request
from wsgiref import headers
from django.http import HttpRequest
from django.test import TestCase, Client
from rest_framework import status
from rest_framework.test import APITestCase
from django.urls import reverse
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
from profiles_api.serializers import UserProfileSerializer
from django.contrib.auth import get_user_model
from profiles_api.views import UserLoginApiView
client = Client()
User = get_user_model()
class MyTestCase(APITestCase,UserLoginApiView):
def test_add_status_to_profile(self):
response = self.client.post("/api/profile/", data={
'email':"John#gmail.com",
'name':'Pavle',
'password':'password'
})
response = self.client.post("/api/login/", data={
'username':"John#gmail.com",
'password':'password'
})
auth_headers = {
'Authorization': 'Bearer ' + response.json()['token']
}
response = self.client.post("/api/feed/", content_type='application/json', data={
'status_text':'Hello world!'
}, **auth_headers)
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
I need to restrict access to the APIs I have defined in my view. Here is my views.py:
rom rest_framework import generics
from rest_framework import permissions
from .serializers import LocationSerializer, PartSerializer, PartLocationSerializer, SiteSerializer
from .models import Location, Part, PartLocation, Site, SPIUser
class SPIPermission(permissions.BasePermission):
"""
blah blah blah ...
"""
def has_permission(self, request, view):
try:
username = request.user.username
SPIUser.objects.get(username=username)
except SPIUser.DoesNotExist:
return False
if not request.user.is_authenticated:
return False
return True
class LocationList(generics.ListCreateAPIView):
# using get_queryset().order_by('id') prevents UnorderedObjectListWarning
queryset = Location.objects.get_queryset().order_by('id')
serializer_class = LocationSerializer
permission_classes = (SPIPermission,)
I want to demonstrate in my unit tests that your have to be an SPIUser to be able to access these api endpoints so I write a simple unit test like so:
from .models import Location, Part, PartLocation, Site, SPIUser
from .urls import urlpatterns
from my.APITestCase import RemoteAuthenticatedTest
from django.db.models import ProtectedError
from django.test import TransactionTestCase
from django.urls import reverse
from rest_framework import status
import django.db.utils
import os
class ViewTestCases(RemoteAuthenticatedTest):
def test_spi_permission(self):
url = reverse('spi:locationlist')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
SPIUser.objects.create(username=self.username)
response = self.client.get(url)
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
This test fails with the this error message:
Failure
Traceback (most recent call last):
File "/apps/man/apman/spi/tests.py", line 21, in test_spi_permission
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
AssertionError: 403 == 403
I noticed that the line in has_permission ...
username = request.user.username
... always sets the username to ''. So has_permission will always return False.
My unit test ViewTestCases inherits class RemoteAuthenticatedTest which is defined like so:
from rest_framework.test import APIClient,APITestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class RemoteAuthenticatedTest(APITestCase):
client_class = APIClient
def setUp(self):
self.username = 'mister_neutron'
self.password = 'XXXXXXXXXXX'
self.user = User.objects.create_user(username= self.username,
email='mister_neutron#example.com',
password=self.password)
#authentication user
self.client.login(username=self.username, password=self.password)
Token.objects.create(user=self.user)
super(RemoteAuthenticatedTest, self).setUp()
So I thought that request.user.username would be mister_neutron.
What am I doing wrong here?
Ah heck. I forgot that I am using RemoteUser authentication so when I make my I need to set REMOTE_USER like so:
response = self.client.get(url, REMOTE_USER=self.username)
I'm learning Django atm and I am stuck with creating a simple test case.
How I can test the following view? If the user is already logged in it redirects him to list view.
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.views.generic import TemplateView
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
class LoginView(TemplateView):
"""
Base class for client area login.
"""
def get(self, request, *args, **kwargs):
"""
Display login form, only if user is not yet authenticated.
Otherwise redirect to tickets index.
:param request:
:param args:
:param kwargs:
:return:
"""
if request.user.is_authenticated():
return redirect(reverse('tickets:index'))
form = AuthenticationForm()
return render(request, 'login.html', {'form': form})
So far the test I have created looks like this:
from django.test import TestCase, RequestFactory
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User, AnonymousUser
from app.account.views import LoginView
class TestLoginForm(TestCase):
def setUp(self):
"""
Set up environment
:return:
"""
self.user = User.objects.create_user(username="test", password="test123", email="test#gmail.com")
self.factory = RequestFactory()
def tearDown(self):
"""
Clean created user object
:return:
"""
self.user.delete()
self.factory = None
def test_login_form_redirect(self):
"""
Test login redirect
:return:
"""
request = self.factory.get(reverse('account:login'))
request.user = AnonymousUser()
view = LoginView()
response = view.get(request=request)
self.assertEqual(response.status_code, 200)
request.user = self.user
response = view.get(request=request)
self.assertRedirects(response, expected_url=reverse('tickets:index'), status_code=302, target_status_code=200)
The first assertation passed, but the second where the user should be redirected if already has logged in throws this error
AttributeError: 'HttpResponseRedirect' object has no attribute 'client'
What am I doing wrong?
Ok I have managed to make successful test case, with following code
def test_login_form_redirect(self):
"""
Test login redirect
:return:
"""
self.client.logout()
response = self.client.get(reverse('account:login'))
self.assertEqual(response.status_code, 200)
self.assertTrue(self.client.login(username='test', password='test123'))
response = self.client.get(reverse('account:login'))
self.assertRedirects(response, expected_url=reverse('tickets:index'), status_code=302, target_status_code=200)
This is my code:
from django.test import TestCase
from django.contrib.auth.models import User
class BetsTest(TestCase):
def setUp(self):
user = User.objects.create_user('test', 'test', 'test')
user.save()
self.client.login(username='test', password='test')
def test_details(self):
response = self.client.post('/bets/makebet/5', {'points' : '24'})
self.failUnlessEqual(response.status_code, 200)
And my view has decorator #login_required.. And no matter what I am doing my response is status: 301....
Well, if you're posting something, it's probably gonna redirect you to a success page right? That's not a 200 code.
Set follow=True on your post call and it should work fine.
response = self.client.post('/bets/makebet/5', {'points' : '24'}, follow=True)
You can also access the redirects if you do this with response.redirect_chain
https://docs.djangoproject.com/en/dev/topics/testing/tools/