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.
Related
I do not know what is wrong with this code when I am trying to test login, first assert passed so user created successfully but failed in second assert.
Hint: I am using simple JWT
Test Code:
from user.models import User
from rest_framework.test import APIClient
from mixer.backend.django import mixer
from django.urls import reverse
import pytest
client = APIClient()
#pytest.mark.django_db
class TestUser():
#pytest.fixture
def setup_user(self):
user = mixer.blend(User, email='a#a.com', password='1')
return user
def test_login(self, setup_user):
assert User.objects.count() == 1
data = {'email': 'a#a.com', 'password': '1'}
response = client.post(reverse('login'), data=data)
assert response.status_code == 200
URL:
path('api/login/', TokenObtainPairView.as_view(), name='login'),
ERROR:
FAILED user/tests/test_user.py::TestUser::test_login - assert 401 == 200
Just for the record: You need to set the password correctly to be able to hash it, in order for the login to work:
#pytest.fixture
def setup_user(self):
user = mixer.blend(User, email='a#a.com')
user.set_password('1')
user.save()
return user
try adding format="json" to your client post call
This is my DRF view:
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def check_user(request):
user = request.user
# use user object here
return JSONResponse({})
And this is my unit test for said view:
class CheckUserViewTest(TestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.client.login(username='username', password='Pas$w0rd')
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
But I always get a 401 UNAUTHORIZED response from my view. I have logged in the user in my test. What am I doing wrong?
Since you are using Django REST Framework you have to also use DRF's test client called APIClient instead of Django's test client. This happens automagically if you inherit from DRF's APITestCase instead of Django's TestCase.
Complete example:
class CheckUserViewTest(APITestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.assertTrue(self.client.login(username='username', password='Pas$w0rd'))
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
An alternative is to use force_authenticate:
class CheckUserViewTest(APITestCase):
def test_check_user(self):
user = User.objects.create_user('username', 'Pas$w0rd')
self.client.force_authenticate(user)
response = self.client.get(reverse('check_user'))
self.assertEqual(response.status_code, httplib.OK)
If your are using djangorestframework you must have to use APITestCase here.
An complete example is just below
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from django.contrib.auth.models import User
from django.contrib.auth.hashers import make_password
class TestLogin(APITestCase):
'''
This will handle login testcases
'''
def setUp(self):
self.url = reverse('check_user')
def test_login(self):
'''
This will test successfull login
'''
data = {
"full_name" : "full name",
'email' : "email#gmail.com",
'password' : "password"
}
User.objects.create(
full_name = data.get('full_name'),
email = data.get('email'),
password = make_password(data.get('password'))
)
response = self.client.get(self.url, data=data)
self.assertEqual(response.status_code,status.HTTP_200_OK)
I'm working on my first project which uses Django REST Framework, and I'm having issues testing the API. I'm getting 403 Forbidden errors instead of the expected 200 or 201. However, the API works as expected.
I have been going through DRF Testing docs, and it all seems straightforward, but I believe my client is not being logged in. Normally in my Django projects I use a mixture of factory boy and django webtest which I've had a lot of happy success with. I'm not finding that same happiness testing the DRF API after a couple days of fiddling around.
I'm not sure if this is a problem relating to something I'm doing wrong with DRF APITestCase/APIClient or a problem with the django test in general.
I'm just pasting the following code and not posting the serializers/viewsets because the API works in the browser, it seems I'm just having issues with the APIClient authentication in the APITestCase.
# settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
]
}
# tests.py
from django.test import TestCase
from rest_framework.test import APITestCase, APIClient
from accounts.models import User
from .factories import StaffUserFactory
class MainSetUp(TestCase):
def setUp(self):
self.user = StaffUserFactory
self.api_root = '/api/v0/'
self.client = APIClient()
class APITests(MainSetUp, APITestCase):
def test_create_feedback(self):
"""
Ensure we can create a new account object.
"""
self.client.login(username='staffuser', password='staffpassword')
url = '%sfeedback/' % self.api_root
data = {
'feedback': 'this is test feedback'
}
response = self.client.post(url, data, user=self.user)
self.assertEqual(response.status_code, 201)
self.assertEqual(response.data, data)
# factories.py
from factory.django import DjangoModelFactory
from django.contrib.auth import get_user_model
User = get_user_model()
class UserFactory(DjangoModelFactory):
class Meta:
model = User
class StaffUserFactory(UserFactory):
username = 'staffuser'
password = 'staffpassword'
email = 'staff#email.com'
first_name = 'Staff'
last_name = 'User'
is_staff = True
I've never used DjangoModelFactory before, but it appears that you have to call create after setting your user to the StaffUserFactory. http://factoryboy.readthedocs.org/en/latest/_modules/factory/django.html#DjangoModelFactory
class MainSetUp(TestCase):
def setUp(self):
self.user = StaffUserFactory
self.user.create()
self.api_root = '/api/v0/'
self.client = APIClient()
I bet your User's password is not being set properly. You should use set_password. As a start, trying changing your setUp to this:
def setUp(self):
self.user = StaffUserFactory
self.user.set_password('staffpassword')
self.user.save() # You could probably omit this, but set_password does't call it
self.api_root = '/api/v0/'
self.client = APIClient()
If that works, you probably want to override _generate() in your factory to add that step.
Another thing to check would be that SessionAuthentication is in your DEFAULT_AUTHENTICATION_CLASSES setting.
I think you must instantiate the Factory to get real object, like this:
self.user = StaffUserFactory()
Hope that help.
Moreover, you don't need to create a seperate class for staff, just set is_staff=True is enough. Like this:
self.user = UseFactory(is_staff=True)
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.
In one of my apps/admin.py I have
from django.contrib import admin
from django.contrib.sites.models import Site
from django.contrib.redirects.models import Redirect
from mezzanine.generic.models import ThreadedComment
from mezzanine.conf.models import Setting
admin.site.unregister(Site)
admin.site.unregister(Redirect)
admin.site.unregister(ThreadedComment)
admin.site.unregister(Setting)
This causes them to be removed from the admin like I want, and the application works fine.
However, when I run my tests via nose, I get this error raise NotRegistered('The model %s is not registered' % model.__name__)
NotRegistered: The model Site is not registered Which I assume is because it is trying to unregister something which is already unregistered. This is an example of a test that is failing:
class TestRegistration(TestCase):
def setUp(self):
self.client = Client()
email = ConfirmedEmail(email='test.com',company='Test Industries')
email.save()
def test_landing(self):
response = self.client.get(reverse('home'))
self.assertEqual(response.status_code, 200)