Merge lp:~jml/launchpad/more-login-helpers into lp:launchpad

Proposed by Jonathan Lange
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11122
Proposed branch: lp:~jml/launchpad/more-login-helpers
Merge into: lp:launchpad
Diff against target: 411 lines (+278/-18)
6 files modified
lib/lp/code/model/tests/test_codeimportjob.py (+4/-5)
lib/lp/testing/__init__.py (+5/-4)
lib/lp/testing/_login.py (+73/-7)
lib/lp/testing/tests/test_login.py (+191/-0)
lib/lp/translations/tests/test_hastranslationtemplates.py (+3/-1)
lib/lp/translations/tests/test_pofile.py (+2/-1)
To merge this branch: bzr merge lp:~jml/launchpad/more-login-helpers
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+29595@code.launchpad.net

Commit message

Add login_team, login_as and login_celebrity helpers.

Description of the change

This branch adds a few new login test helpers: login_team, login_as and login_celebrity.

login_team logs you in as an arbitrary member of a team. login_as logs you in as pretty much whatever you give it: None, ANONYMOUS, a person or a team. login_celebrity will log you in as whatever celebrity you provide.

This branch also has another big advantage: tests! For the first time ever, the login helpers themselves have unit tests.

I did some drive-by cleanup in lp/testing/__init__ too, getting rid of an unnecessary re-export.

Probably the weirdest thing about this branch is the way I figure out the currently logged in user. As far as I can tell, we have two mechanisms: inspecting the zope security policy and checking the launch bag. I've used both to be extra safe, but would happily be convinced to use only one.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

+ ANONYMOUS are equivalent, and will log the person is as the anonymous

typo - ITYM 'in as'

get_arbitrary should return different members each time I think. At the moment it will give a false sense of arbitrariness.

If you don't like random(), perhaps walking the team list from end to start would do (so that the owner isn't always the first one returned).

Rather than an XXX, I think you've just written some clear explanation of a bit of cruft you're tolerating - thats fine. As I understand XXX's in the lp code base they should be attached to something to fix - and in this case I'd put it on the 'launch bag' saying 'this duplicates the security context and should be deleted'.

Finally, I'd really like a single class rather than these separate functions, but that would be future work, its not needed for this patch - this patch is a clear improvement on its own.

-Rob

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/model/tests/test_codeimportjob.py'
--- lib/lp/code/model/tests/test_codeimportjob.py 2010-05-14 01:46:38 +0000
+++ lib/lp/code/model/tests/test_codeimportjob.py 2010-07-13 10:01:07 +0000
@@ -36,14 +36,14 @@
36 ICodeImportJobSet, ICodeImportJobWorkflow)36 ICodeImportJobSet, ICodeImportJobWorkflow)
37from lp.code.interfaces.codeimportresult import ICodeImportResult37from lp.code.interfaces.codeimportresult import ICodeImportResult
38from lp.registry.interfaces.person import IPersonSet38from lp.registry.interfaces.person import IPersonSet
39from lp.testing import ANONYMOUS, login, logout, TestCaseWithFactory39from lp.testing import (
40 ANONYMOUS, login, login_celebrity, logout, TestCaseWithFactory)
40from canonical.launchpad.testing.codeimporthelpers import (41from canonical.launchpad.testing.codeimporthelpers import (
41 make_finished_import, make_running_import)42 make_finished_import, make_running_import)
42from canonical.launchpad.testing.pages import get_feedback_messages43from canonical.launchpad.testing.pages import get_feedback_messages
43from canonical.launchpad.webapp import canonical_url44from canonical.launchpad.webapp import canonical_url
44from canonical.librarian.interfaces import ILibrarianClient45from canonical.librarian.interfaces import ILibrarianClient
45from canonical.testing import (46from canonical.testing import LaunchpadFunctionalLayer
46 LaunchpadFunctionalLayer, LaunchpadZopelessLayer)
4747
4848
49def login_for_code_imports():49def login_for_code_imports():
@@ -52,8 +52,7 @@
52 CodeImports are currently hidden from regular users currently. Members of52 CodeImports are currently hidden from regular users currently. Members of
53 the vcs-imports team and can access the objects freely.53 the vcs-imports team and can access the objects freely.
54 """54 """
55 # David Allouche is a member of the vcs-imports team.55 login_celebrity('vcs_imports')
56 login('david.allouche@canonical.com')
5756
5857
59class TestCodeImportJobSet(unittest.TestCase):58class TestCodeImportJobSet(unittest.TestCase):
6059
=== modified file 'lib/lp/testing/__init__.py'
--- lib/lp/testing/__init__.py 2010-06-28 20:30:32 +0000
+++ lib/lp/testing/__init__.py 2010-07-13 10:01:07 +0000
@@ -19,7 +19,10 @@
19 'launchpadlib_for',19 'launchpadlib_for',
20 'launchpadlib_credentials_for',20 'launchpadlib_credentials_for',
21 'login',21 'login',
22 'login_as',
23 'login_celebrity',
22 'login_person',24 'login_person',
25 'login_team',
23 'logout',26 'logout',
24 'map_branch_contents',27 'map_branch_contents',
25 'normalize_whitespace',28 'normalize_whitespace',
@@ -33,9 +36,6 @@
33 'test_tales',36 'test_tales',
34 'time_counter',37 'time_counter',
35 'unlink_source_packages',38 'unlink_source_packages',
36 # XXX: This really shouldn't be exported from here. People should import
37 # it from Zope.
38 'verifyObject',
39 'validate_mock_class',39 'validate_mock_class',
40 'WindmillTestCase',40 'WindmillTestCase',
41 'with_anonymous_login',41 'with_anonymous_login',
@@ -92,7 +92,8 @@
92# Import the login and logout functions here as it is a much better92# Import the login and logout functions here as it is a much better
93# place to import them from in tests.93# place to import them from in tests.
94from lp.testing._login import (94from lp.testing._login import (
95 is_logged_in, login, login_person, logout)95 is_logged_in, login, login_as, login_celebrity, login_person, login_team,
96 logout)
96# canonical.launchpad.ftests expects test_tales to be imported from here.97# canonical.launchpad.ftests expects test_tales to be imported from here.
97# XXX: JonathanLange 2010-01-01: Why?!98# XXX: JonathanLange 2010-01-01: Why?!
98from lp.testing._tales import test_tales99from lp.testing._tales import test_tales
99100
=== modified file 'lib/lp/testing/_login.py'
--- lib/lp/testing/_login.py 2010-04-15 21:07:53 +0000
+++ lib/lp/testing/_login.py 2010-07-13 10:01:07 +0000
@@ -1,21 +1,32 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4# We like global statements!4# We like global statements!
5# pylint: disable-msg=W0602,W06035# pylint: disable-msg=W0602,W0603
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = [
9 'login',
10 'login_as',
11 'login_celebrity',
12 'login_person',
13 'login_team',
14 'logout',
15 'is_logged_in',
16 ]
17
18import random
19
20from zope.component import getUtility
8from zope.security.management import endInteraction21from zope.security.management import endInteraction
22from zope.security.proxy import removeSecurityProxy
23
24from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
9from canonical.launchpad.webapp.interaction import (25from canonical.launchpad.webapp.interaction import (
10 setupInteractionByEmail, setupInteractionForPerson)26 ANONYMOUS, setupInteractionByEmail, setupInteractionForPerson)
11from canonical.launchpad.webapp.servers import LaunchpadTestRequest27from canonical.launchpad.webapp.servers import LaunchpadTestRequest
12from canonical.launchpad.webapp.vhosts import allvhosts28from canonical.launchpad.webapp.vhosts import allvhosts
1329
14__all__ = [
15 'login',
16 'login_person',
17 'logout',
18 'is_logged_in']
1930
2031
21_logged_in = False32_logged_in = False
@@ -61,10 +72,65 @@
6172
62def login_person(person, participation=None):73def login_person(person, participation=None):
63 """Login the person with their preferred email."""74 """Login the person with their preferred email."""
75 if person is not None:
76 # The login will fail even without this check, but this gives us a
77 # nice error message, which can save time when debugging.
78 if getattr(person, 'is_team', None):
79 raise ValueError("Got team, expected person: %r" % (person,))
64 participation = _test_login_impl(participation)80 participation = _test_login_impl(participation)
65 setupInteractionForPerson(person, participation)81 setupInteractionForPerson(person, participation)
6682
6783
84def _get_arbitrary_team_member(team):
85 """Get an arbitrary member of 'team'.
86
87 :param team: An `ITeam`.
88 """
89 # Set up the interaction.
90 login(ANONYMOUS)
91 return random.choice(list(team.allmembers))
92
93
94def login_team(team, participation=None):
95 """Login as a member of 'team'."""
96 # This check isn't strictly necessary (it depends on the implementation of
97 # _get_arbitrary_team_member), but this gives us a nice error message,
98 # which can save time when debugging.
99 if not team.is_team:
100 raise ValueError("Got person, expected team: %r" % (team,))
101 person = _get_arbitrary_team_member(team)
102 login_person(person, participation=participation)
103 return person
104
105
106def login_as(person_or_team, participation=None):
107 """Login as a person or a team.
108
109 :param person_or_team: A person, a team, ANONYMOUS or None. None and
110 ANONYMOUS are equivalent, and will log the person in as the anonymous
111 user.
112 """
113 if person_or_team == ANONYMOUS:
114 login_method = login
115 elif person_or_team is None:
116 login_method = login_person
117 elif person_or_team.is_team:
118 login_method = login_team
119 else:
120 login_method = login_person
121 return login_method(person_or_team, participation=participation)
122
123
124def login_celebrity(celebrity_name, participation=None):
125 """Login as a celebrity."""
126 login(ANONYMOUS)
127 celebs = getUtility(ILaunchpadCelebrities)
128 celeb = getattr(celebs, celebrity_name, None)
129 if celeb is None:
130 raise ValueError("No such celebrity: %r" % (celebrity_name,))
131 return login_as(celeb, participation=participation)
132
133
68def logout():134def logout():
69 """Tear down after login(...), ending the current interaction.135 """Tear down after login(...), ending the current interaction.
70136
71137
=== added file 'lib/lp/testing/tests/test_login.py'
--- lib/lp/testing/tests/test_login.py 1970-01-01 00:00:00 +0000
+++ lib/lp/testing/tests/test_login.py 2010-07-13 10:01:07 +0000
@@ -0,0 +1,191 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the login helpers."""
5
6__metaclass__ = type
7
8import unittest
9
10from zope.app.security.interfaces import IUnauthenticatedPrincipal
11from zope.component import getUtility
12
13from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
14from canonical.launchpad.webapp.interaction import get_current_principal
15from canonical.launchpad.webapp.interfaces import IOpenLaunchBag
16from canonical.testing.layers import DatabaseFunctionalLayer
17from lp.testing import (
18 ANONYMOUS, is_logged_in, login, login_as, login_celebrity, login_person,
19 login_team, logout)
20from lp.testing import TestCaseWithFactory
21
22
23class TestLoginHelpers(TestCaseWithFactory):
24
25 layer = DatabaseFunctionalLayer
26
27 def getLoggedInPerson(self):
28 """Return the currently logged-in person.
29
30 If no one is logged in, return None. If there is an anonymous user
31 logged in, then return ANONYMOUS. Otherwise, return the logged-in
32 `IPerson`.
33 """
34 # I don't really know the canonical way of asking for "the logged-in
35 # person", so instead I'm using all the ways I can find and making
36 # sure they match each other. -- jml
37 by_launchbag = getUtility(IOpenLaunchBag).user
38 principal = get_current_principal()
39 if principal is None:
40 return None
41 elif IUnauthenticatedPrincipal.providedBy(principal):
42 if by_launchbag is None:
43 return ANONYMOUS
44 else:
45 raise ValueError(
46 "Unauthenticated principal, but launchbag thinks "
47 "%r is logged in." % (by_launchbag,))
48 else:
49 by_principal = principal.person
50 self.assertEqual(by_launchbag, by_principal)
51 return by_principal
52
53 def assertLoggedIn(self, person):
54 """Assert that 'person' is logged in."""
55 self.assertEqual(person, self.getLoggedInPerson())
56
57 def assertLoggedOut(self):
58 """Assert that no one is currently logged in."""
59 self.assertIs(None, get_current_principal())
60 self.assertIs(None, getUtility(IOpenLaunchBag).user)
61
62 def test_not_logged_in(self):
63 # After logout has been called, we are not logged in.
64 logout()
65 self.assertEqual(False, is_logged_in())
66 self.assertLoggedOut()
67
68 def test_logout_twice(self):
69 # Logging out twice don't harm anybody none.
70 logout()
71 logout()
72 self.assertEqual(False, is_logged_in())
73 self.assertLoggedOut()
74
75 def test_logged_in(self):
76 # After login has been called, we are logged in.
77 login_person(self.factory.makePerson())
78 self.assertEqual(True, is_logged_in())
79
80 def test_login_person_actually_logs_in(self):
81 # login_person changes the currently logged in person.
82 person = self.factory.makePerson()
83 logout()
84 login_person(person)
85 self.assertLoggedIn(person)
86
87 def test_login_different_person_overrides(self):
88 # Calling login_person a second time with a different person changes
89 # the currently logged in user.
90 a = self.factory.makePerson()
91 b = self.factory.makePerson()
92 logout()
93 login_person(a)
94 login_person(b)
95 self.assertLoggedIn(b)
96
97 def test_login_person_with_team(self):
98 # Calling login_person with a team raises a nice error.
99 team = self.factory.makeTeam()
100 e = self.assertRaises(ValueError, login_person, team)
101 self.assertEqual(str(e), "Got team, expected person: %r" % (team,))
102
103 def test_login_account(self):
104 # Calling login_person with an account logs you in with that account.
105 person = self.factory.makePerson()
106 account = person.account
107 login_person(account)
108 self.assertLoggedIn(person)
109
110 def test_login_with_email(self):
111 # login() logs a person in by email.
112 person = self.factory.makePerson()
113 email = person.preferredemail.email
114 logout()
115 login(email)
116 self.assertLoggedIn(person)
117
118 def test_login_anonymous(self):
119 # login as 'ANONYMOUS' logs in as the anonymous user.
120 logout()
121 login(ANONYMOUS)
122 self.assertLoggedIn(ANONYMOUS)
123
124 def test_login_team(self):
125 # login_team() logs in as a member of the given team.
126 team = self.factory.makeTeam()
127 logout()
128 login_team(team)
129 person = self.getLoggedInPerson()
130 self.assertTrue(person.inTeam(team))
131
132 def test_login_team_with_person(self):
133 # Calling login_team() with a person instead of a team raises a nice
134 # error.
135 person = self.factory.makePerson()
136 logout()
137 e = self.assertRaises(ValueError, login_team, person)
138 self.assertEqual(str(e), "Got person, expected team: %r" % (person,))
139
140 def test_login_team_returns_logged_in_person(self):
141 # login_team returns the logged-in person.
142 team = self.factory.makeTeam()
143 logout()
144 person = login_team(team)
145 self.assertLoggedIn(person)
146
147 def test_login_as_person(self):
148 # login_as() logs in as a person if it's given a person.
149 person = self.factory.makePerson()
150 logout()
151 login_as(person)
152 self.assertLoggedIn(person)
153
154 def test_login_as_team(self):
155 # login_as() logs in as a member of a team if it's given a team.
156 team = self.factory.makeTeam()
157 logout()
158 login_as(team)
159 person = self.getLoggedInPerson()
160 self.assertTrue(person.inTeam(team))
161
162 def test_login_as_anonymous(self):
163 # login_as(ANONYMOUS) logs in as the anonymous user.
164 logout()
165 login_as(ANONYMOUS)
166 self.assertLoggedIn(ANONYMOUS)
167
168 def test_login_as_None(self):
169 # login_as(None) logs in as the anonymous user.
170 logout()
171 login_as(None)
172 self.assertLoggedIn(ANONYMOUS)
173
174 def test_login_celebrity(self):
175 # login_celebrity logs in a celebrity.
176 logout()
177 login_celebrity('vcs_imports')
178 vcs_imports = getUtility(ILaunchpadCelebrities).vcs_imports
179 person = self.getLoggedInPerson()
180 self.assertTrue(person.inTeam, vcs_imports)
181
182 def test_login_nonexistent_celebrity(self):
183 # login_celebrity raises ValueError when called with a non-existent
184 # celebrity.
185 logout()
186 e = self.assertRaises(ValueError, login_celebrity, 'nonexistent')
187 self.assertEqual(str(e), "No such celebrity: 'nonexistent'")
188
189
190def test_suite():
191 return unittest.TestLoader().loadTestsFromName(__name__)
0192
=== modified file 'lib/lp/translations/tests/test_hastranslationtemplates.py'
--- lib/lp/translations/tests/test_hastranslationtemplates.py 2009-07-17 02:25:09 +0000
+++ lib/lp/translations/tests/test_hastranslationtemplates.py 2010-07-13 10:01:07 +0000
@@ -5,11 +5,13 @@
55
6import unittest6import unittest
77
8from zope.interface.verify import verifyObject
9
8from canonical.testing import ZopelessDatabaseLayer10from canonical.testing import ZopelessDatabaseLayer
9from lp.translations.interfaces.potemplate import IHasTranslationTemplates11from lp.translations.interfaces.potemplate import IHasTranslationTemplates
10from lp.translations.interfaces.translationfileformat import (12from lp.translations.interfaces.translationfileformat import (
11 TranslationFileFormat)13 TranslationFileFormat)
12from lp.testing import TestCaseWithFactory, verifyObject14from lp.testing import TestCaseWithFactory
1315
1416
15class HasTranslationTemplatesTestMixin(TestCaseWithFactory):17class HasTranslationTemplatesTestMixin(TestCaseWithFactory):
1618
=== modified file 'lib/lp/translations/tests/test_pofile.py'
--- lib/lp/translations/tests/test_pofile.py 2010-04-23 14:46:43 +0000
+++ lib/lp/translations/tests/test_pofile.py 2010-07-13 10:01:07 +0000
@@ -11,6 +11,7 @@
11from unittest import TestLoader11from unittest import TestLoader
1212
13from zope.component import getAdapter, getUtility13from zope.component import getAdapter, getUtility
14from zope.interface.verify import verifyObject
14from zope.security.proxy import removeSecurityProxy15from zope.security.proxy import removeSecurityProxy
1516
16from lp.translations.interfaces.pofile import IPOFileSet17from lp.translations.interfaces.pofile import IPOFileSet
@@ -20,7 +21,7 @@
20 TranslationValidationStatus)21 TranslationValidationStatus)
21from lp.translations.interfaces.translationcommonformat import (22from lp.translations.interfaces.translationcommonformat import (
22 ITranslationFileData)23 ITranslationFileData)
23from lp.testing import TestCaseWithFactory, verifyObject24from lp.testing import TestCaseWithFactory
24from canonical.testing import LaunchpadZopelessLayer, ZopelessDatabaseLayer25from canonical.testing import LaunchpadZopelessLayer, ZopelessDatabaseLayer
25from canonical.launchpad.webapp.publisher import canonical_url26from canonical.launchpad.webapp.publisher import canonical_url
2627