Functional tests for PasswordResetTool ====================================== Introduction ------------ For this test, we're going to use ``zope.testbrowser``. This package is included with Zope 3.2 which is included in Zope 2.9, and it has excellent documentation in its ``README.txt``. Note that our usage of testbrowser is unusual and inconsistent, mostly because Plone forms have inconsistencies and because testbrowser makes assumptions that are not true for Plone forms. >>> from Products.PasswordResetTool.tests.utils import Browser >>> browser = Browser() >>> browser.open('http://nohost/plone/') Assumptions ----------- First of all we have to be aware that Plone by default implements two distinct password policies regarding member registration. A. Users can provide their own password (and can optionally be send an e-mail with login credentials) and can login directly without validation of the e-mail address. B. A password is generated for the users (and an e-mail with login credentials is sent automatically). This policy can enabled or disabled in the ``validate_email`` property on the Plone Site object. By default ``validate_email`` is enabled and the first policy applies. Another aspect we have to take into account is the fact that Plone by default allows both Anonymous users to register themselves (by joining the Portal) and Administrators to register (other) members. Users of PasswordResetTool don't want any credentials to be sent out via e-mail. Instead, PasswordResetTool sends out an e-mail containing an URL where the user can set their password. The PasswordResetTool has to respect both policies (A and B) and both use cases (Anonymous or Admin?). The desired result after installing PasswordResetTool is as follows: 1. Anonymous user registers himself A. Users can provide their own password during registration, but don't have the option to send credentials by e-mail. B. Users can't provide a password but are sent an e-mail with a link to set their password. (Validates e-mail address.) 2. Site Admin registers a user A. The Site Admin can provide a password. He is not allowed to send the credentials via e-mail. B. The Site Admin can't provide a password. Instead, Plone will send the registered user an e-mail with a link to set the password. (Validates e-mail address.) In addition, we want users to be logged in directly whenever possible. E.g., whenever a user enters his credentials he should not be asked for it again on the next page. 1A. User joins and forgets password ----------------------------------- What we do here: - Join the portal - Log in - Log out again - Forget our password (this is where PasswordResetTool comes in) - Read the e-mail that contains the URL we visit to reset our password - Reset our password - Log in with our new password First we need to turn on explicit password entry, by setting``validate_email`` to False: >>> portal.portal_properties.site_properties.validate_email = False Let's join as a member. Plone's default settings will let the user type in his initial password: >>> browser.getLink('Join').click() >>> browser.url 'http://nohost/plone/join_form' >>> browser.getControl(name='username').value = 'jsmith' >>> browser.getControl(name='email').value = 'jsmith@example.com' >>> browser.getControl(name='password').value = 'secret' >>> browser.getControl(name='password_confirm').value = 'secret' >>> browser.getControl(name='form.button.Register').click() XXX Make sure we don't have a way to receive our credentials via e-mail. >>> "You have been registered" in browser.contents True We are not logged in yet at this point. Let's try to log in: >>> browser.getLink('Log in').click() >>> browser.url 'http://nohost/plone/login_form' >>> browser.getControl(name='__ac_name').value = 'jsmith' >>> browser.getControl(name='__ac_password').value = 'secret' >>> browser.getControl(name='submit').click() >>> "You are now logged in" in browser.contents True Log out again: >>> browser.getLink('Log out').click() >>> "You are now logged out" in browser.contents True Now it is time to forget our password and click the ``Forgot your password`` in the portlet: >>> browser.open('http://nohost/plone') >>> browser.getLink('Forgot your password?').click() >>> browser.url 'http://nohost/plone/mail_password_form' >>> form = browser.getForm(name='mail_password') >>> form.getControl(name='userid').value = 'jsmith' >>> form.submit() As part of our test setup, we replaced the original MailHost with our own version. Our version doesn't mail messages, it just collects them in a list called ``messages``: >>> mailhost = self.portal.MailHost >>> mailhost.messages # doctest: +ELLIPSIS [] >>> msg = str(mailhost.messages[-1]) Now that we have the message, we want to look at its contents, and then we extract the address that lets us reset our password: >>> "To: jsmith@example.com" in msg True >>> please_visit_text = "please visit this address:" >>> please_visit_text in msg True >>> url_index = msg.index(please_visit_text) + len(please_visit_text) >>> address = msg[url_index:].split()[0] >>> address # doctest: +ELLIPSIS 'http://nohost/plone/passwordreset/...' Now that we have the address, we will reset our password: >>> browser.open(address) >>> "Set your password" in browser.contents True >>> form = browser.getForm(name='pwreset_action') >>> form.getControl(name='userid').value = 'jsmith' >>> form.getControl(name='password').value = 'secretion' >>> form.getControl(name='password2').value = 'secretion' >>> form.submit() We can now login using our new password: >>> "Your password has been set successfully." in browser.contents True >>> browser.url 'http://nohost/plone/pwreset_finish' >>> browser.getControl(name='__ac_name').value = 'jsmith' >>> browser.getControl(name='__ac_password').value = 'secretion' >>> browser.getControl(name='submit').click() We should be logged in now: >>> "You are now logged in" in browser.contents True Log out again: >>> browser.getLink('Log out').click() >>> "You are now logged out" in browser.contents True 2A. Administrator registers user -------------------------------- - Log in as the portal owner - Browse to User and Group Management and add user - Register a member (with send email checked???) - Log out - Log in as the new member First, we want to login as the portal owner: >>> from Products.PloneTestCase import PloneTestCase as PTC >>> browser.getLink('Log in').click() >>> browser.getControl(name='__ac_name').value = PTC.portal_owner >>> browser.getControl(name='__ac_password').value = PTC.default_password >>> browser.getControl(name='submit').click() >>> "You are now logged in" in browser.contents True We navigate to the Users Overview page and register a new user: >>> browser.getLink('Site Setup').click() >>> browser.getLink('Users and Groups Administration').click() >>> browser.getControl('Add New User').click() >>> browser.url 'http://nohost/plone/join_form?came_from_prefs=1' >>> browser.getControl(name='username').value = 'wsmith' >>> browser.getControl(name='email').value = 'wsmith@example.com' >>> browser.getControl(name='password').value = 'supersecret' >>> browser.getControl(name='password_confirm').value = 'supersecret' >>> browser.getControl(name='form.button.Register').click() XXX Make sure we don't have a way to send the credentials via e-mail. We want to logout and login as the new member: >>> browser.getLink('Log out').click() >>> browser.getControl("Login Name").value = 'wsmith' >>> browser.getControl("Password").value = 'supersecret' >>> browser.getControl("Log in").click() >>> "You are now logged in" in browser.contents True >>> browser.getLink('Log out').click() 1B. User joins with e-mail validation enabled and forgets password ------------------------------------------------------------------ What we do here is quite similiar to 1A, but instead of typing in the password ourselves, we will be sent an e-mail with the URL to set our password. First off, we need to set ``validate_mail`` to False: >>> browser.getLink('Log in').click() >>> browser.getControl(name='__ac_name').value = PTC.portal_owner >>> browser.getControl(name='__ac_password').value = PTC.default_password >>> browser.getControl(name='submit').click() >>> "You are now logged in" in browser.contents True Let's go directly to the ``reconfig_form``: >>> browser.open('http://nohost/plone/reconfig_form') >>> ctrl = browser.getControl("Generate and e-mail user's initial password") >>> ctrl.selected = True >>> browser.getControl('Save').click() Log out again and then join: >>> browser.getLink('Log out').click() >>> "You are now logged out" in browser.contents True >>> browser.getLink('Join').click() >>> browser.getControl(name='username').value = 'bsmith' >>> browser.getControl(name='email').value = 'bsmith@example.com' We shouldn't be able to fill in our password: >>> browser.getControl(name='password').value = 'secret' # doctest: +ELLIPSIS Traceback (most recent call last): ... LookupError: name 'password' Now register: >>> browser.getControl(name='form.button.Register').click() >>> "You have been registered" in browser.contents True We should have received an e-mail at this point: >>> mailhost = self.portal.MailHost >>> len(mailhost.messages) 2 >>> msg = str(mailhost.messages[-1]) Now that we have the message, we want to look at its contents, and then we extract the address that lets us reset our password: >>> "To: bsmith@example.com" in msg True >>> please_visit_text = "Please activate your account by visiting" >>> please_visit_text in msg True >>> url_index = msg.index(please_visit_text) + len(please_visit_text) >>> address = msg[url_index:].split()[0] >>> address # doctest: +ELLIPSIS 'http://nohost/plone/passwordreset/...' Now that we have the address, we will reset our password: >>> browser.open(address) >>> "Please fill out the form below to set your password" in browser.contents True >>> browser.getControl(name='password').value = 'secret' >>> browser.getControl(name='password2').value = 'secret' >>> browser.getControl("Set my password").click() >>> "Your password has been set successfully." in browser.contents True Now we can log in: >>> browser.getControl("Login Name").value = 'bsmith' >>> browser.getControl("Password").value = 'secret' >>> browser.getControl("Log in").click() >>> "You are now logged in" in browser.contents True Log out again: >>> browser.getLink('Log out').click() >>> "You are now logged out" in browser.contents True 2B. Administrator adds user with email validation enabled --------------------------------------------------------- Simliar to 2A, but instead of setting the password for new member, an e-mail is sent containing the URL that lets the user log in. First, we want to login as the portal owner: >>> from Products.PloneTestCase import PloneTestCase as PTC >>> browser.getLink('Log in').click() >>> browser.getControl(name='__ac_name').value = PTC.portal_owner >>> browser.getControl(name='__ac_password').value = PTC.default_password >>> browser.getControl(name='submit').click() >>> "You are now logged in" in browser.contents True We navigate to the Users Overview page and register a new user: >>> browser.getLink('Site Setup').click() >>> browser.getLink('Users and Groups Administration').click() >>> browser.getControl('Add New User').click() >>> browser.url 'http://nohost/plone/join_form?came_from_prefs=1' >>> browser.getControl(name='username').value = 'wwwsmith' >>> browser.getControl(name='email').value = 'wwwsmith@example.com' We shouldn't be able to fill in our password: >>> browser.getControl(name='password').value = 'secret' # doctest: +ELLIPSIS Traceback (most recent call last): ... LookupError: name 'password' Now register and logout: >>> browser.getControl(name='form.button.Register').click() >>> browser.getLink('Log out').click() >>> "You are now logged out" in browser.contents True We should have received an e-mail at this point: >>> mailhost = self.portal.MailHost >>> len(mailhost.messages) 3 >>> msg = str(mailhost.messages[-1]) Now that we have the message, we want to look at its contents, and then we extract the address that lets us reset our password: >>> "To: wwwsmith@example.com" in msg True >>> please_visit_text = "Please activate your account by visiting" >>> please_visit_text in msg True >>> url_index = msg.index(please_visit_text) + len(please_visit_text) >>> address = msg[url_index:].split()[0] >>> address # doctest: +ELLIPSIS 'http://nohost/plone/passwordreset/...' Now that we have the address, we will reset our password: >>> browser.open(address) >>> "Please fill out the form below to set your password" in browser.contents True >>> browser.getControl(name='password').value = 'superstr0ng' >>> browser.getControl(name='password2').value = 'superstr0ng' >>> browser.getControl("Set my password").click() >>> "Your password has been set successfully." in browser.contents True Now we can log in: >>> browser.getControl("Login Name").value = 'wwwsmith' >>> browser.getControl("Password").value = 'superstr0ng' >>> browser.getControl("Log in").click() >>> "You are now logged in" in browser.contents True >>> browser.getLink('Log out').click()