Merge lp:~deryck/launchpad/refactor-editemail-doctest-363916 into lp:launchpad

Proposed by Deryck Hodge on 2012-04-26
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
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: mp+103718@code.launchpad.net

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.

To post a comment you must log in.
Richard Harding (rharding) wrote :

Thanks for killing doctests, my mortal enemies!

- I wondering if there's reasoning for the differences in the test helpers. Things like createSetContactViaAddEmailView are camel cased, but then there's _assertEmailAndError. 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/TestsStyleGuide

review: Approve (code*)
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.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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&lt;script&gt;"
260+ "window.alert('XSS')&lt;/script&gt;'"
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&lt;br/&gt;&lt;script&gt;window.alert('XSS')&lt;/script&gt;'
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'