Merge ~cjwatson/launchpad:branch-repository-api-redirects into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 4337fd11ef269da02dc2274ba8158b8f24ed112d
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:branch-repository-api-redirects
Merge into: launchpad:master
Diff against target: 324 lines (+115/-22)
5 files modified
lib/lp/app/browser/launchpad.py (+4/-4)
lib/lp/app/browser/tests/test_launchpad.py (+22/-5)
lib/lp/code/browser/tests/test_branchtraversal.py (+23/-9)
lib/lp/registry/browser/person.py (+8/-4)
lib/lp/registry/browser/tests/test_person.py (+58/-0)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+392544@code.launchpad.net

Commit message

Send proper webservice redirects for branches and repositories

Description of the change

launchpadlib handles this correctly nowadays, but only if we send redirects to the correct target host (e.g. api.launchpad.net rather than code.launchpad.net).

To post a comment you must log in.
Revision history for this message
Ioana Lasc (ilasc) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/app/browser/launchpad.py b/lib/lp/app/browser/launchpad.py
2index d31df20..eb4b9bb 100644
3--- a/lib/lp/app/browser/launchpad.py
4+++ b/lib/lp/app/browser/launchpad.py
5@@ -1,4 +1,4 @@
6-# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
7+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """Browser code for the launchpad application."""
11@@ -739,7 +739,7 @@ class LaunchpadRootNavigation(Navigation):
12 path = '/'.join(self.request.stepstogo)
13 try:
14 branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
15- target_url = canonical_url(branch)
16+ target_url = canonical_url(branch, request=self.request)
17 if trailing != '':
18 target_url = urlappend(target_url, trailing)
19 except (NoLinkedBranch) as e:
20@@ -789,7 +789,7 @@ class LaunchpadRootNavigation(Navigation):
21 try:
22 repository, trailing = getUtility(IGitLookup).getByPath(path)
23 if repository is not None:
24- target_url = canonical_url(repository)
25+ target_url = canonical_url(repository, request=self.request)
26 if trailing:
27 target_url = urlappend(target_url, trailing)
28 except (InvalidNamespace, InvalidProductName, NameLookupFailed,
29@@ -802,7 +802,7 @@ class LaunchpadRootNavigation(Navigation):
30 # Attempt a bzr lookup as well:
31 try:
32 branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
33- bzr_url = canonical_url(branch)
34+ bzr_url = canonical_url(branch, request=self.request)
35 if trailing != '':
36 bzr_url = urlappend(bzr_url, trailing)
37
38diff --git a/lib/lp/app/browser/tests/test_launchpad.py b/lib/lp/app/browser/tests/test_launchpad.py
39index a9c6adf..b170634 100644
40--- a/lib/lp/app/browser/tests/test_launchpad.py
41+++ b/lib/lp/app/browser/tests/test_launchpad.py
42@@ -36,7 +36,10 @@ from lp.services.webapp.interfaces import (
43 BrowserNotificationLevel,
44 ILaunchpadRoot,
45 )
46-from lp.services.webapp.servers import LaunchpadTestRequest
47+from lp.services.webapp.servers import (
48+ LaunchpadTestRequest,
49+ WebServiceTestRequest,
50+ )
51 from lp.services.webapp.url import urlappend
52 from lp.testing import (
53 admin_logged_in,
54@@ -108,11 +111,12 @@ class TraversalMixin:
55 NotFound, self.traverse, path,
56 use_default_referer=use_default_referer)
57
58- def assertRedirects(self, segments, url):
59- redirection = self.traverse(segments)
60+ def assertRedirects(self, segments, url, webservice=False):
61+ redirection = self.traverse(segments, webservice=webservice)
62 self.assertEqual(url, redirection.target)
63
64- def traverse(self, path, first_segment, use_default_referer=True):
65+ def traverse(self, path, first_segment, use_default_referer=True,
66+ webservice=False):
67 """Traverse to 'path' using a 'LaunchpadRootNavigation' object.
68
69 Using the Zope traversal machinery, traverse to the path given by
70@@ -125,6 +129,8 @@ class TraversalMixin:
71 :param use_default_referer: If True, set the referer attribute in the
72 request header to DEFAULT_REFERER = "http://launchpad.test"
73 (otherwise it remains as None)
74+ :param webservice: If True, use a webservice-like request rather
75+ than a normal test request.
76 :return: The object found.
77 """
78 # XXX: What's the difference between first_segment and path? -- mbp
79@@ -132,7 +138,9 @@ class TraversalMixin:
80 extra = {'PATH_INFO': urlappend('/%s' % first_segment, path)}
81 if use_default_referer:
82 extra['HTTP_REFERER'] = DEFAULT_REFERER
83- request = LaunchpadTestRequest(**extra)
84+ request_factory = (
85+ WebServiceTestRequest if webservice else LaunchpadTestRequest)
86+ request = request_factory(**extra)
87 segments = reversed(path.split('/'))
88 request.setTraversalStack(segments)
89 traverser = LaunchpadRootNavigation(
90@@ -168,6 +176,9 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
91 # branch.
92 branch = self.factory.makeAnyBranch()
93 self.assertRedirects(branch.unique_name, canonical_url(branch))
94+ self.assertRedirects(
95+ branch.unique_name, canonical_url(branch, rootsite='api'),
96+ webservice=True)
97
98 def test_no_such_unique_name(self):
99 # Traversing to /+branch/<unique_name> where 'unique_name' is for a
100@@ -346,10 +357,16 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
101 def test_project_bzr_branch(self):
102 branch = self.factory.makeAnyBranch()
103 self.assertRedirects(branch.unique_name, canonical_url(branch))
104+ self.assertRedirects(
105+ branch.unique_name, canonical_url(branch, rootsite='api'),
106+ webservice=True)
107
108 def test_project_git_branch(self):
109 git_repo = self.factory.makeGitRepository()
110 self.assertRedirects(git_repo.unique_name, canonical_url(git_repo))
111+ self.assertRedirects(
112+ git_repo.unique_name, canonical_url(git_repo, rootsite='api'),
113+ webservice=True)
114
115 def test_no_such_bzr_unique_name(self):
116 branch = self.factory.makeAnyBranch()
117diff --git a/lib/lp/code/browser/tests/test_branchtraversal.py b/lib/lp/code/browser/tests/test_branchtraversal.py
118index 98c9f75..c26a699 100644
119--- a/lib/lp/code/browser/tests/test_branchtraversal.py
120+++ b/lib/lp/code/browser/tests/test_branchtraversal.py
121@@ -16,10 +16,11 @@ from lp.registry.interfaces.personproduct import (
122 IPersonProductFactory,
123 )
124 from lp.services.webapp.publisher import canonical_url
125-from lp.testing import (
126- FakeLaunchpadRequest,
127- TestCaseWithFactory,
128+from lp.services.webapp.servers import (
129+ LaunchpadTestRequest,
130+ WebServiceTestRequest,
131 )
132+from lp.testing import TestCaseWithFactory
133 from lp.testing.layers import DatabaseFunctionalLayer
134
135
136@@ -36,11 +37,11 @@ class TestPersonBranchTraversal(TestCaseWithFactory):
137 TestCaseWithFactory.setUp(self)
138 self.person = self.factory.makePerson()
139
140- def assertRedirects(self, segments, url):
141- redirection = self.traverse(segments)
142+ def assertRedirects(self, segments, url, webservice=False):
143+ redirection = self.traverse(segments, webservice=webservice)
144 self.assertEqual(url, redirection.target)
145
146- def traverse(self, segments):
147+ def traverse(self, segments, webservice=False):
148 """Traverse to 'segments' using a 'PersonNavigation' object.
149
150 Using the Zope traversal machinery, traverse to the path given by
151@@ -48,11 +49,16 @@ class TestPersonBranchTraversal(TestCaseWithFactory):
152 'person' attribute.
153
154 :param segments: A list of path segments.
155+ :param webservice: If True, use a webservice-like request rather
156+ than a normal test request.
157 :return: The object found.
158 """
159 stack = list(reversed(segments))
160 name = stack.pop()
161- request = FakeLaunchpadRequest(['~' + self.person.name], stack)
162+ request_factory = (
163+ WebServiceTestRequest if webservice else LaunchpadTestRequest)
164+ request = request_factory()
165+ request.setTraversalStack(stack)
166 traverser = PersonNavigation(self.person, request)
167 return traverser.publishTraverse(request, name)
168
169@@ -60,11 +66,15 @@ class TestPersonBranchTraversal(TestCaseWithFactory):
170 branch = self.factory.makeProductBranch(owner=self.person)
171 segments = ['+branch', branch.product.name, branch.name]
172 self.assertRedirects(segments, canonical_url(branch))
173+ self.assertRedirects(
174+ segments, canonical_url(branch, rootsite='api'), webservice=True)
175
176 def test_redirect_junk_branch(self):
177 branch = self.factory.makePersonalBranch(owner=self.person)
178 segments = ['+branch', '+junk', branch.name]
179 self.assertRedirects(segments, canonical_url(branch))
180+ self.assertRedirects(
181+ segments, canonical_url(branch, rootsite='api'), webservice=True)
182
183 def test_redirect_branch_not_found(self):
184 self.assertRaises(
185@@ -78,6 +88,10 @@ class TestPersonBranchTraversal(TestCaseWithFactory):
186 ['foo', branch.distroseries.name, branch.sourcepackagename.name,
187 branch.name],
188 canonical_url(branch))
189+ self.assertRedirects(
190+ ['foo', branch.distroseries.name, branch.sourcepackagename.name,
191+ branch.name],
192+ canonical_url(branch, rootsite='api'), webservice=True)
193
194 def test_junk_branch(self):
195 branch = self.factory.makePersonalBranch(owner=self.person)
196@@ -134,8 +148,8 @@ class TestPersonProductBranchTraversal(TestCaseWithFactory):
197 """
198 stack = list(reversed(segments))
199 name = stack.pop()
200- request = FakeLaunchpadRequest(
201- ['~' + self.person.name, self.product.name], stack)
202+ request = LaunchpadTestRequest()
203+ request.setTraversalStack(stack)
204 traverser = PersonProductNavigation(self.person_product, request)
205 return traverser.publishTraverse(request, name)
206
207diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
208index facd4d2..193df25 100644
209--- a/lib/lp/registry/browser/person.py
210+++ b/lib/lp/registry/browser/person.py
211@@ -364,7 +364,8 @@ class BranchTraversalMixin:
212 """Redirect to canonical_url."""
213 branch = getUtility(IBranchNamespaceSet).traverse(self._getSegments())
214 if branch:
215- return self.redirectSubTree(canonical_url(branch))
216+ return self.redirectSubTree(
217+ canonical_url(branch, request=self.request))
218 raise NotFoundError
219
220 @stepthrough('+git')
221@@ -414,7 +415,8 @@ class BranchTraversalMixin:
222 if using_pillar_alias:
223 # This repository was accessed through one of its project's
224 # aliases, so we must redirect to its canonical URL.
225- return self.redirectSubTree(canonical_url(repository))
226+ return self.redirectSubTree(
227+ canonical_url(repository, request=self.request))
228
229 return repository
230 except (NotFoundError, InvalidNamespace, InvalidProductName):
231@@ -478,13 +480,15 @@ class BranchTraversalMixin:
232 if branch.product.name != pillar_name:
233 # This branch was accessed through one of its project's
234 # aliases, so we must redirect to its canonical URL.
235- return self.redirectSubTree(canonical_url(branch))
236+ return self.redirectSubTree(
237+ canonical_url(branch, request=self.request))
238
239 if branch.distribution is not None:
240 if branch.distribution.name != pillar_name:
241 # This branch was accessed through one of its distribution's
242 # aliases, so we must redirect to its canonical URL.
243- return self.redirectSubTree(canonical_url(branch))
244+ return self.redirectSubTree(
245+ canonical_url(branch, request=self.request))
246
247 return branch
248
249diff --git a/lib/lp/registry/browser/tests/test_person.py b/lib/lp/registry/browser/tests/test_person.py
250index 2008a61..07ef8d4 100644
251--- a/lib/lp/registry/browser/tests/test_person.py
252+++ b/lib/lp/registry/browser/tests/test_person.py
253@@ -184,6 +184,32 @@ class TestPersonNavigation(TestCaseWithFactory):
254 repository.owner.name, project.name, repository.name)
255 self.assertEqual(repository, test_traverse(url)[0])
256
257+ def test_traverse_git_repository_project_alias(self):
258+ project = self.factory.makeProduct()
259+ alias = project.name + "-2"
260+ removeSecurityProxy(project).setAliases([alias])
261+ repository = self.factory.makeGitRepository(target=project)
262+ url = "/~%s/%s/+git/%s" % (
263+ repository.owner.name, alias, repository.name)
264+ expected_url = "http://code.launchpad.test/~%s/%s/+git/%s" % (
265+ repository.owner.name, project.name, repository.name)
266+ _, view, _ = test_traverse(url)
267+ self.assertIsInstance(view, RedirectionView)
268+ self.assertEqual(expected_url, removeSecurityProxy(view).target)
269+
270+ def test_traverse_git_repository_project_alias_api(self):
271+ project = self.factory.makeProduct()
272+ alias = project.name + "-2"
273+ removeSecurityProxy(project).setAliases([alias])
274+ repository = self.factory.makeGitRepository(target=project)
275+ url = "http://api.launchpad.test/devel/~%s/%s/+git/%s" % (
276+ repository.owner.name, alias, repository.name)
277+ expected_url = "http://api.launchpad.test/devel/~%s/%s/+git/%s" % (
278+ repository.owner.name, project.name, repository.name)
279+ _, view, _ = test_traverse(url)
280+ self.assertIsInstance(view, RedirectionView)
281+ self.assertEqual(expected_url, removeSecurityProxy(view).target)
282+
283 def test_traverse_git_repository_package(self):
284 dsp = self.factory.makeDistributionSourcePackage()
285 repository = self.factory.makeGitRepository(target=dsp)
286@@ -192,6 +218,38 @@ class TestPersonNavigation(TestCaseWithFactory):
287 dsp.sourcepackagename.name, repository.name)
288 self.assertEqual(repository, test_traverse(url)[0])
289
290+ def test_traverse_git_repository_package_alias(self):
291+ dsp = self.factory.makeDistributionSourcePackage()
292+ alias = dsp.distribution.name + "-2"
293+ removeSecurityProxy(dsp.distribution).setAliases([alias])
294+ repository = self.factory.makeGitRepository(target=dsp)
295+ url = "/~%s/%s/+source/%s/+git/%s" % (
296+ repository.owner.name, alias,
297+ dsp.sourcepackagename.name, repository.name)
298+ expected_url = (
299+ "http://code.launchpad.test/~%s/%s/+source/%s/+git/%s" % (
300+ repository.owner.name, dsp.distribution.name,
301+ dsp.sourcepackagename.name, repository.name))
302+ _, view, _ = test_traverse(url)
303+ self.assertIsInstance(view, RedirectionView)
304+ self.assertEqual(expected_url, removeSecurityProxy(view).target)
305+
306+ def test_traverse_git_repository_package_alias_api(self):
307+ dsp = self.factory.makeDistributionSourcePackage()
308+ alias = dsp.distribution.name + "-2"
309+ removeSecurityProxy(dsp.distribution).setAliases([alias])
310+ repository = self.factory.makeGitRepository(target=dsp)
311+ url = "http://api.launchpad.test/devel/~%s/%s/+source/%s/+git/%s" % (
312+ repository.owner.name, alias,
313+ dsp.sourcepackagename.name, repository.name)
314+ expected_url = (
315+ "http://api.launchpad.test/devel/~%s/%s/+source/%s/+git/%s" % (
316+ repository.owner.name, dsp.distribution.name,
317+ dsp.sourcepackagename.name, repository.name))
318+ _, view, _ = test_traverse(url)
319+ self.assertIsInstance(view, RedirectionView)
320+ self.assertEqual(expected_url, removeSecurityProxy(view).target)
321+
322 def test_traverse_git_repository_oci_project(self):
323 oci_project = self.factory.makeOCIProject()
324 repository = self.factory.makeGitRepository(target=oci_project)

Subscribers

People subscribed via source and target branches

to status/vote changes: