Why does Django Redirect Test Fail? - django

I have a view unit test that is failing and I can't figure out the reason why. I believe it has something to do with the test database. The view in question is the default Django login view, django.contrib.auth.views.login. In my project, after the user logs in, they are redirected to a page that show which members are logged in. I've only stubbed out that page.
Here is the unit test:
from django.test import TestCase
from django.contrib.auth.models import User
from django.test.client import Client, RequestFactory
from django.core.urlresolvers import reverse
from utils.factories import UserFactory
class TestSignInView(TestCase):
def setUp(self):
self.client = Client()
# self.user = UserFactory()
self.user = User.objects.create_user(username='jdoe', password='jdoepass')
def tearDown(self):
self.user.delete()
def test_user_enters_valid_data(self):
response = self.client.post(reverse('login'), {'username': self.user.username, 'password': self.user.password}, follow=True)
print response.context['form'].errors
self.assertRedirects(response, reverse('show-members-online'))
Here is the error I get:
File "/Users/me/.virtualenvs/sp/lib/python2.7/site-packages/django/test/testcases.py", line 576, in assertRedirects
(response.status_code, status_code))
AssertionError: Response didn't redirect as expected: Response code was 200 (expected 302)
<ul class="errorlist"><li>__all__<ul class="errorlist"><li>Please enter a correct username and password. Note that both fields may be case-sensitive.</li></ul></li></ul>
The test fails with the same error whether I create the user manually with the create_user function or if I use this factory_boy factory:
from django.contrib.auth.models import User
class UserFactory(factory.DjangoModelFactory):
FACTORY_FOR = User
username = 'jdoe'
# password = 'jdoepass'
password = factory.PostGenerationMethodCall('set_password', 'jdoepass')
email = 'jdoe#example.com'
Here's the view I'm redirecting the user to after they log in successfully:
from django.shortcuts import render
def show_members_online(request, template):
return render(request, template)
I printed out the error which shows that the test isn't recognizing the username/password pair. I've also printed out the username and password inside the test to confirm that they're the same values as what I initialize them to in setUp. At first, when I was utilizing the User factory, I thought it was because I wasn't encrypting the password when I created the user. That's when I did some research and learned that I needed to use the PostGenerationMethodCall to set the password.
I also looked at Django's testcases.py file. I don't understand everything that it's doing but it prompted me to try setting 'follow=True' when I do the post but that didn't make a difference. Can anyone tell me what I'm doing wrong? By the way, I'm using nosetests as my test runner.
Thanks!

In your test test_user_enters_valid_data, you are passing the password as self.user.password. This will be the SHA of the password because Django stores the sha of password on db. That's why you can never read the password for a particular user using user.password.
So, change your test_user_enters_valid_data.
def test_user_enters_valid_data(self):
response = self.client.post(reverse('login'), {'username': self.user.username, 'password': 'jdoepass'}, follow=True)
####
####
And it should work then.

Your test is sending {'username': self.user.username, 'password': self.user.password} in the POST. However, self.user.password is the hashed password not the plain text password which is why they aren't matching and you are seeing the form error rather than the redirect. Changing this to {'username': self.user.username, 'password': 'jdoepass'} should validate the username/password combination.

Related

Django testing how to make a request as logged in user?

In Django tests, I want to log in and make a request.
Here the code
def test_something(self):
self.client.login(email=EMAIL, password=PASSWORD)
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)
But the code doesn't work and returns
AttributeError: 'AnonymousUser' object has no attribute 'profile'
How to make request in test as logged in user?
As the documentation on .login(…) says:
(…)
login() returns True if it the credentials were accepted and login
was successful.
Finally, you’ll need to remember to create user accounts before
you can use this method.
The tests run normally on a separate database, and thus the items created while debugging, or in production, do not have effect on that.
You thus first create a user and then test it with:
from django.contrib.auth import get_user_model
# …
def test_something(self):
User = get_user_model()
User.objects.create_user('my-user-name', email=EMAIL, password=PASSWORD)
self.assertTrue(self.client.login(email=EMAIL, password=PASSWORD))
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)
The standard authentication engine works with a username and password, not with an email and password, so if you used the builtin authentication manager, you login with:
from django.contrib.auth import get_user_model
# …
def test_something(self):
User = get_user_model()
User.objects.create_user('my-user-name', email=EMAIL, password=PASSWORD)
self.assertTrue(self.client.login(username='my-user-name', password=PASSWORD))
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)

