I have page with login_required decorator which I want to test whether correct template is used. On stackoverflow I've found authorization method for unit test but for me it do not work for some reason. Here is my test:
from django.test import TestCase
from django.test import Client
import base64
class TestUsingCorrectTemplates(TestCase):
def test_correct_classroom_template_used(self):
auth_headers = {'HTTP_AUTHORIZATION': 'Basic '+base64.b64encode('admin#dot.com:admin')}
c = Client()
response = c.get('/classroom/', **auth_headers)
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response,'classroom.html')
Also would like to mention that authorization handled with OpenId/AllAuth and there is no /login page, user logs in from start page /
Content of response variable is following:
Vary: Cookie
X-Frame-Options: SAMEORIGIN
Content-Type: text/html; charset=utf-8
Location: http://testserver/?next=/classroom/
Test error:
self.assertEqual(response.status_code, 200)
AssertionError: 302 != 200
What am I doing wrong?
The HTTP code 302 means that your server is sending a redirect response. You should tell your client to follow redirects so that you deal with the actual login page. You can change your get call like this:
response = c.get('/classroom/', follow=True, **auth_headers)
If you want to check the intermediate redirection steps you can inspect response.redirect_chain. It is all documented here.
Did you try creating a user and calling the login method of your Client instance?
import base64
from django.test import TestCase
class TestUsingCorrectTemplates(TestCase):
def setUp(self):
# Create your user here
# self.user = ...
def test_correct_classroom_template_used(self):
self.client.login('admin#dot.com', 'admin')
response = self.client.get('/classroom/') # XXX: You should use url reversal here.
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'classroom.html')
Related
I am building a RESTAPI in Django using the following library called drf-firebase-token-auth. The client sends a get request with the token_id available in the Authorization header, and the library is expected to handle authentication in Firebase automatically.
But the problem is if I do a simple get request without headers like curl <url+endpoint> the library doesn't verify if there's a an Authorization header and try to get a token for verification as if the request happens successfully as if drf-firebase-token-auth didn't raised it to be a problem. So I handled an exception whereas if the get request doesn't have an an authorization header, then raise an exception. But is it secure?
from datetime import datetime
from rest_framework import status
from rest_framework.decorators import (
api_view,
)
from rest_framework.response import Response
# Create your views here.
#api_view(["GET"])
def check_token(request):
# if token is valid response with current server time
try:
if request.META["HTTP_AUTHORIZATION"]:
date = datetime.now()
return Response(
data="Server time is: " + str(date), status=status.HTTP_200_OK
)
except:
return Response(data="Authorization header unavailable")
I have written a view function that processes a post request containing json data from a source outside of django (labview). I'm just testing it to begin with so it looks like this
def post_entry(request):
'''Process incoming json string
'''
if request.method == 'POST':
post_data = request.body
# Return a response
return HttpResponse('data received OK')
I've written a test to test this and it passes fine:
def test_post_entry_view_good_post_data(self):
'''post_entry view should return a 200 status if valid
'''
data = {'DHTP Data': ['10', '50.296', '50.94', '50.418', '50.425', '50.431', '50.94'],
'Test String': 'My Test String'}
request_url = reverse('post_entry')
response = self.client.post(request_url, content_type='application/json',
data=dumps(data))
# Should return a 200 response indicating ok
self.assertEqual(response.status_code, 200)
But when labview posts the data post_entry returns a 403 forbidden error. I guess this is due to no csrf token being present, but why does the test pass in this case?
The test client works around the CSRF functionality. See https://docs.djangoproject.com/en/1.9/ref/csrf/#testing
If you are going to have a view that accepts post data from a source external to your app you need to make your view exempt from CSRF protection by using csrf_exempt:
#csrf_exempt
def post_entry(request):
'''Process incoming json string
'''
If you are going to do this, you should use some other method of validating the request
If your view is supposed to accept POST from external sources it is upon you to validate the request as every POST request is required to have a CSRF token (Refer: CSRF). Hence, for your purpose, you'll have to exempt the view from CSRF validation using #csrf_exempt decorator and write your own validation for the request using something like Token Authentication
Use this line to get the decorator needed to bypass CSRF protection:
from django.views.decorators.csrf import csrf_exempt
then put the #csrf_exempt decorator on the line before your function.
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/
The query to my endpoint works fine (as long as I pass it a valid token), it returns the json representation of my response data.
The code in the service api that calls my endpoint, passing an auth token in the header:
headers = {'content-type': 'application/json',
'Authorization': 'Token {}'.format(myToken)}
url = 'http://localhost:8000/my_endpoint/'
r = session.get(url=url, params=params, headers=headers)
In views.py, I have a method decorator that wraps the dispatch method on the view (viewsets.ReadOnlyModelViewSet):
def login_required(f):
def check_login_and_call(request, *args, **kwargs):
authentication = request.META.get('HTTP_AUTHORIZATION', b'')
if isinstance(authentication, str):
authentication = authentication.encode(HTTP_HEADER_ENCODING)
key = authentication.split()
if not key or len(key) != 2:
raise PermissionDenied('Authentication failed.')
user, token = authenticate_credentials(key[1])
return f(request, *args, **kwargs)
return check_login_and_call
I'm trying to write a test to authenticate the request using a token:
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory
from rest_framework.test import APITestCase
from rest_framework.test import force_authenticate
class EndpointViewTest(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = User.objects.create_user(
username='user#foo.com', email='user#foo.com', password='top_secret')
self.token = Token.objects.create(user=self.user)
self.token.save()
def test_token_auth(self):
request = self.factory.get('/my_endpoint')
force_authenticate(request, token=self.token.key)
view = views.EndpointViewSet.as_view({'get': 'list'})
response = view(request)
self.assertEqual(response.status_code, 200)
json_response = json.loads(response.render().content)['results']
For some reason, I cannot get the request to properly pass the token for this test. Using force_authenticate doesn't seem to change the header that I'm using for validating the token. The current output is raising "PermissionDenied: Authentication failed." because the token isn't being set on the request.
Is there a proper way to set this in the request header in my test or to refactor the way I'm using it in the first place?
I found a way to get the test to pass, but please post if you have a better idea of how to handle any of this.
request = self.factory.get('/my_endpoint', HTTP_AUTHORIZATION='Token {}'.format(self.token))
force_authenticate(request, user=self.user)
After changing the above two lines of the test, it seems to authenticate based on the token properly.
I wanted to test the authentication function itself, so forcing authentication wans't an option.
One way to properly pass the token is to use APIClient, which you already have imported.
client = APIClient()
client.credentials(HTTP_AUTHORIZATION='Token ' + self.token.key)
response = client.get('/api/vehicles/')
That sets your given token into the request header and lets the back end decide if it's valid or not.
Sorry for digging this old thread up, but if someone is using APIClient() to do their tests you can do the following:
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
from rest_framework.authtoken.models import Token
from django.contrib.auth.models import User
class VehicleCreationTests(APITestCase):
def setUp(self):
self.client = APIClient()
self.user = User.objects.create_superuser('admin', 'admin#admin.com', 'admin123')
self.token = Token.objects.create(user=self.user)
def testcase(self):
self.client.force_login(user=self.user)
response = self.client.post('/api/vehicles/', data=vehicle_data, format='json', HTTP_AUTHORIZATION=self.token)
self.assertEqual(response.status_code, 201)
Really good resource that I've used to come up with this is django-rest-framework-jwt tests
The simpler way to force_authentication using a built-in method from APITestCase is:
class Test(APITestCase):
def setUp(self):
user1 = User.objects.create_user(username='foo')
self.client.force_authenticate(user=user1) # self.client is from APITestCase
... the rest of your tests ...
I am testing a set of Django applications which make extensive use of the 'permission_required' decorator. This derives in a 302 HTTP response in most of the views that I have.
My question is: there is any way to avoid or deactivate the 'permission_required' in testing, so I can get a 200 response when I call my views, instead of the 302?
Thank you!
Just log in with a superuser in your test case setUp method
from django.test import TestCase
from django.contrib.auth.models import User
class TestThatNeedsLogin(TestCase):
def setUp(self):
User.objects.create_superuser(
'user1',
'user1#example.com',
'pswd',
)
self.client.login(username="user1", password="pswd")
def tearDown(self):
self.client.logout()
def test_something(self):
response = self.client.get("/")
self.assertEqual(200, response.status_code)
You could monkey patch it:
import django.contrib.auth.decorators
real_permission_required = decorators.permission_required
# return a function that returns the exact function that was decorated, ignoring arguments
decorators.permission_required = lambda *args, **kwargs: lambda func: func
You need to make sure this happens before it's used, which is at the definition time of the object it's decorating. (For example, when that module is included.)
It also has to happen before it's rebound to another scope. After import django.contrib.auth.decorators is fine, but before from django.contrib.auth.decorators import permission_required.
Well. The solution I have found is to create a superuser in the setUp method from the TestCase class. I did it in that way:
def setUp(self):
self.client = Client()
self.user = User.objects.create_superuser(
'testuser',
'test#example.com',
'easy_password',
)
Then, when I want to test a URL, I do this:
def test_search_customers(self):
url = reverse('customer_search')
response = self.client.get(url)
# Not logged user. Must return a 302 HTTP code.
self.assertEquals(response.status_code, 302)
self.assertEquals(response['Location'], 'http://testserver/unauthorized/?next=/search/customers/')
# HERE I LOG IN MY SUPERUSER
self.client.login(username='testuser', password='easy_password')
response = self.client.get(url, follow=True)
# Same URL requested with a logged user with permissions. Must return 200 HTTP code.
self.assertEquals(response.status_code, 200)
This is what it worked for me :)
Thank you all. Cheers,
Jose