Commit 83062c0b by Jason Bau

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 =)
parent 4a98e2ed
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 student.models import unique_id_for_user
from student.views import process_survey_link, _cert_info
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, 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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment