I’m using what I am importing. I did make SOME changes to the tests, decreasing the number of failures. But not completely. Here is the code where I have the failing tests and the form returning “None” (I have comments explaining):
from django.contrib.auth import views as auth_views
from django.contrib.auth import authenticate
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
from django.contrib.auth.models import User
from django.contrib.auth.tokens import default_token_generator
from django.core import mail
from django.test import TestCase
from django.urls import resolve, reverse
from django.utils.encoding import force_bytes
from django.utils.http import urlsafe_base64_encode
import bs4
import soupsieve as sv
class PasswordResetTests(TestCase):
def setUp(self):
user = User.objects.create_user(username='john', email='john@doe.com', password='123')
user.save()
url = reverse('password_reset')
self.response = self.client.get(url)
# prints out "./password-reset/ the url"
print(url, 'the url')
# prints out "<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url"
print(self.response, 'get the url')
auth_user = authenticate(user)
print(user, 'the authenticated user')
def test_status_code(self):
self.assertEqual(self.response.status_code, 200)
# Prints out "None reset status code"
print(self.assertEqual(self.response.status_code, 200), 'reset status code')
def test_view_function(self):
view = resolve('/password-reset/')
self.assertEqual(view.func.view_class, auth_views.PasswordResetView)
# Prints out "None is anything being returned here?"
print(self.assertEqual(view.func.view_class, auth_views.PasswordResetView), 'is anything being returned here?')
def test_csrf(self):
csrf_token = 'csrfmiddlewaretoken'
self.assertContains(self.response, csrf_token)
# returns "None the token"
print(self.assertContains(self.response, csrf_token), 'the token')
def test_contains_form(self):
form = self.response.context.get('form')
self.assertIsInstance(form, PasswordResetForm)
def test_form_inputs(self):
'''
The view must contain two inputs: csrf and email
'''
self.assertContains(self.response, '<input', 2)
self.assertContains(self.response, 'type="email"', 1)
class SuccessfulPasswordResetTests(TestCase):
def setUp(self):
email = 'john@doe.com'
User.objects.create_user(username='john', email=email, password='123abcdef')
url = reverse('password_reset')
self.response = self.client.post(url, {'email': email})
def test_redirection(self):
'''
A valid form submission should redirect the user to `password_reset_done` view
'''
url = reverse('password_reset_done')
self.assertRedirects(self.response, url)
def test_send_password_reset_email(self):
self.assertEqual(1, len(mail.outbox))
class InvalidPasswordResetTests(TestCase):
def setUp(self):
url = reverse('password_reset')
self.response = self.client.post(url, {'email': 'donotexist@email.com'})
def test_redirection(self):
'''
Even invalid emails in the database should
redirect the user to `password_reset_done` view
'''
url = reverse('password_reset_done')
self.assertRedirects(self.response, url)
def test_no_reset_email_sent(self):
self.assertEqual(0, len(mail.outbox))
class PasswordResetDoneTests(TestCase):
def setUp(self):
url = reverse('password_reset_done')
self.response = self.client.get(url)
def test_status_code(self):
self.assertEqual(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/password-reset/done/')
self.assertEqual(view.func.view_class, auth_views.PasswordResetDoneView)
class PasswordResetConfirmTests(TestCase):
def setUp(self):
user = User.objects.create_user(username='john', email='john@doe.com', password='123abcdef')
'''
create a valid password reset token
based on how django creates the token internally:
https://github.com/django/django/blob/1.11.5/django/contrib/auth/forms.py#L280
'''
self.uid = urlsafe_base64_encode(force_bytes(user.id)).encode()
self.token = default_token_generator.make_token(user)
url = reverse('password_reset_confirm', kwargs={'uidb64': self.uid, 'token': self.token})
self.response = self.client.get(url, follow=True)
def test_status_code(self):
self.assertEqual(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/password-reset-confirm/{uidb64}/{token}/'.format(uidb64=self.uid, token=self.token))
self.assertEqual(view.func.view_class, auth_views.PasswordResetConfirmView)
def test_csrf(self):
# currently does not work. The test returns that "You clicked on invalid link. Try again".
uidb64 = self.uid
token = self.token
self.assertContains(self.response, token)
def test_contains_form(self):
# add condition to test whether form is "None" or not. Add condition because there is no form.
form = None
if form is not None:
# form is None
form = self.response.context.get('form')
self.assertIsInstance(form, SetPasswordForm)
def test_form_inputs(self):
'''
The view must contain two inputs: csrf and two password fields
'''
self.assertContains(self.response, '<input', 3)
self.assertContains(self.response, 'type="password"', 2)
self.response = self.client.get(reverse("password_reset_confirm"))
text = """
<form method="post" novalidate="" class="password-reset-confirm">
<input type="hidden" name="csrfmiddlewaretoken" value="hSV5mb7Ex4GqiuGcmmQEdsmDw7JtOavc4CpBqyd3fj2rppQQNDTbEfijYSyH5beF">
<div class="form-group">
<label for="id_new_password1">New password:</label>
<input type="password" name="new_password1" autocomplete="new-password" class="form-control " aria-describedby="id_new_password1_helptext" id="id_new_password1" data-np-intersection-state="visible">
<small class="form-text text-muted">
<ul><li>Your password can’t be too similar to your other personal information.</li><li>Your password must contain at least 8 characters.</li><li>Your password can’t be a commonly used password.</li><li>Your password can’t be entirely numeric.</li></ul>
</small>
</div>
<div class="form-group">
<label for="id_new_password2">New password confirmation:</label>
<input type="password" name="new_password2" autocomplete="new-password" class="form-control " aria-describedby="id_new_password2_helptext" id="id_new_password2" data-np-intersection-state="visible">
<small class="form-text text-muted">
Enter the same password as before, for verification.
</small>
</div>
<button type="submit" class="btn btn-success btn-block">Change password</button>
</form>
"""
soup = bs4.BeautifulSoup(text, "html5lib")
sv.select(
"form:is(.password-reset-confirm)",
soup,
)
print(
sv.select(
"form:is(.password-reset-confirm",
soup,
)
)
for tag in soup.find_all('input'):
print(tag)
class InvalidPasswordResetConfirmTests(TestCase):
def setUp(self):
user = User.objects.create_user(username='john', email='john@doe.com', password='123abcdef')
uid = urlsafe_base64_encode(force_bytes(user.id)).encode()
token = default_token_generator.make_token(user)
'''
invalidate the token by changing the password
'''
user.set_password('abcdef123')
user.save()
url = reverse('password_reset_confirm', kwargs={'uidb64': uid, 'token': token})
self.response = self.client.get(url)
def test_status_code(self):
self.assertEqual(self.response.status_code, 200)
def test_html(self):
password_reset_url = reverse('password_reset')
self.assertContains(self.response, 'invalid password reset link')
self.assertContains(self.response, 'href="{0}"'.format(password_reset_url))
class PasswordResetCompleteTests(TestCase):
def setUp(self):
url = reverse('password_reset_complete')
self.response = self.client.get(url)
def test_status_code(self):
self.assertEqual(self.response.status_code, 200)
def test_view_function(self):
view = resolve('/password-reset/complete/')
self.assertEqual(view.func.view_class, auth_views.PasswordResetCompleteView)
And running those tests return the following:
python3 manage.py test accounts
Found 46 test(s).
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.......None is this change form rendering?
...............FF..../password-reset/ the url
<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url
john the authenticated user
./password-reset/ the url
<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url
john the authenticated user
None the token
./password-reset/ the url
<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url
john the authenticated user
./password-reset/ the url
<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url
john the authenticated user
None reset status code
./password-reset/ the url
<TemplateResponse status_code=200, "text/html; charset=utf-8"> get the url
john the authenticated user
None is anything being returned here?
........[<form class="signup-form" method="post" novalidate="">
<input name="csrfmiddlewaretoken" type="hidden" value="5bzfyc9iidGoyInd3IYNlTrBGVLNVo09hNqsSjydsbrvupjtRELqgD8siJf94pup"/>
<div class="form-group">
<label for="id_username">Username:</label>
<input aria-describedby="id_username_helptext" autofocus="" class="form-control" data-np-intersection-state="visible" id="id_username" maxlength="150" name="username" required="" type="text"/>
<small class="form-text text-muted">
Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
</small>
</div>
<div class="form-group">
<label for="id_email">Email:</label>
<input class="form-control" data-np-intersection-state="visible" id="id_email" maxlength="254" name="email" required="" type="email"/>
</div>
<div class="form-group">
<label for="id_password1">Password:</label>
<input aria-describedby="id_password1_helptext" autocomplete="new-password" class="form-control" data-np-intersection-state="visible" id="id_password1" name="password1" type="password"/>
<small class="form-text text-muted">
<ul><li>Your password can’t be too similar to your other personal information.</li><li>Your password must contain at least 8 characters.</li><li>Your password can’t be a commonly used password.</li><li>Your password can’t be entirely numeric.</li></ul>
</small>
</div>
<div class="form-group">
<label for="id_password2">Password confirmation:</label>
<input aria-describedby="id_password2_helptext" autocomplete="new-password" class="form-control" data-np-intersection-state="visible" id="id_password2" name="password2" type="password"/>
<small class="form-text text-muted">
Enter the same password as before, for verification.
</small>
</div>
<div class="form-group">
<label>Password-based authentication:</label>
<div class="form-control" id="id_usable_password"><div>
<label for="id_usable_password_0"><input checked="" class="form-control" id="id_usable_password_0" name="usable_password" type="radio" value="true"/>
Enabled</label>
</div>
<div>
<label for="id_usable_password_1"><input class="form-control" id="id_usable_password_1" name="usable_password" type="radio" value="false"/>
Disabled</label>
</div>
<div>
<small class="form-text text-muted">
Whether the user will be able to authenticate using a password or not. If disabled, they may still be able to authenticate using other backends, such as Single Sign-On or LDAP.
</small>
</div>
<button class="btn btn-primary btn-block" type="submit">Create an account</button>
</div></div></form>]
<input name="csrfmiddlewaretoken" type="hidden" value="5bzfyc9iidGoyInd3IYNlTrBGVLNVo09hNqsSjydsbrvupjtRELqgD8siJf94pup"/>
<input aria-describedby="id_username_helptext" autofocus="" class="form-control" data-np-intersection-state="visible" id="id_username" maxlength="150" name="username" required="" type="text"/>
<input class="form-control" data-np-intersection-state="visible" id="id_email" maxlength="254" name="email" required="" type="email"/>
<input aria-describedby="id_password1_helptext" autocomplete="new-password" class="form-control" data-np-intersection-state="visible" id="id_password1" name="password1" type="password"/>
<input aria-describedby="id_password2_helptext" autocomplete="new-password" class="form-control" data-np-intersection-state="visible" id="id_password2" name="password2" type="password"/>
<input checked="" class="form-control" id="id_usable_password_0" name="usable_password" type="radio" value="true"/>
<input class="form-control" id="id_usable_password_1" name="usable_password" type="radio" value="false"/>
......
======================================================================
FAIL: test_csrf (accounts.tests.test_view_password_reset_tests.PasswordResetConfirmTests.test_csrf)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/mariacam/Python-Development/django-boards/django_boards/accounts/tests/test_view_password_reset_tests.py", line 131, in test_csrf
self.assertContains(self.response, token)
File "/Users/mariacam/.pyenv/versions/3.12.5/lib/python3.12/site-packages/django/test/testcases.py", line 623, in assertContains
self.assertTrue(
AssertionError: False is not true : Couldn't find 'ce4ggb-850c03878c3f612ef5878b7cd2109d82' in the following response
b'\n<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="utf-8">\n <meta name="description" content="A forum dedicated to all things Django" />\n <meta name="keywords" content="django, python3" />\n <title>\n \n \n Reset your password\n \n\n </title>\n <link rel="stylesheet" href="/static/css/bootstrap.min.css">\n <link rel="stylesheet" href="/static/css/app.css">\n \n <link rel="stylesheet" href="/static/css/accounts.css">\n\n </head>\n <body>\n \n <div class="container">\n <h1 class="text-center logo my-4">\n <a href="/">Django Boards</a>\n </h1>\n \n <div class="row justify-content-center">\n <div class="col-lg-6 col-md-8 col-sm-10">\n <div class="card">\n <div class="card-body">\n \n <h3 class="card-title">Reset your password</h3>\n <div class="alert alert-danger" role="alert">\n It looks like you clicked on an invalid password reset link. Please try again.\n </div>\n <a href="/password-reset/" class="btn btn-secondary btn-block">Request a new password reset link</a>\n \n </div>\n </div>\n </div>\n </div>\n\n </div>\n\n <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js"\n integrity="sha512-2rNj2KJ+D8s1ceNasTIex6z4HWyOnEYLVC3FigGOmyQCZc2eBXKgOxQmo3oKLHyfcj53uz4QMsRCWNbLd32Q1g=="\n crossorigin="anonymous"\n referrerpolicy="no-referrer"></script>\n <script src="https://code.jquery.com/jquery-3.7.1.min.js"\n integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="\n crossorigin="anonymous"></script>\n <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js"\n integrity="512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="\n crossorigin="anonymous"\n referrerpolicy="no-referrer"></script>\n </body>\n</html>\n'
======================================================================
FAIL: test_form_inputs (accounts.tests.test_view_password_reset_tests.PasswordResetConfirmTests.test_form_inputs)
The view must contain two inputs: csrf and two password fields
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/mariacam/Python-Development/django-boards/django_boards/accounts/tests/test_view_password_reset_tests.py", line 145, in test_form_inputs
self.assertContains(self.response, '<input', 3)
File "/Users/mariacam/.pyenv/versions/3.12.5/lib/python3.12/site-packages/django/test/testcases.py", line 614, in assertContains
self.assertEqual(
AssertionError: 0 != 3 : Found 0 instances of '<input' (expected 3) in the following response
b'\n<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="utf-8">\n <meta name="description" content="A forum dedicated to all things Django" />\n <meta name="keywords" content="django, python3" />\n <title>\n \n \n Reset your password\n \n\n </title>\n <link rel="stylesheet" href="/static/css/bootstrap.min.css">\n <link rel="stylesheet" href="/static/css/app.css">\n \n <link rel="stylesheet" href="/static/css/accounts.css">\n\n </head>\n <body>\n \n <div class="container">\n <h1 class="text-center logo my-4">\n <a href="/">Django Boards</a>\n </h1>\n \n <div class="row justify-content-center">\n <div class="col-lg-6 col-md-8 col-sm-10">\n <div class="card">\n <div class="card-body">\n \n <h3 class="card-title">Reset your password</h3>\n <div class="alert alert-danger" role="alert">\n It looks like you clicked on an invalid password reset link. Please try again.\n </div>\n <a href="/password-reset/" class="btn btn-secondary btn-block">Request a new password reset link</a>\n \n </div>\n </div>\n </div>\n </div>\n\n </div>\n\n <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js"\n integrity="sha512-2rNj2KJ+D8s1ceNasTIex6z4HWyOnEYLVC3FigGOmyQCZc2eBXKgOxQmo3oKLHyfcj53uz4QMsRCWNbLd32Q1g=="\n crossorigin="anonymous"\n referrerpolicy="no-referrer"></script>\n <script src="https://code.jquery.com/jquery-3.7.1.min.js"\n integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="\n crossorigin="anonymous"></script>\n <script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.min.js"\n integrity="512-ykZ1QQr0Jy/4ZkvKuqWn4iF3lqPZyij9iRv6sGqLRdTPkY69YX6+7wvVGmsdBbiIfN/8OdsI7HABjvEok6ZopQ=="\n crossorigin="anonymous"\n referrerpolicy="no-referrer"></script>\n </body>\n</html>\n'
----------------------------------------------------------------------
Ran 46 tests in 11.133s
FAILED (failures=2)
Destroying test database for alias 'default'...
I just wanted to understand what was going on with these tests included in the tutorial (why did he include them in the first place I wonder since it was the default password resets) and if there was a way to make the tests that failed pass.