Merge lp:~mwhudson/launchpad/test_traverse-set-participation-bug-611570 into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Gary Poster
Approved revision: no longer in the source branch.
Merged at revision: 11300
Proposed branch: lp:~mwhudson/launchpad/test_traverse-set-participation-bug-611570
Merge into: lp:launchpad
Diff against target: 142 lines (+106/-2)
2 files modified
lib/lp/testing/publication.py (+14/-2)
lib/lp/testing/tests/test_publication.py (+92/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/test_traverse-set-participation-bug-611570
Reviewer Review Type Date Requested Status
Gary Poster (community) Approve
Review via email: mp+31731@code.launchpad.net

Commit message

Add tests for the test_traverse helper and make it set up its own interaction properly

Description of the change

Hi,

This branch started out by making test_traverse set up a new interaction for the traversal it performs, so as to make the request that is being traversed 'current'. Then I wrote some more tests and discovered that, at least in some sense, the docstring's claim that it 'uses the current user' was inaccurate, so I fixed that too.

The key test helper I use is bonkers, but I think that's zope's fault (you can read the implementation of the browser:page in zope.browserpage.metaconfigure to see where I cribbed the insanity from).

Cheers,
mwh

To post a comment you must log in.
Revision history for this message
Gary Poster (gary) wrote :

Very nice, thank you.

FWIW, yes, browser:page was disparaged and eliminated from use at my last job (ZC), because of the dynamic class creation. (The newer approach is to subclass from zope.publisher.browser.BrowserPage, define your own __call__, and simply register the resulting class as an adapter.) That's what I/we didn't like about it, anyway.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/testing/publication.py'
2--- lib/lp/testing/publication.py 2010-04-28 10:13:00 +0000
3+++ lib/lp/testing/publication.py 2010-08-05 03:21:07 +0000
4@@ -15,13 +15,17 @@
5 # Z3 doesn't make this available as a utility.
6 from zope.app import zapi
7 from zope.app.publication.requestpublicationregistry import factoryRegistry
8+from zope.app.security.interfaces import IUnauthenticatedPrincipal
9 from zope.component import getUtility
10 from zope.interface import providedBy
11 from zope.publisher.interfaces.browser import IDefaultSkin
12+from zope.security.management import restoreInteraction
13
14 from canonical.launchpad.interfaces.launchpad import IOpenLaunchBag
15 import canonical.launchpad.layers as layers
16 from canonical.launchpad.webapp import urlsplit
17+from canonical.launchpad.webapp.interaction import (
18+ get_current_principal, setupInteraction)
19 from canonical.launchpad.webapp.servers import ProtocolErrorPublication
20
21
22@@ -103,8 +107,13 @@
23 if layer is not None:
24 layers.setAdditionalLayer(request, layer)
25
26- principal = publication.getPrincipal(request)
27- request.setPrincipal(principal)
28+ principal = get_current_principal()
29+
30+ if IUnauthenticatedPrincipal.providedBy(principal):
31+ login = None
32+ else:
33+ login = principal.person
34+ setupInteraction(principal, login, request)
35
36 getUtility(IOpenLaunchBag).clear()
37 app = publication.getApplication(request)
38@@ -112,4 +121,7 @@
39 # Since the last traversed object is the view, the second last should be
40 # the object that the view is on.
41 obj = request.traversed_objects[-2]
42+
43+ restoreInteraction()
44+
45 return obj, view, request
46
47=== added file 'lib/lp/testing/tests/test_publication.py'
48--- lib/lp/testing/tests/test_publication.py 1970-01-01 00:00:00 +0000
49+++ lib/lp/testing/tests/test_publication.py 2010-08-05 03:21:07 +0000
50@@ -0,0 +1,92 @@
51+# Copyright 2010 Canonical Ltd. This software is licensed under the
52+# GNU Affero General Public License version 3 (see the file LICENSE).
53+
54+"""Tests for the helpers in `lp.testing.publication`."""
55+
56+__metaclass__ = type
57+
58+from zope.app.pagetemplate.simpleviewclass import simple
59+from zope.component import getSiteManager, getUtility
60+from zope.interface import Interface
61+from zope.publisher.interfaces.browser import IDefaultBrowserLayer
62+from zope.security.checker import CheckerPublic, Checker, defineChecker
63+
64+from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
65+from canonical.launchpad.webapp.interfaces import ILaunchBag
66+from canonical.launchpad.webapp.publisher import get_current_browser_request
67+from canonical.launchpad.webapp.servers import LaunchpadTestRequest
68+from canonical.testing import DatabaseFunctionalLayer
69+from lp.testing import ANONYMOUS, login, login_person, TestCaseWithFactory
70+from lp.testing.publication import test_traverse
71+
72+
73+class TestTestTraverse(TestCaseWithFactory):
74+ # Tests for `test_traverse`
75+
76+ layer = DatabaseFunctionalLayer
77+
78+ def registerViewCallable(self, view_callable):
79+ """Return a URL traversing to which will call `view_callable`.
80+
81+ :param view_callable: Will be called with no arguments during
82+ traversal.
83+ """
84+ # This method is completely out of control. Thanks, Zope.
85+ name = '+' + self.factory.getUniqueString()
86+ class new_class(simple):
87+ def __init__(self, context, request):
88+ view_callable()
89+ required = {}
90+ for n in ('browserDefault', '__call__', 'publishTraverse'):
91+ required[n] = CheckerPublic
92+ defineChecker(new_class, Checker(required))
93+ getSiteManager().registerAdapter(
94+ new_class, (ILaunchpadRoot, IDefaultBrowserLayer), Interface,
95+ name)
96+ self.addCleanup(
97+ getSiteManager().unregisterAdapter, new_class,
98+ (ILaunchpadRoot, IDefaultBrowserLayer), Interface, name)
99+ return 'https://launchpad.dev/' + name
100+
101+ def test_traverse_simple(self):
102+ # test_traverse called with a product URL returns the product
103+ # as the traversed object.
104+ login(ANONYMOUS)
105+ product = self.factory.makeProduct()
106+ context, view, request = test_traverse(
107+ 'https://launchpad.dev/' + product.name)
108+ self.assertEqual(product, context)
109+
110+ def test_request_is_current_during_traversal(self):
111+ # The request that test_traverse creates is current during
112+ # traversal in the sense of get_current_browser_request.
113+ login(ANONYMOUS)
114+ requests = []
115+ def record_current_request():
116+ requests.append(get_current_browser_request())
117+ context, view, request = test_traverse(
118+ self.registerViewCallable(record_current_request))
119+ self.assertEqual(1, len(requests))
120+ self.assertIs(request, requests[0])
121+
122+ def test_participation_restored(self):
123+ # test_traverse restores the interaction (and hence
124+ # participation) that was present before it was called.
125+ request = LaunchpadTestRequest()
126+ login(ANONYMOUS, request)
127+ product = self.factory.makeProduct()
128+ test_traverse('https://launchpad.dev/' + product.name)
129+ self.assertIs(request, get_current_browser_request())
130+
131+ def test_uses_current_user(self):
132+ # test_traverse performs the traversal as the currently logged
133+ # in user.
134+ person = self.factory.makePerson()
135+ login_person(person)
136+ users = []
137+ def record_user():
138+ users.append(getUtility(ILaunchBag).user)
139+ context, view, request = test_traverse(
140+ self.registerViewCallable(record_user))
141+ self.assertEqual(1, len(users))
142+ self.assertEqual(person, users[0])