Tests + Now subclass PasswordResetForm instead of copy
Changed to subclassing django's PasswordResetForm and overriding clean_password() instead of copy/paste. Less lines to worry about for diff-cover this way =)
This commit is contained in:
@@ -1,33 +1,15 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from django.template import loader
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD, is_password_usable, get_hasher
|
||||
from django.contrib.auth.tokens import default_token_generator
|
||||
from django.contrib.sites.models import get_current_site
|
||||
from django.utils.http import int_to_base36
|
||||
from django.contrib.auth.forms import PasswordResetForm
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD
|
||||
|
||||
|
||||
|
||||
# This is a literal copy from Django 1.4.5's django.contrib.auth.forms.PasswordResetForm
|
||||
# I think copy-and-paste here is somewhat better than subclassing and
|
||||
# just changing the definition of clean_email, because it's less
|
||||
# likely to be broken by incompatibility with a new django version.
|
||||
# (If this form is good enough now, a snapshot of it ought to last a while)
|
||||
|
||||
class PasswordResetFormNoActive(forms.Form):
|
||||
error_messages = {
|
||||
'unknown': _("That e-mail address doesn't have an associated "
|
||||
"user account. Are you sure you've registered?"),
|
||||
'unusable': _("The user account associated with this e-mail "
|
||||
"address cannot reset the password."),
|
||||
}
|
||||
email = forms.EmailField(label=_("E-mail"), max_length=75)
|
||||
|
||||
class PasswordResetFormNoActive(PasswordResetForm):
|
||||
def clean_email(self):
|
||||
"""
|
||||
Validates that an active user exists with the given email address.
|
||||
"""
|
||||
This is a literal copy from Django 1.4.5's django.contrib.auth.forms.PasswordResetForm
|
||||
Except removing the requirement of active users
|
||||
Validates that a user exists with the given email address.
|
||||
"""
|
||||
email = self.cleaned_data["email"]
|
||||
#The line below contains the only change, removing is_active=True
|
||||
self.users_cache = User.objects.filter(email__iexact=email)
|
||||
@@ -37,36 +19,3 @@ class PasswordResetFormNoActive(forms.Form):
|
||||
for user in self.users_cache):
|
||||
raise forms.ValidationError(self.error_messages['unusable'])
|
||||
return email
|
||||
|
||||
def save(self, domain_override=None,
|
||||
subject_template_name='registration/password_reset_subject.txt',
|
||||
email_template_name='registration/password_reset_email.html',
|
||||
use_https=False, token_generator=default_token_generator,
|
||||
from_email=None, request=None):
|
||||
"""
|
||||
Generates a one-use only link for resetting password and sends to the
|
||||
user.
|
||||
"""
|
||||
from django.core.mail import send_mail
|
||||
for user in self.users_cache:
|
||||
if not domain_override:
|
||||
current_site = get_current_site(request)
|
||||
site_name = current_site.name
|
||||
domain = current_site.domain
|
||||
else:
|
||||
site_name = domain = domain_override
|
||||
c = {
|
||||
'email': user.email,
|
||||
'domain': domain,
|
||||
'site_name': site_name,
|
||||
'uid': int_to_base36(user.id),
|
||||
'user': user,
|
||||
'token': token_generator.make_token(user),
|
||||
'protocol': use_https and 'https' or 'http',
|
||||
}
|
||||
subject = loader.render_to_string(subject_template_name, c)
|
||||
# Email subject *must not* contain newlines
|
||||
subject = ''.join(subject.splitlines())
|
||||
email = loader.render_to_string(email_template_name, c)
|
||||
send_mail(subject, email, from_email, [user.email])
|
||||
|
||||
|
||||
@@ -5,18 +5,118 @@ when you run "manage.py test".
|
||||
Replace this with more appropriate tests for your application.
|
||||
"""
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
import unittest
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
from mock import Mock
|
||||
from django.test.client import RequestFactory
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.hashers import UNUSABLE_PASSWORD
|
||||
from django.template.loader import render_to_string, get_template, TemplateDoesNotExist
|
||||
from django.core.urlresolvers import is_valid_path
|
||||
|
||||
from mock import Mock, patch
|
||||
from textwrap import dedent
|
||||
|
||||
from student.models import unique_id_for_user
|
||||
from student.views import process_survey_link, _cert_info
|
||||
|
||||
from student.views import process_survey_link, _cert_info, password_reset, password_reset_confirm_wrapper
|
||||
from student.tests.factories import UserFactory
|
||||
from student.tests.test_email import mock_render_to_string
|
||||
COURSE_1 = 'edX/toy/2012_Fall'
|
||||
COURSE_2 = 'edx/full/6.002_Spring_2012'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
get_template('registration/password_reset_email.html')
|
||||
project_uses_password_reset = True
|
||||
except TemplateDoesNotExist:
|
||||
project_uses_password_reset = False
|
||||
|
||||
|
||||
class ResetPasswordTests(TestCase):
|
||||
""" Tests that clicking reset password sends email, and doesn't activate the user
|
||||
"""
|
||||
request_factory = RequestFactory()
|
||||
|
||||
def setUp(self):
|
||||
self.user = UserFactory.create()
|
||||
self.user.is_active = False
|
||||
self.user.save()
|
||||
|
||||
self.user_bad_passwd = UserFactory.create()
|
||||
self.user_bad_passwd.is_active = False
|
||||
self.user_bad_passwd.password = UNUSABLE_PASSWORD
|
||||
self.user_bad_passwd.save()
|
||||
|
||||
|
||||
@unittest.skipUnless(project_uses_password_reset, dedent("""Skipping Test because CMS has not provided
|
||||
necessary templates for password reset. If this message is in LMS tests, that is a bug and needs to be fixed."""))
|
||||
@patch('student.views.password_reset_confirm')
|
||||
@patch('django.core.mail.send_mail')
|
||||
@patch('student.views.render_to_string', Mock(side_effect=mock_render_to_string, autospec=True))
|
||||
def test_reset_password_email(self, send_email, reset_confirm):
|
||||
"""Tests sending of reset password email"""
|
||||
|
||||
#First test the bad password user, mainly for diff-cover sake
|
||||
bad_pwd_req = self.request_factory.post('/password_reset/', {'email': self.user_bad_passwd.email})
|
||||
bad_pwd_resp = password_reset(bad_pwd_req)
|
||||
self.assertEquals(bad_pwd_resp.status_code, 200)
|
||||
self.assertEquals(bad_pwd_resp.content, json.dumps({'success': False,
|
||||
'error': 'Invalid e-mail or user'}))
|
||||
|
||||
#Now test the exception cases with invalid email.
|
||||
bad_email_req = self.request_factory.post('/password_reset/', {'email': self.user.email+"makeItFail"})
|
||||
bad_email_resp = password_reset(bad_email_req)
|
||||
self.assertEquals(bad_email_resp.status_code, 200)
|
||||
self.assertEquals(bad_email_resp.content, json.dumps({'success': False,
|
||||
'error': 'Invalid e-mail or user'}))
|
||||
|
||||
#Now test the legit case where email should have been sent
|
||||
good_req = self.request_factory.post('/password_reset/', {'email': self.user.email})
|
||||
good_resp = password_reset(good_req)
|
||||
self.assertEquals(good_resp.status_code, 200)
|
||||
self.assertEquals(good_resp.content,
|
||||
json.dumps({'success': True,
|
||||
'value': "('registration/password_reset_done.html', [])"}))
|
||||
|
||||
((subject, msg, from_addr, to_addrs), sm_kwargs) = send_email.call_args
|
||||
self.assertIn("Password reset", subject)
|
||||
self.assertIn("You're receiving this e-mail because you requested a password reset", msg)
|
||||
self.assertEquals(from_addr, settings.DEFAULT_FROM_EMAIL)
|
||||
self.assertEquals(len(to_addrs), 1)
|
||||
self.assertIn(self.user.email, to_addrs)
|
||||
|
||||
#test that the user is not active
|
||||
#it's a bit unsettling that we have to reload the user from the db for this test to work
|
||||
#but I guess the user is cached here in the instance of ResetPasswordTests
|
||||
#so the update in the view does not know to update this class.
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
self.assertFalse(self.user.is_active)
|
||||
|
||||
#now try to activate the user in the password reset phase
|
||||
bad_reset_req = self.request_factory.get('/password_reset_confirm/NO-OP/')
|
||||
bad_reset_resp = password_reset_confirm_wrapper(bad_reset_req, 'NO', 'OP')
|
||||
(confirm_args, confirm_kwargs) = reset_confirm.call_args
|
||||
self.assertEquals(confirm_kwargs['uidb36'], 'NO')
|
||||
self.assertEquals(confirm_kwargs['token'], 'OP')
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
self.assertFalse(self.user.is_active)
|
||||
|
||||
reset_match = re.search(r'password_reset_confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/', msg).groupdict()
|
||||
good_reset_req = self.request_factory.get('/password_reset_confirm/{0}-{1}/'.format(reset_match['uidb36'],
|
||||
reset_match['token']))
|
||||
good_reset_resp = password_reset_confirm_wrapper(good_reset_req, reset_match['uidb36'], reset_match['token'])
|
||||
(confirm_args, confirm_kwargs) = reset_confirm.call_args
|
||||
self.assertEquals(confirm_kwargs['uidb36'], reset_match['uidb36'])
|
||||
self.assertEquals(confirm_kwargs['token'], reset_match['token'])
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
self.assertTrue(self.user.is_active)
|
||||
|
||||
|
||||
|
||||
class CourseEndingTest(TestCase):
|
||||
"""Test things related to course endings: certificates, surveys, etc"""
|
||||
|
||||
@@ -975,7 +975,7 @@ def password_reset(request):
|
||||
'value': render_to_string('registration/password_reset_done.html', {})}))
|
||||
else:
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'Invalid e-mail'}))
|
||||
'error': 'Invalid e-mail or user'}))
|
||||
|
||||
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
|
||||
''' A wrapper around django.contrib.auth.views.password_reset_confirm.
|
||||
|
||||
Reference in New Issue
Block a user