Merge lp:~deryck/launchpad/refactor-editemail-doctest-363916 into lp:launchpad
- refactor-editemail-doctest-363916
- Merge into devel
| Status: | Merged |
|---|---|
| Approved by: | j.c.sackett on 2012-04-26 |
| Approved revision: | no longer in the source branch. |
| Merged at revision: | 15171 |
| Proposed branch: | lp:~deryck/launchpad/refactor-editemail-doctest-363916 |
| Merge into: | lp:launchpad |
| Diff against target: |
710 lines (+186/-437) 5 files modified
lib/lp/registry/browser/tests/test_person.py (+186/-13) lib/lp/registry/stories/person/xx-add-email.txt (+0/-187) lib/lp/registry/stories/person/xx-person-delete-email.txt (+0/-23) lib/lp/registry/stories/person/xx-set-preferredemail.txt (+0/-64) lib/lp/registry/stories/person/xx-validate-email.txt (+0/-150) |
| To merge this branch: | bzr merge lp:~deryck/launchpad/refactor-editemail-doctest-363916 |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| j.c.sackett (community) | 2012-04-26 | Approve on 2012-04-26 | |
| Richard Harding (community) | code* | 2012-04-26 | Approve on 2012-04-26 |
|
Review via email:
|
|||
Commit Message
Refactor page tests for +editemails into browser unit tests.
Description of the Change
This work was done as part of fixing a security issue. In an earlier branch (not yet up for review), I changed Launchpad to re-authenticate a user when going to edit their settings for email, gpg, ssh, etc. The work broke a ton of tests and rather than spend more time than already lost fixing tests, I decided to refactor the page tests into unit tests. I have a number of reasons that I decided to do this, chief of which are:
* It's hard to test in doctest when login redirects to re-auth the user
* I needed to find some LOC credit for the work
This is a large branch deleting doctests, so I decided to land it independent of the actual bug fix. Everything that was tested in the doctest is either tested already in another place, or I added a new unit tests for it here. There is no real functionality change here.
We are lint free for these changes, too.
| Deryck Hodge (deryck) wrote : | # |
I answered questions here on IRC, and per that discussion, I won't change anything except for adding comments to the test functions. I also add comments to the existing tests in the same testcase that didn't have comments, since I was following the pattern there.
Thanks for the review, Rick!
| j.c.sackett (jcsackett) wrote : | # |
I have nothing to add, thanks very much for this refactor.
Preview Diff
| 1 | === modified file 'lib/lp/registry/browser/tests/test_person.py' |
| 2 | --- lib/lp/registry/browser/tests/test_person.py 2012-04-17 22:56:29 +0000 |
| 3 | +++ lib/lp/registry/browser/tests/test_person.py 2012-04-27 16:45:30 +0000 |
| 4 | @@ -3,6 +3,7 @@ |
| 5 | |
| 6 | __metaclass__ = type |
| 7 | |
| 8 | +import email |
| 9 | import doctest |
| 10 | from textwrap import dedent |
| 11 | |
| 12 | @@ -16,6 +17,7 @@ |
| 13 | ) |
| 14 | import transaction |
| 15 | from zope.component import getUtility |
| 16 | +from zope.publisher.interfaces import NotFound |
| 17 | |
| 18 | from lp.app.errors import NotFoundError |
| 19 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
| 20 | @@ -37,8 +39,11 @@ |
| 21 | from lp.registry.model.milestone import milestone_sort_key |
| 22 | from lp.services.config import config |
| 23 | from lp.services.identity.interfaces.account import AccountStatus |
| 24 | +from lp.services.identity.interfaces.emailaddress import IEmailAddressSet |
| 25 | +from lp.services.mail import stub |
| 26 | from lp.services.verification.interfaces.authtoken import LoginTokenType |
| 27 | from lp.services.verification.interfaces.logintoken import ILoginTokenSet |
| 28 | +from lp.services.verification.tests.logintoken import get_token_url_from_email |
| 29 | from lp.services.webapp import canonical_url |
| 30 | from lp.services.webapp.interfaces import ILaunchBag |
| 31 | from lp.services.webapp.servers import LaunchpadTestRequest |
| 32 | @@ -65,7 +70,10 @@ |
| 33 | LaunchpadZopelessLayer, |
| 34 | ) |
| 35 | from lp.testing.matchers import HasQueryCount |
| 36 | -from lp.testing.pages import extract_text |
| 37 | +from lp.testing.pages import ( |
| 38 | + extract_text, |
| 39 | + setupBrowserForUser, |
| 40 | + ) |
| 41 | from lp.testing.views import ( |
| 42 | create_initialized_view, |
| 43 | create_view, |
| 44 | @@ -355,38 +363,70 @@ |
| 45 | self.ppa = self.factory.makeArchive(owner=self.person) |
| 46 | self.view = create_initialized_view(self.person, '+edit') |
| 47 | |
| 48 | - def test_add_email_good_data(self): |
| 49 | - email_address = self.factory.getUniqueEmailAddress() |
| 50 | + def createAddEmailView(self, email_address): |
| 51 | + """Test helper to create +editemails view.""" |
| 52 | form = { |
| 53 | 'field.VALIDATED_SELECTED': self.valid_email_address, |
| 54 | 'field.VALIDATED_SELECTED-empty-marker': 1, |
| 55 | 'field.actions.add_email': 'Add', |
| 56 | 'field.newemail': email_address, |
| 57 | } |
| 58 | - create_initialized_view(self.person, "+editemails", form=form) |
| 59 | - |
| 60 | + return create_initialized_view(self.person, "+editemails", form=form) |
| 61 | + |
| 62 | + def createSetContactViaAddEmailView(self, email_address): |
| 63 | + """Test helper to use +editemails view to set preferred address.""" |
| 64 | + form = { |
| 65 | + 'field.VALIDATED_SELECTED': email_address, |
| 66 | + 'field.actions.set_preferred': 'Set as Contact Address', |
| 67 | + } |
| 68 | + return create_initialized_view(self.person, '+editemails', form=form) |
| 69 | + |
| 70 | + def _assertEmailAndError(self, email_str, expected_msg): |
| 71 | + """Special assert function for dealing with email-related errors.""" |
| 72 | + view = self.createAddEmailView(email_str) |
| 73 | + error_msg = view.errors[0] |
| 74 | + if type(error_msg) != unicode: |
| 75 | + error_msg = error_msg.doc() |
| 76 | + self.assertEqual(expected_msg, error_msg) |
| 77 | + |
| 78 | + def test_add_email(self): |
| 79 | + """Adding email should add a login token, notification, and email.""" |
| 80 | + stub.test_emails = [] |
| 81 | + email_address = self.factory.getUniqueEmailAddress() |
| 82 | + view = self.createAddEmailView(email_address) |
| 83 | # If everything worked, there should now be a login token to validate |
| 84 | # this email address for this user. |
| 85 | token = getUtility(ILoginTokenSet).searchByEmailRequesterAndType( |
| 86 | email_address, |
| 87 | self.person, |
| 88 | LoginTokenType.VALIDATEEMAIL) |
| 89 | - self.assertTrue(token is not None) |
| 90 | + self.assertIsNotNone(token) |
| 91 | + notifications = view.request.response.notifications |
| 92 | + self.assertEqual(1, len(notifications)) |
| 93 | + expected_msg = ( |
| 94 | + u"A confirmation message has been sent to '%s'." |
| 95 | + " Follow the instructions in that message to confirm" |
| 96 | + " that the address is yours. (If the message doesn't arrive in a" |
| 97 | + " few minutes, your mail provider might use 'greylisting', which" |
| 98 | + " could delay the message for up to an hour or two.)" % |
| 99 | + email_address) |
| 100 | + self.assertEqual(expected_msg, notifications[0].message) |
| 101 | + transaction.commit() |
| 102 | + self.assertEqual(2, len(stub.test_emails)) |
| 103 | + to_addrs = [to_addr for from_addr, to_addr, msg in stub.test_emails] |
| 104 | + # Both the new and old addr should be sent email. |
| 105 | + self.assertIn([self.valid_email_address], to_addrs) |
| 106 | + self.assertIn([email_address], to_addrs) |
| 107 | |
| 108 | def test_add_email_address_taken(self): |
| 109 | + """Adding an already existing email should give error notice.""" |
| 110 | email_address = self.factory.getUniqueEmailAddress() |
| 111 | self.factory.makePerson( |
| 112 | name='deadaccount', |
| 113 | displayname='deadaccount', |
| 114 | email=email_address, |
| 115 | account_status=AccountStatus.NOACCOUNT) |
| 116 | - form = { |
| 117 | - 'field.VALIDATED_SELECTED': self.valid_email_address, |
| 118 | - 'field.VALIDATED_SELECTED-empty-marker': 1, |
| 119 | - 'field.actions.add_email': 'Add', |
| 120 | - 'field.newemail': email_address, |
| 121 | - } |
| 122 | - view = create_initialized_view(self.person, "+editemails", form=form) |
| 123 | + view = self.createAddEmailView(email_address) |
| 124 | error_msg = view.errors[0] |
| 125 | expected_msg = ( |
| 126 | "The email address '%s' is already registered to " |
| 127 | @@ -397,6 +437,139 @@ |
| 128 | % email_address) |
| 129 | self.assertEqual(expected_msg, error_msg) |
| 130 | |
| 131 | + def test_validate_email(self): |
| 132 | + """Validating an email should send a notice email to the user.""" |
| 133 | + stub.test_emails = [] |
| 134 | + added_email = self.factory.getUniqueEmailAddress() |
| 135 | + view = self.createAddEmailView(added_email) |
| 136 | + form = { |
| 137 | + 'field.UNVALIDATED_SELECTED': added_email, |
| 138 | + 'field.actions.validate': 'Confirm', |
| 139 | + } |
| 140 | + view = create_initialized_view(self.person, '+editemails', form=form) |
| 141 | + notifications = view.request.response.notifications |
| 142 | + self.assertEqual(1, len(notifications)) |
| 143 | + expected_msg = ( |
| 144 | + u"An e-mail message was sent to '%s' " |
| 145 | + "with instructions on how to confirm that it belongs to you." |
| 146 | + % added_email) |
| 147 | + self.assertEqual(expected_msg, notifications[0].message) |
| 148 | + # Ensure we sent mail to the right address. |
| 149 | + transaction.commit() |
| 150 | + to_addrs = [to_addr for from_addr, to_addr, msg in stub.test_emails] |
| 151 | + self.assertIn([added_email], to_addrs) |
| 152 | + |
| 153 | + def test_validate_token(self): |
| 154 | + """Hitting +validateemail should actuall validate the email.""" |
| 155 | + stub.test_emails = [] |
| 156 | + added_email = self.factory.getUniqueEmailAddress() |
| 157 | + self.createAddEmailView(added_email) |
| 158 | + form = { |
| 159 | + 'field.UNVALIDATED_SELECTED': added_email, |
| 160 | + 'field.actions.validate': 'Confirm', |
| 161 | + } |
| 162 | + create_initialized_view(self.person, '+editemails', form=form) |
| 163 | + # Get the token from the email msg. |
| 164 | + transaction.commit() |
| 165 | + messages = [msg for from_addr, to_addr, msg in stub.test_emails] |
| 166 | + raw_msg = None |
| 167 | + for orig_msg in messages: |
| 168 | + msg = email.message_from_string(orig_msg) |
| 169 | + if msg.get('to') == added_email: |
| 170 | + raw_msg = orig_msg |
| 171 | + token_url = get_token_url_from_email(raw_msg) |
| 172 | + browser = setupBrowserForUser(user=self.person) |
| 173 | + browser.open(token_url) |
| 174 | + expected_msg = u'Confirm e-mail address <code>%s</code>' % added_email |
| 175 | + self.assertIn(expected_msg, browser.contents) |
| 176 | + browser.getControl('Continue').click() |
| 177 | + # Login again to access displayname, since browser logged us out. |
| 178 | + login_person(self.person) |
| 179 | + expected_title = u'%s in Launchpad' % self.person.displayname |
| 180 | + self.assertEqual(expected_title, browser.title) |
| 181 | + |
| 182 | + def test_remove_unvalidated_email_address(self): |
| 183 | + """A user should be able to remove and unvalidated email.""" |
| 184 | + added_email = self.factory.getUniqueEmailAddress() |
| 185 | + view = self.createAddEmailView(added_email) |
| 186 | + form = { |
| 187 | + 'field.UNVALIDATED_SELECTED': added_email, |
| 188 | + 'field.actions.remove_unvalidated': 'Remove', |
| 189 | + } |
| 190 | + view = create_initialized_view(self.person, '+editemails', form=form) |
| 191 | + notifications = view.request.response.notifications |
| 192 | + self.assertEqual(1, len(notifications)) |
| 193 | + expected_msg = ( |
| 194 | + u"The email address '%s' has been removed." % added_email) |
| 195 | + self.assertEqual(expected_msg, notifications[0].message) |
| 196 | + |
| 197 | + def test_cannot_remove_contact_address(self): |
| 198 | + """A user should not be able to remove their own contact email.""" |
| 199 | + form = { |
| 200 | + 'field.VALIDATED_SELECTED': self.valid_email_address, |
| 201 | + 'field.actions.remove_validated': 'Remove', |
| 202 | + } |
| 203 | + view = create_initialized_view(self.person, '+editemails', form=form) |
| 204 | + error_msg = view.errors[0] |
| 205 | + expected_msg = ( |
| 206 | + "You can't remove %s because it's your contact email address." |
| 207 | + % self.valid_email_address) |
| 208 | + self.assertEqual(expected_msg, error_msg) |
| 209 | + |
| 210 | + def test_set_contact_address(self): |
| 211 | + """A user should be able to change to a new contact email.""" |
| 212 | + added_email = self.factory.getUniqueEmailAddress() |
| 213 | + view = self.createAddEmailView(added_email) |
| 214 | + # We need a commit to make sure person and other data are in DB. |
| 215 | + transaction.commit() |
| 216 | + validated_email = getUtility( |
| 217 | + IEmailAddressSet).new(added_email, self.person) |
| 218 | + self.person.validateAndEnsurePreferredEmail(validated_email) |
| 219 | + view = self.createSetContactViaAddEmailView(added_email) |
| 220 | + notifications = view.request.response.notifications |
| 221 | + self.assertEqual(1, len(notifications)) |
| 222 | + expected_msg = ( |
| 223 | + u"Your contact address has been changed to: %s" % added_email) |
| 224 | + self.assertEqual(expected_msg, notifications[0].message) |
| 225 | + |
| 226 | + def test_set_contact_address_already_set(self): |
| 227 | + """Users should be warned when setting the same contact email.""" |
| 228 | + view = self.createSetContactViaAddEmailView(self.valid_email_address) |
| 229 | + notifications = view.request.response.notifications |
| 230 | + self.assertEqual(1, len(notifications)) |
| 231 | + expected_msg = ( |
| 232 | + "%s is already set as your contact address." |
| 233 | + % self.valid_email_address) |
| 234 | + self.assertEqual(expected_msg, notifications[0].message) |
| 235 | + |
| 236 | + def test_team_editemails_not_found(self): |
| 237 | + """Teams should not have a +editemails page.""" |
| 238 | + team = self.factory.makeTeam(owner=self.person, members=[self.person]) |
| 239 | + url = '%s/+editemails' % canonical_url(team) |
| 240 | + browser = setupBrowserForUser(user=self.person) |
| 241 | + self.assertRaises(NotFound, browser.open, url) |
| 242 | + |
| 243 | + def test_email_string_validation_no_email_prodvided(self): |
| 244 | + """+editemails should warn if no email is provided.""" |
| 245 | + no_email = '' |
| 246 | + expected_msg = u'Required input is missing.' |
| 247 | + self._assertEmailAndError(no_email, expected_msg) |
| 248 | + |
| 249 | + def test_email_string_validation_invalid_email(self): |
| 250 | + """+editemails should warn when provided data is not an email.""" |
| 251 | + not_an_email = 'foo' |
| 252 | + expected_msg = u"'foo' doesn't seem to be a valid email address." |
| 253 | + self._assertEmailAndError(not_an_email, expected_msg) |
| 254 | + |
| 255 | + def test_email_string_validation_is_escaped(self): |
| 256 | + """+editemails should escape output to prevent XSS.""" |
| 257 | + xss_email = "foo@example.com<script>window.alert('XSS')</script>" |
| 258 | + expected_msg = ( |
| 259 | + u"'foo@example.com<script>" |
| 260 | + "window.alert('XSS')</script>'" |
| 261 | + " doesn't seem to be a valid email address.") |
| 262 | + self._assertEmailAndError(xss_email, expected_msg) |
| 263 | + |
| 264 | |
| 265 | class PersonAdministerViewTestCase(TestPersonRenameFormMixin, |
| 266 | TestCaseWithFactory): |
| 267 | |
| 268 | === removed file 'lib/lp/registry/stories/person/xx-add-email.txt' |
| 269 | --- lib/lp/registry/stories/person/xx-add-email.txt 2012-04-11 15:48:20 +0000 |
| 270 | +++ lib/lp/registry/stories/person/xx-add-email.txt 1970-01-01 00:00:00 +0000 |
| 271 | @@ -1,187 +0,0 @@ |
| 272 | -= Registering email addresses = |
| 273 | - |
| 274 | -Users can have any number of email addresses registered in their Launchpad |
| 275 | -accounts, although we'll always communicate with them using their preferred |
| 276 | -one. In order to register a new email address users must follow a link sent |
| 277 | -by us to the address they want to add and confirm the registration. |
| 278 | - |
| 279 | -Sample Person will now add a couple email addresses to his account. |
| 280 | - |
| 281 | - >>> from lp.services.mail import stub |
| 282 | - |
| 283 | - >>> orig_email = 'test@canonical.com' |
| 284 | - >>> browser = setupBrowser(auth='Basic %s:test' % orig_email) |
| 285 | - >>> browser.open('http://launchpad.dev/~name12') |
| 286 | - >>> browser.getLink(url='+editemails').click() |
| 287 | - >>> browser.url |
| 288 | - 'http://launchpad.dev/~name12/+editemails' |
| 289 | - |
| 290 | - >>> new_email = 'test2@canonical.com' |
| 291 | - >>> browser.getControl('Add a new address').value = new_email |
| 292 | - >>> browser.getControl('Add', index=1).click() |
| 293 | - |
| 294 | - >>> browser.url |
| 295 | - 'http://launchpad.dev/%7Ename12/+editemails' |
| 296 | - >>> for msg in get_feedback_messages(browser.contents): |
| 297 | - ... print msg |
| 298 | - A confirmation message has been sent to... |
| 299 | - |
| 300 | -There should be an email for the new address and one for the original |
| 301 | -preferred email address that the change was made to their account. Order gets |
| 302 | -mixed up so you have to check both spots. |
| 303 | - |
| 304 | - >>> to_addrs = [to_addr for from_addr, to_addr, msg in stub.test_emails] |
| 305 | - >>> assert len(to_addrs) == 2 |
| 306 | - >>> if orig_email in to_addrs[0][0]: |
| 307 | - ... assert new_email in to_addrs[1][0] |
| 308 | - ... else: |
| 309 | - ... assert new_email in to_addrs[0][0] |
| 310 | - ... assert orig_email in to_addrs[1][0] |
| 311 | - >>> stub.test_emails = [] |
| 312 | - |
| 313 | -Trying to add the same email again (while it's unconfirmed) will only cause |
| 314 | -a new email to be sent with a new link. The links in the old and new email are |
| 315 | -still accessible and will confirm the new address if/when the user follows |
| 316 | -them. |
| 317 | - |
| 318 | - >>> browser.getControl('Add a new address').value = 'test2@canonical.com' |
| 319 | - >>> browser.getControl('Add', index=1).click() |
| 320 | - |
| 321 | - >>> browser.url |
| 322 | - 'http://launchpad.dev/%7Ename12/+editemails' |
| 323 | - >>> for msg in get_feedback_messages(browser.contents): |
| 324 | - ... print msg |
| 325 | - A confirmation message has been sent to... |
| 326 | - |
| 327 | - # Extract the link (from the email we just sent) the user will have to |
| 328 | - # use to finish the registration process. There will be two emails so we |
| 329 | - # must check them both in order to make sure it's sent. |
| 330 | - >>> from lp.services.verification.tests.logintoken import ( |
| 331 | - ... get_token_url_from_email) |
| 332 | - >>> def check_tokenemail_and_reset(): |
| 333 | - ... found, url, toaddr = False, None, None |
| 334 | - ... for from_addr, to_addrs, raw_msg in stub.test_emails: |
| 335 | - ... if 'token' in raw_msg: |
| 336 | - ... found = True |
| 337 | - ... url = get_token_url_from_email(raw_msg) |
| 338 | - ... toaddr = to_addrs |
| 339 | - ... assert found |
| 340 | - ... return url, toaddr |
| 341 | - >>> token_url, toaddrs = check_tokenemail_and_reset() |
| 342 | - >>> token_url |
| 343 | - 'http://launchpad.dev/token/...' |
| 344 | - >>> toaddrs |
| 345 | - ['test2@canonical.com'] |
| 346 | - |
| 347 | -Follow the token link, to confirm the new email address. |
| 348 | - |
| 349 | - >>> browser.open(token_url) |
| 350 | - >>> browser.url |
| 351 | - 'http://launchpad.dev/token/.../+validateemail' |
| 352 | - >>> browser.getControl('Continue').click() |
| 353 | - |
| 354 | - >>> browser.url |
| 355 | - 'http://launchpad.dev/~name12' |
| 356 | - >>> for msg in get_feedback_messages(browser.contents): |
| 357 | - ... print msg |
| 358 | - Email address successfully confirmed. |
| 359 | - |
| 360 | -Now that the address is confirmed he sees it in the list of his confirmed |
| 361 | -addresses. |
| 362 | - |
| 363 | - >>> from lp.testing.pages import strip_label |
| 364 | - |
| 365 | - >>> browser.getLink(url='+editemails').click() |
| 366 | - >>> confirmed = browser.getControl(name="field.VALIDATED_SELECTED") |
| 367 | - >>> [strip_label(option) for option in confirmed.displayOptions] |
| 368 | - ['test@canonical.com', 'test2@canonical.com', 'testing@canonical.com'] |
| 369 | - |
| 370 | -If he tries to add it again, he'll get an error message explaining |
| 371 | -that it's already registered. |
| 372 | - |
| 373 | - >>> browser.getControl('Add a new address').value = 'test2@canonical.com' |
| 374 | - >>> browser.getControl('Add', index=1).click() |
| 375 | - >>> browser.url |
| 376 | - 'http://launchpad.dev/%7Ename12/+editemails' |
| 377 | - >>> for msg in get_feedback_messages(browser.contents): |
| 378 | - ... print msg |
| 379 | - There is 1 error. |
| 380 | - The email address 'test2@canonical.com' is already registered as your |
| 381 | - email address... |
| 382 | - |
| 383 | - |
| 384 | -== Adding a second email address == |
| 385 | - |
| 386 | - >>> browser.getControl('Add a new address').value = 'sample@ubuntu.com' |
| 387 | - >>> browser.getControl('Add', index=1).click() |
| 388 | - |
| 389 | - >>> browser.url |
| 390 | - 'http://launchpad.dev/%7Ename12/+editemails' |
| 391 | - >>> for tag in find_tags_by_class(browser.contents, 'message'): |
| 392 | - ... print tag.renderContents() |
| 393 | - A confirmation message has been sent to... |
| 394 | - |
| 395 | - # Extract the link (from the email we just sent) the user will have to |
| 396 | - # use to finish the registration process. |
| 397 | - >>> token_url, toaddrs = check_tokenemail_and_reset() |
| 398 | - >>> token_url |
| 399 | - 'http://launchpad.dev/token/...' |
| 400 | - >>> toaddrs |
| 401 | - ['sample@ubuntu.com'] |
| 402 | - |
| 403 | -Follow the token link, to confirm the new email address. |
| 404 | - |
| 405 | - >>> browser.open(token_url) |
| 406 | - >>> browser.url |
| 407 | - 'http://launchpad.dev/token/.../+validateemail' |
| 408 | - >>> browser.getControl('Continue').click() |
| 409 | - |
| 410 | - >>> browser.url |
| 411 | - 'http://launchpad.dev/~name12' |
| 412 | - |
| 413 | - >>> for tag in find_tags_by_class(browser.contents, 'informational'): |
| 414 | - ... print tag.renderContents() |
| 415 | - Email address successfully confirmed. |
| 416 | - |
| 417 | - |
| 418 | -== Trying to register an already registered email address == |
| 419 | - |
| 420 | -Email addresses can not be registered more than once in Launchpad, so if |
| 421 | -a given email address is registered by Joe, then Sample Person won't be |
| 422 | -able to register it for himself. When that happens we tell the user the |
| 423 | -email is already registered and explain he may want to merge the other |
| 424 | -account if that's a duplicate. |
| 425 | - |
| 426 | - >>> login(ANONYMOUS) |
| 427 | - >>> person = factory.makePerson(email='joe@ubuntu.com') |
| 428 | - >>> person_with_hidden_emails = factory.makePerson( |
| 429 | - ... email='joe2@ubuntu.com', hide_email_addresses=True) |
| 430 | - >>> logout() |
| 431 | - >>> browser.open('http://launchpad.dev/~name12/+editemails') |
| 432 | - >>> browser.getControl('Add a new address').value = 'joe@ubuntu.com' |
| 433 | - >>> browser.getControl('Add', index=1).click() |
| 434 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
| 435 | - There is 1 error. |
| 436 | - The email address 'joe@ubuntu.com' is already registered... |
| 437 | - If you think that is a duplicated account, you can merge it... |
| 438 | - |
| 439 | - >>> browser.open('http://launchpad.dev/~name12/+editemails') |
| 440 | - >>> browser.getControl('Add a new address').value = 'joe2@ubuntu.com' |
| 441 | - >>> browser.getControl('Add', index=1).click() |
| 442 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
| 443 | - There is 1 error. |
| 444 | - The email address 'joe2@ubuntu.com' is already registered... |
| 445 | - If you think that is a duplicated account, you can merge it... |
| 446 | - |
| 447 | -If someone tries to add an already registered email address for a team, |
| 448 | -a similar error will be shown. |
| 449 | - |
| 450 | - >>> browser.open( |
| 451 | - ... 'http://launchpad.dev/~landscape-developers/+contactaddress') |
| 452 | - >>> browser.getControl('Another e-mail address').selected = True |
| 453 | - >>> browser.getControl( |
| 454 | - ... name='field.contact_address').value = 'joe2@ubuntu.com' |
| 455 | - >>> browser.getControl('Change').click() |
| 456 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
| 457 | - There is 1 error. |
| 458 | - joe2@ubuntu.com is already registered in Launchpad... |
| 459 | |
| 460 | === removed file 'lib/lp/registry/stories/person/xx-person-delete-email.txt' |
| 461 | --- lib/lp/registry/stories/person/xx-person-delete-email.txt 2008-08-28 13:19:44 +0000 |
| 462 | +++ lib/lp/registry/stories/person/xx-person-delete-email.txt 1970-01-01 00:00:00 +0000 |
| 463 | @@ -1,23 +0,0 @@ |
| 464 | -= Removing an email address = |
| 465 | - |
| 466 | -Any email address other than the preferred one can be removed from |
| 467 | -a person's +editemails page. |
| 468 | - |
| 469 | - >>> browser = setupBrowser(auth='Basic test@canonical.com:test') |
| 470 | - >>> browser.open('http://launchpad.dev/~name12/+editemails') |
| 471 | - >>> browser.getControl('test@canonical.com').selected = True |
| 472 | - >>> browser.getControl('Remove', index=0).click() |
| 473 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
| 474 | - There is 1 error. |
| 475 | - You can't remove test@canonical.com because it's your contact email |
| 476 | - address. |
| 477 | - |
| 478 | - >>> browser.getControl('testing@canonical.com').selected = True |
| 479 | - >>> browser.getControl('Remove', index=0).click() |
| 480 | - >>> print "\n".join(get_feedback_messages(browser.contents)) |
| 481 | - The email address 'testing@canonical.com' has been removed. |
| 482 | - |
| 483 | - >>> browser.getControl('testing@canonical.com') |
| 484 | - Traceback (most recent call last): |
| 485 | - ... |
| 486 | - LookupError: label 'testing@canonical.com' |
| 487 | |
| 488 | === removed file 'lib/lp/registry/stories/person/xx-set-preferredemail.txt' |
| 489 | --- lib/lp/registry/stories/person/xx-set-preferredemail.txt 2010-06-18 23:25:36 +0000 |
| 490 | +++ lib/lp/registry/stories/person/xx-set-preferredemail.txt 1970-01-01 00:00:00 +0000 |
| 491 | @@ -1,64 +0,0 @@ |
| 492 | -================================ |
| 493 | -A person's contact email address |
| 494 | -================================ |
| 495 | - |
| 496 | -A person may have many confirmed email address which he may use to |
| 497 | -login with, but only one email address will be the contact email |
| 498 | -address. |
| 499 | - |
| 500 | - |
| 501 | -Setting the contact email address |
| 502 | -================================= |
| 503 | - |
| 504 | -Sample Person chooses to change his contact email address to the |
| 505 | -address he prefers to login with. |
| 506 | - |
| 507 | - >>> browser = setupBrowser(auth='Basic testing@canonical.com:test') |
| 508 | - >>> browser.open('http://launchpad.dev/~name12') |
| 509 | - >>> browser.getLink(url='+editemails').click() |
| 510 | - >>> print browser.url |
| 511 | - http://launchpad.dev/~name12/+editemails |
| 512 | - >>> print browser.title |
| 513 | - Change your e-mail settings... |
| 514 | - |
| 515 | -Sample Person has a second browser open to the same page; maybe he is |
| 516 | -absent minded. |
| 517 | - |
| 518 | - >>> second_browser = setupBrowser(auth='Basic testing@canonical.com:test') |
| 519 | - >>> second_browser.open('http://launchpad.dev/~name12/+editemails') |
| 520 | - |
| 521 | -His confirmed email addresses are listed for him to manage. His |
| 522 | -contact email address is selected, and always first. |
| 523 | - |
| 524 | - >>> print_radio_button_field(browser.contents, "VALIDATED_SELECTED") |
| 525 | - (*) test@canonical.com |
| 526 | - ( ) testing@canonical.com |
| 527 | - |
| 528 | -Sample Persons selects his testing email address and submits his choice |
| 529 | -with the "Set as Contact Address" button. |
| 530 | - |
| 531 | - >>> browser.getControl('testing@canonical.com').selected = True |
| 532 | - >>> browser.getControl('Set as Contact Address').click() |
| 533 | - |
| 534 | - >>> for msg in get_feedback_messages(browser.contents): |
| 535 | - ... print msg |
| 536 | - Your contact address has been changed to: testing@canonical.com |
| 537 | - |
| 538 | -His testing email address is now first in the list and selected |
| 539 | -as his contact address. |
| 540 | - |
| 541 | - >>> print_radio_button_field(browser.contents, "VALIDATED_SELECTED") |
| 542 | - (*) testing@canonical.com |
| 543 | - ( ) test@canonical.com |
| 544 | - |
| 545 | -In a moment of deja vu, Sample Person returns to his second browser, |
| 546 | -selects his testing email address again, then submits his choice. There |
| 547 | -is no change; he sees a message explaining that testing was already his |
| 548 | -contact address. |
| 549 | - |
| 550 | - >>> second_browser.getControl('testing@canonical.com').selected = True |
| 551 | - >>> second_browser.getControl('Set as Contact Address').click() |
| 552 | - |
| 553 | - >>> for msg in get_feedback_messages(second_browser.contents): |
| 554 | - ... print msg |
| 555 | - testing@canonical.com is already set as your contact address. |
| 556 | |
| 557 | === removed file 'lib/lp/registry/stories/person/xx-validate-email.txt' |
| 558 | --- lib/lp/registry/stories/person/xx-validate-email.txt 2012-04-11 14:40:02 +0000 |
| 559 | +++ lib/lp/registry/stories/person/xx-validate-email.txt 1970-01-01 00:00:00 +0000 |
| 560 | @@ -1,150 +0,0 @@ |
| 561 | -=========================== |
| 562 | -Validating an email address |
| 563 | -=========================== |
| 564 | - |
| 565 | -The user 'salgado' has an unvalidated email address that was probably |
| 566 | -added by gina. Now he wants to validate it. |
| 567 | - |
| 568 | - # Workaround for https://launchpad.net/launchpad/+bug/39016 |
| 569 | - >>> from lp.services.mail import stub |
| 570 | - >>> stub.test_emails[:] = [] |
| 571 | - |
| 572 | - >>> browser = setupBrowser(auth='Basic salgado@ubuntu.com:test') |
| 573 | - >>> browser.open('http://launchpad.dev/~salgado/+editemails') |
| 574 | - >>> print browser.title |
| 575 | - Change your e-mail settings... |
| 576 | - |
| 577 | - >>> browser.getControl(name="field.UNVALIDATED_SELECTED").getControl( |
| 578 | - ... value='salgado@ubuntu.com').selected = True |
| 579 | - >>> browser.getControl('Confirm').click() |
| 580 | - >>> for msg in get_feedback_messages(browser.contents): |
| 581 | - ... print msg |
| 582 | - An e-mail message was sent to 'salgado@ubuntu.com'... |
| 583 | - |
| 584 | -Retrieve the email and make sure it was sent to the right address. |
| 585 | - |
| 586 | - >>> import email |
| 587 | - >>> found = False |
| 588 | - >>> raw_msg = None |
| 589 | - >>> for from_addr, to_addrs, orig_msg in stub.test_emails: |
| 590 | - ... msg = email.message_from_string(orig_msg) |
| 591 | - ... if msg.get('to') == 'salgado@ubuntu.com': |
| 592 | - ... raw_msg = orig_msg |
| 593 | - ... found = True |
| 594 | - >>> assert found |
| 595 | - |
| 596 | -Visit the token URL mentioned in the email, and get redirected to |
| 597 | -+validateemail. |
| 598 | - |
| 599 | - >>> from lp.services.verification.tests.logintoken import ( |
| 600 | - ... get_token_url_from_email) |
| 601 | - >>> token_url = get_token_url_from_email(raw_msg) |
| 602 | - >>> browser.open(token_url) |
| 603 | - |
| 604 | - >>> print browser.url |
| 605 | - http://launchpad.dev/token/.../+validateemail |
| 606 | - >>> print browser.title |
| 607 | - Confirm e-mail address |
| 608 | - |
| 609 | - >>> print extract_text(find_main_content(browser.contents)) |
| 610 | - Confirm e-mail address |
| 611 | - Confirm e-mail address salgado@ubuntu.com |
| 612 | - |
| 613 | - >>> browser.getControl('Continue').click() |
| 614 | - >>> print browser.title |
| 615 | - Guilherme Salgado in Launchpad |
| 616 | - |
| 617 | -Check that the email address now shows up as validated. |
| 618 | - |
| 619 | - >>> browser.getLink(url='+editemails').click() |
| 620 | - >>> browser.getControl(name="field.VALIDATED_SELECTED").getControl( |
| 621 | - ... value='salgado@ubuntu.com') |
| 622 | - <ItemControl...optionValue='salgado@ubuntu.com'...> |
| 623 | - |
| 624 | - >>> browser.getControl(name="field.UNVALIDATED_SELECTED").getControl( |
| 625 | - ... value='salgado@ubuntu.com') |
| 626 | - Traceback (most recent call last): |
| 627 | - ... |
| 628 | - LookupError... |
| 629 | - |
| 630 | -An email address that the user adds manually should have the same |
| 631 | -validation workflow as an email address guessed by the system and |
| 632 | -added by gina. |
| 633 | - |
| 634 | - >>> browser.getControl('Add a new address').value = 'salgado@example.com' |
| 635 | - >>> browser.getControl('Add', index=1).click() |
| 636 | - >>> browser.url |
| 637 | - 'http://launchpad.dev/%7Esalgado/+editemails' |
| 638 | - |
| 639 | - >>> browser.getControl(name="field.UNVALIDATED_SELECTED").getControl( |
| 640 | - ... value='salgado@example.com').selected = True |
| 641 | - >>> browser.getControl('Confirm').click() |
| 642 | - >>> for msg in get_feedback_messages(browser.contents): |
| 643 | - ... print msg |
| 644 | - An e-mail message was sent to 'salgado@example.com'... |
| 645 | - |
| 646 | - |
| 647 | -Validating the email address string |
| 648 | -=================================== |
| 649 | - |
| 650 | -Leaving the email address field blank and hitting the 'Add' button |
| 651 | -should display an error message. |
| 652 | - |
| 653 | - >>> browser.getControl('Add a new address').value |
| 654 | - '' |
| 655 | - |
| 656 | - >>> browser.getControl('Add', index=1).click() |
| 657 | - >>> print browser.title |
| 658 | - Change your e-mail settings... |
| 659 | - |
| 660 | - >>> for msg in get_feedback_messages(browser.contents): |
| 661 | - ... print msg |
| 662 | - There is 1 error. |
| 663 | - Required input is missing. |
| 664 | - |
| 665 | -Entering a string that does not look like an email address causes an |
| 666 | -error to be displayed. |
| 667 | - |
| 668 | - >>> print browser.title |
| 669 | - Change your e-mail settings... |
| 670 | - |
| 671 | - >>> browser.getControl('Add a new address').value = 'foo' |
| 672 | - >>> browser.getControl('Add', index=1).click() |
| 673 | - >>> print browser.title |
| 674 | - Change your e-mail settings... |
| 675 | - |
| 676 | - >>> for msg in get_feedback_messages(browser.contents): |
| 677 | - ... print msg |
| 678 | - There is 1 error. |
| 679 | - 'foo' doesn't seem to be a valid email address. |
| 680 | - |
| 681 | -Markup in new addresses is escaped in error messages so that it cannot |
| 682 | -be interpreted by the browser. Scripts that are embedded in the address |
| 683 | -will not run. A malicious hacker cannot use a XSS vulnerability to gain |
| 684 | -information about Launchpad users. |
| 685 | - |
| 686 | - >>> print browser.title |
| 687 | - Change your e-mail settings... |
| 688 | - |
| 689 | - >>> browser.getControl('Add a new address').value = ( |
| 690 | - ... "salgado@example.com<br/><script>window.alert('XSS')</script>") |
| 691 | - >>> browser.getControl('Add', index=1).click() |
| 692 | - >>> for msg in get_feedback_messages(browser.contents): |
| 693 | - ... print msg |
| 694 | - There is 1 error. |
| 695 | - 'salgado...com<br/><script>window.alert('XSS')</script>' |
| 696 | - doesn't seem to be a valid email address. |
| 697 | - |
| 698 | - |
| 699 | -+editemails for teams |
| 700 | -===================== |
| 701 | - |
| 702 | -Because people and teams share the same namespace, it used to be possible to |
| 703 | -hack the url to get to a team's +editemails page. This makes no sense, and |
| 704 | -for teams you should use the +contactaddress url. The +editemails page is no |
| 705 | -longer available for teams. |
| 706 | - |
| 707 | - >>> browser.open('http://launchpad.dev/~guadamen/+editemails') |
| 708 | - Traceback (most recent call last): |
| 709 | - ... |
| 710 | - NotFound: Object: <...>, name: u'+editemails' |

Thanks for killing doctests, my mortal enemies!
- I wondering if there's reasoning for the differences in the test helpers. Things like createSetContac tViaAddEmailVie w are camel cased, but then there's _assertEmailAnd Error. Should they all be _ prefixed 'private' methods?
- What's the unicode inducing .doc method? (#73)
- The test methods should have some comment for use when failing and displaying the error per the test style guide: https:/ /dev.launchpad. net/TestsStyleG uide