Test that Django sends the e-mail for password reset

Good morning,
I would like to test that Django properly sends an e-mail for password reset as I have customised the routes. However I get a <HttpResponseForbidden status_code=403, "text/html"> and the e-mail is not implemented when I test it.
Here are the main features:
url :
path('password_reset/', auth_views.PasswordResetView.as_view(template_name="customer/password_reset_form.html", email_template_name="customer/password_reset_e-mail.html", success_url="done/"),
test method:
from django.contrib.auth import views as auth_views
from django.test import RequestFactory
from django.contrib.auth.models import AnonymousUser
class PasswordResetTest(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = User.objects.create_user(
username="user",
email="user#mail.com",
password="pwd")
def test_send_mail(self):
request = self.factory.post('/customer/password_reset', {'email': 'user#mail.com'})
response = auth_views.PasswordResetView.as_view()(request)
self.assertTemplateUsed('password_reset_e-mail.html')
print(response)
self.assertEqual(len(mail.outbox), 1)
Throught the print command, I can see an error 403 and that the outbox is empty.
I just cannot find proper documentation to build a test on this very part of Django.
Thank you for your support.

How to use django.contrib.auth with django-rest-framework?

django-rest-framework makes use of django.contrib.auth for authentication and authorization (as stated in the django-rest-framework authentication api guide)
However, no-where in the documentation does it talk about how users are actually authenticated using the rest-framework
By default the django.contrib.auth views will respond with a server-side rendered login form.
However, if using a client-side framework such as AngularJs this is not desired - you simply want an api endpoint against which you can authenticate.
Questions:
Is there django-rest-framework documentation I am somehow missing which explains how user authentication is done-out-of-the-box?
Does an out-of-the-box solution even exist?
If not, what is the recommended way of achieving this with minimal reinvention of the wheel?
lets say that you have login view:
Note: with this method you have to assure SSL/TLS because username and password are sending as plain text.
import json
import requests
def login(request):
if request.method == "POST":
username = request.POST['username']
password = request.POST['password']
login_url = 'http://your_url:port/rest-api/login/'
response = requests.post(login_url, data={'username': username, 'password': password})
response = json.loads(response.text)
if response.status_code == 200:
return render_to_response("login.html", {"success": True}, RequestContext(request))
your view in rest-api:
from django.contrib.auth.backends import ModelBackend as DjangoModelBackend
def login(request):
response = base_response.copy()
username = request.DATA.get('username', '')
password = request.DATA.get('password', '')
user = DjangoModelBackend().authenticate(username=email, password=password)
if user is not None:
response["message"] = "Authenticated"
else:
response["message"] = "Login Failed"
return Response(response)
and here is the part of ModelBackend
from django.contrib.auth import get_user_model
class ModelBackend(object):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
return None
You don't usually go through login forms when authenticating yourself at an API endpoint - you either use an API token or send the authentication credentials through a header, see How to use Basic Auth with jQuery and AJAX? on how to do that.

Permissions in unit testing

I wanted to introduce unit testing to a Django application. Although I started failing on the first thing I wanted to test. Can you tell me what I am doing wrong?
The view I want to test
#user_passes_test(lambda u: u.has_module_perms('myModule'))
def myView(request):
...someCode...
I wanted to test the user_passes_test bit, I also have more complex tests so I wanted to know if my tests let the right users and only them access the view. I focused on the bit that didn't work and simplified it a bit.
from django.contrib.auth.models import User
from django.test import TestCase
from settings import DJANGO_ROOT
class PermissionsTestCase(TestCase):
fixtures = [DJANGO_ROOT + 'testdata.json']
def setUp(self):
self.user = User.objects.create(username='user', password='pass')
self.user.save()
def test_permissions_overview(self):
url = '/secret/'
#User not logged in (guest)
response = self.client.get(url)
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/secret/')
#Logged in user that should not be able to see this page
response = self.client.get(url)
self.client.login(username='user', password='pass')
self.assertRedirects(response, 'http://testserver/accounts/login/?next=/secret/')
#Logged in user that has 'myModule' module permissions
self.user.user_permissions.add('myModule.view_myThing')
self.user.save()
self.assertTrue(self.user.has_module_perms('myModule')) #This one fails
self.client.login(username='user',password='pass')
response = self.client.get(url)
self.assertContains(response, "What to look for") #This one too
And the last bit keeps on failing. The permission doesn't get through. Any ideas?
This won't convert the password to hash
User.objects.create(username='user', password='pass')
the correct way to create user is :
User.objects.create_user(username='user', password='pass')
here is full summary
>>> from django.contrib.auth.models import User
>>> x=User.objects.create(username='user', password='pass')
>>> x.password
'pass'
>>> from django.test import Client
>>> c = Client()
>>> c.login(username='user',password='pass')
False
# But create_user works
>>> y=User.objects.create_user(username='user2', password='pass')
>>> y.password
u'pbkdf2_sha256$12000$oh55gfrnCZn5$hwGrkUZx38siegWHLXSYoNDT2SSP1M5+Whh5PnJZD8I='
>>> c.login(username='user2',password='pass')
True
Permissions are strings.
# this tests to see if a user has 'myModule'
#user_passes_test(lambda u: u.has_module_perms('myModule'))
#gives the user a completely different permission called 'myModule.view_myThing'
self.user.user_permissions.add('myModule.view_myThing')
#tests to see if user has a permission called 'myModule' which is not 'myModule.view_myThing'
self.assertTrue(self.user.has_module_perms('myModule'))
Base on what you have written I cannot infer your intent but you have at least 2 distinct permissions you are attempting to add/check. I would change the permissions you give to the user as follows:
self.user.user_permissions.add('myModule')

Test that user was logged in successfully

How can I test that a user is logged in after submitting the registration form?
I tried the following but it returns True even before I added the login logic to my registration view.
def test_that_user_gets_logged_in(self):
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
user = User.objects.get(username='foo')
assert user.is_authenticated()
The code that's being tested:
class RegistrationView(CreateView):
template_name = 'auth/registration.html'
form_class = UserCreationForm
success_url = '/'
def auth_login(self, request, username, password):
'''
Authenticate always needs to be called before login because it
adds which backend did the authentication which is required by login.
'''
user = authenticate(username=username, password=password)
login(request, user)
def form_valid(self, form):
'''
Overwrite form_valid to login.
'''
#save the user
response = super(RegistrationView, self).form_valid(form)
#Get the user creditials
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
#authenticate and login
self.auth_login(self.request, username, password)
return response
You can use the get_user method of the auth module. It says it wants a request as parameter, but it only ever uses the session attribute of the request. And it just so happens that our Client has that attribute.
from django.contrib import auth
user = auth.get_user(self.client)
assert user.is_authenticated
This is not the best answer. See https://stackoverflow.com/a/35871564/307511
Chronial has given
an excellent example on how to make this assertion below. His answer
better than mine for nowadays code.
The most straightforward method to test if a user is logged in is by testing the Client object:
self.assertIn('_auth_user_id', self.client.session)
You could also check if a specific user is logged in:
self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
As an additional info, the response.request object is not a HttpRequest object; instead, it's an ordinary dict with some info about the actual request, so it won't have the user attribute anyway.
Also, testing the response.context object is not safe because you don't aways have a context.
Django's TestClient has a login method which returns True if the user was successfully logged in.
The method is_authenticated() on the User model always returns True. False is returned for request.user.is_authenticated() in the case that request.user is an instance of AnonymousUser, which is_authenticated() method always returns False.
While testing you can have a look at response.context['request'].user.is_authenticated().
You can also try to access another page in test which requires to be logged in, and see if response.status returns 200 or 302 (redirect from login_required).
Where are you initialising your self.client? What else is in your setUp method? I have a similar test and your code should work fine. Here's how I do it:
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
class UserTestCase(TestCase):
def setUp(self):
self.client = Client()
def testLogin(self):
print User.objects.all() # returns []
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
print User.objects.all() # returns one user
print User.objects.all()[0].is_authenticated() # returns True
EDIT
If I comment out my login logic, I don't get any User after self.client.post(. If you really want to check if the user has been authenticated, use the self.client to access another url which requires user authentication. Continuing from the above, access another page:
response = self.client.get(reverse('another-page-which-requires-authentication'))
print response.status_code
The above should return 200 to confirm that the user has authenticated. Anything else, it will redirect to the login page with a 302 code.
There is another succinct way, using wsgi_request in response:
response = self.client.post('/signup', data)
assert response.wsgi_request.user.is_authenticated()
and #Chronial 's manner is also available with wsgi_request:
from django.contrib import auth
user = auth.get_user(response.wsgi_request)
assert user.is_authenticated()
Because response.wsgi_request object has a session attribute.
However, I think using response.wsgi_request.user is more simple.