Merge ~cjwatson/launchpad:faster-bugs-webservice-tests into launchpad:master

Proposed by Colin Watson on 2019-11-21
Status: Merged
Approved by: Colin Watson on 2019-12-09
Approved revision: 69c5e5b29fe41ac8d8b6341e84d01ffbc3a1a935
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:faster-bugs-webservice-tests
Merge into: launchpad:master
Diff against target: 757 lines (+223/-175)
6 files modified
lib/lp/bugs/browser/tests/test_bugattachment_file_access.py (+53/-46)
lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py (+65/-65)
lib/lp/bugs/browser/tests/test_structuralsubscription.py (+43/-26)
lib/lp/bugs/model/tests/test_bugtask.py (+23/-18)
lib/lp/bugs/tests/test_bug_messages_webservice.py (+33/-20)
lib/lp/testing/__init__.py (+6/-0)
Reviewer Review Type Date Requested Status
Tom Wardill 2019-11-21 Approve on 2019-12-09
Review via email: mp+375798@code.launchpad.net

Commit message

Stop using launchpadlib in bugs webservice tests

Description of the change

Port the bugs webservice tests to use in-process webservice calls rather than launchpadlib and AppServerLayer. While the code is a bit longer as a result, it's easier to debug and substantially faster: this change takes the test time for these test suites from 106 seconds to 22 seconds on my laptop.

Similarly, I downgraded a couple of bug subscription filter test suites from LaunchpadFunctionalLayer to DatabaseFunctionalLayer, since they didn't use anything extra from the more sophisticated layer.

To post a comment you must log in.
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
2index f43e922..30e6cca 100644
3--- a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
4+++ b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
5@@ -1,15 +1,16 @@
6-# Copyright 2010 Canonical Ltd. This software is licensed under the
7+# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 __metaclass__ = type
11
12 import re
13-from urlparse import (
14+
15+import requests
16+from six.moves.urllib.parse import (
17 parse_qs,
18 urlparse,
19+ urlunparse,
20 )
21-
22-from lazr.restfulclient.errors import NotFound as RestfulNotFound
23 import transaction
24 from zope.component import (
25 getMultiAdapter,
26@@ -17,24 +18,27 @@ from zope.component import (
27 )
28 from zope.publisher.interfaces import NotFound
29 from zope.security.interfaces import Unauthorized
30-from zope.security.management import endInteraction
31
32 from lp.bugs.browser.bugattachment import BugAttachmentFileNavigation
33+from lp.services.config import config
34 from lp.services.librarian.interfaces import ILibraryFileAliasWithParent
35-from lp.services.webapp.interfaces import ILaunchBag
36+from lp.services.webapp.interfaces import (
37+ ILaunchBag,
38+ OAuthPermission,
39+ )
40 from lp.services.webapp.publisher import RedirectionView
41 from lp.services.webapp.servers import LaunchpadTestRequest
42 from lp.testing import (
43- launchpadlib_for,
44+ api_url,
45 login_person,
46+ logout,
47 TestCaseWithFactory,
48- ws_object,
49 )
50-from lp.testing.layers import (
51- AppServerLayer,
52- LaunchpadFunctionalLayer,
53+from lp.testing.layers import LaunchpadFunctionalLayer
54+from lp.testing.pages import (
55+ LaunchpadWebServiceCaller,
56+ webservice_for_person,
57 )
58-from lp.testing.pages import LaunchpadWebServiceCaller
59
60
61 class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
62@@ -119,7 +123,7 @@ class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
63 class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
64 """Tests access to bug attachments via the webservice."""
65
66- layer = AppServerLayer
67+ layer = LaunchpadFunctionalLayer
68
69 def setUp(self):
70 super(TestWebserviceAccessToBugAttachmentFiles, self).setUp()
71@@ -127,48 +131,41 @@ class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
72 getUtility(ILaunchBag).clear()
73 login_person(self.bug_owner)
74 self.bug = self.factory.makeBug(owner=self.bug_owner)
75- self.bugattachment = self.factory.makeBugAttachment(
76+ self.factory.makeBugAttachment(
77 bug=self.bug, filename='foo.txt', data='file content')
78+ self.bug_url = api_url(self.bug)
79
80 def test_anon_access_to_public_bug_attachment(self):
81 # Attachments of public bugs can be accessed by anonymous users.
82- #
83- # Need to endInteraction() because launchpadlib_for_anonymous() will
84- # setup a new one.
85- endInteraction()
86- launchpad = launchpadlib_for('test', None, version='devel')
87- ws_bug = ws_object(launchpad, self.bug)
88- ws_bugattachment = ws_bug.attachments[0]
89- self.assertEqual(
90- 'file content', ws_bugattachment.data.open().read())
91+ logout()
92+ webservice = LaunchpadWebServiceCaller(
93+ 'test', '', default_api_version='devel')
94+ ws_bug = self.getWebserviceJSON(webservice, self.bug_url)
95+ ws_bug_attachment = self.getWebserviceJSON(
96+ webservice, ws_bug['attachments_collection_link'])['entries'][0]
97+ response = webservice.get(ws_bug_attachment['data_link'])
98+ self.assertEqual(303, response.status)
99+ response = requests.get(response.getHeader('Location'))
100+ response.raise_for_status()
101+ self.assertEqual(b'file content', response.content)
102
103 def test_user_access_to_private_bug_attachment(self):
104 # Users having access to private bugs can also read attachments
105 # of these bugs.
106 self.bug.setPrivate(True, self.bug_owner)
107 other_user = self.factory.makePerson()
108- launchpad = launchpadlib_for('test', self.bug_owner, version='devel')
109- ws_bug = ws_object(launchpad, self.bug)
110- ws_bugattachment = ws_bug.attachments[0]
111-
112- # The attachment contains a link to a HostedBytes resource;
113- # the response to a GET request of this URL is a redirect to a
114- # Librarian URL. We cannot simply access these Librarian URLs
115- # for restricted Librarian files because the host name used in
116- # the URLs is different for each file, and our test envireonment
117- # does not support wildcard DNS, and because the Launchpadlib
118- # browser automatically follows redirects.
119- # LaunchpadWebServiceCaller, on the other hand, gives us
120- # access to a raw HTTPResonse object.
121- webservice = LaunchpadWebServiceCaller(
122- 'launchpad-library', 'salgado-change-anything')
123- response = webservice.get(ws_bugattachment.data._wadl_resource._url)
124+ webservice = webservice_for_person(
125+ self.bug_owner, permission=OAuthPermission.READ_PRIVATE)
126+ ws_bug = self.getWebserviceJSON(webservice, self.bug_url)
127+ ws_bug_attachment = self.getWebserviceJSON(
128+ webservice, ws_bug['attachments_collection_link'])['entries'][0]
129+ response = webservice.get(ws_bug_attachment['data_link'])
130 self.assertEqual(303, response.status)
131
132 # The Librarian URL has, for our test case, the form
133 # "https://NNNN.restricted.launchpad.test:PORT/NNNN/foo.txt?token=..."
134 # where NNNN and PORT are integers.
135- parsed_url = urlparse(response.getHeader('location'))
136+ parsed_url = urlparse(response.getHeader('Location'))
137 self.assertEqual('https', parsed_url.scheme)
138 mo = re.search(
139 r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc)
140@@ -178,10 +175,20 @@ class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
141 params = parse_qs(parsed_url.query)
142 self.assertEqual(['token'], params.keys())
143
144+ # Our test environment does not support wildcard DNS. Work around
145+ # this.
146+ librarian_netloc = '%s:%d' % (
147+ config.librarian.download_host, config.librarian.download_port)
148+ url = urlunparse(
149+ ('http', librarian_netloc, parsed_url.path, parsed_url.params,
150+ parsed_url.query, parsed_url.fragment))
151+ response = requests.get(url, headers={'Host': parsed_url.netloc})
152+ response.raise_for_status()
153+ self.assertEqual(b'file content', response.content)
154+
155 # If a user which cannot access the private bug itself tries to
156- # to access the attachment, an NotFound error is raised.
157- other_launchpad = launchpadlib_for(
158- 'test_unauthenticated', other_user, version='devel')
159- self.assertRaises(
160- RestfulNotFound, other_launchpad._browser.get,
161- ws_bugattachment.data._wadl_resource._url)
162+ # to access the attachment, we deny its existence.
163+ other_webservice = webservice_for_person(
164+ other_user, permission=OAuthPermission.READ_PRIVATE)
165+ response = other_webservice.get(ws_bug_attachment['data_link'])
166+ self.assertEqual(404, response.status)
167diff --git a/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py b/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
168index cd5cacb..1ae3b1e 100644
169--- a/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
170+++ b/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
171@@ -1,18 +1,15 @@
172-# Copyright 2010-2012 Canonical Ltd. This software is licensed under the
173+# Copyright 2010-2019 Canonical Ltd. This software is licensed under the
174 # GNU Affero General Public License version 3 (see the file LICENSE).
175
176 """Tests for bug subscription filter browser code."""
177
178 __metaclass__ = type
179
180-from functools import partial
181+import json
182 from urlparse import urlparse
183
184-from lazr.restfulclient.errors import BadRequest
185 from lxml import html
186-from storm.exceptions import LostObjectError
187 from testtools.matchers import StartsWith
188-import transaction
189
190 from lp.app.enums import InformationType
191 from lp.bugs.browser.structuralsubscription import (
192@@ -23,21 +20,19 @@ from lp.bugs.interfaces.bugtask import (
193 BugTaskImportance,
194 BugTaskStatus,
195 )
196+from lp.services.webapp.interfaces import OAuthPermission
197 from lp.services.webapp.publisher import canonical_url
198 from lp.services.webapp.servers import LaunchpadTestRequest
199 from lp.testing import (
200 anonymous_logged_in,
201+ api_url,
202 login_person,
203 normalize_whitespace,
204 person_logged_in,
205 TestCaseWithFactory,
206- ws_object,
207- )
208-from lp.testing.layers import (
209- AppServerLayer,
210- DatabaseFunctionalLayer,
211- LaunchpadFunctionalLayer,
212 )
213+from lp.testing.layers import DatabaseFunctionalLayer
214+from lp.testing.pages import webservice_for_person
215 from lp.testing.views import create_initialized_view
216
217
218@@ -53,12 +48,14 @@ class TestBugSubscriptionFilterBase:
219 self.owner, self.owner)
220 self.initial_filter = self.subscription.bug_filters.one()
221 self.subscription_filter = self.subscription.newBugFilter()
222+ self.subscription_url = api_url(self.subscription)
223+ self.subscription_filter_url = api_url(self.subscription_filter)
224
225
226 class TestBugSubscriptionFilterNavigation(
227 TestBugSubscriptionFilterBase, TestCaseWithFactory):
228
229- layer = LaunchpadFunctionalLayer
230+ layer = DatabaseFunctionalLayer
231
232 def test_canonical_url(self):
233 url = urlparse(canonical_url(self.subscription_filter))
234@@ -80,35 +77,34 @@ class TestBugSubscriptionFilterNavigation(
235 class TestBugSubscriptionFilterAPI(
236 TestBugSubscriptionFilterBase, TestCaseWithFactory):
237
238- layer = AppServerLayer
239+ layer = DatabaseFunctionalLayer
240
241 def test_visible_attributes(self):
242 # Bug subscription filters are not private objects. All attributes are
243 # visible to everyone.
244- transaction.commit()
245- # Create a service for a new person.
246- service = self.factory.makeLaunchpadService()
247- get_ws_object = partial(ws_object, service)
248- ws_subscription = get_ws_object(self.subscription)
249- ws_subscription_filter = get_ws_object(self.subscription_filter)
250+ webservice = webservice_for_person(self.factory.makePerson())
251+ ws_subscription = self.getWebserviceJSON(
252+ webservice, self.subscription_url)
253+ ws_subscription_filter = self.getWebserviceJSON(
254+ webservice, self.subscription_filter_url)
255 self.assertEqual(
256- ws_subscription.self_link,
257- ws_subscription_filter.structural_subscription_link)
258+ ws_subscription["self_link"],
259+ ws_subscription_filter["structural_subscription_link"])
260 self.assertEqual(
261 self.subscription_filter.find_all_tags,
262- ws_subscription_filter.find_all_tags)
263+ ws_subscription_filter["find_all_tags"])
264 self.assertEqual(
265 self.subscription_filter.description,
266- ws_subscription_filter.description)
267+ ws_subscription_filter["description"])
268 self.assertEqual(
269 list(self.subscription_filter.statuses),
270- ws_subscription_filter.statuses)
271+ ws_subscription_filter["statuses"])
272 self.assertEqual(
273 list(self.subscription_filter.importances),
274- ws_subscription_filter.importances)
275+ ws_subscription_filter["importances"])
276 self.assertEqual(
277 list(self.subscription_filter.tags),
278- ws_subscription_filter.tags)
279+ ws_subscription_filter["tags"])
280
281 def test_structural_subscription_cannot_be_modified(self):
282 # Bug filters cannot be moved from one structural subscription to
283@@ -117,15 +113,14 @@ class TestBugSubscriptionFilterAPI(
284 user = self.factory.makePerson(name=u"baz")
285 with person_logged_in(self.owner):
286 user_subscription = self.structure.addBugSubscription(user, user)
287- transaction.commit()
288- # Create a service for the structure owner.
289- service = self.factory.makeLaunchpadService(self.owner)
290- get_ws_object = partial(ws_object, service)
291- ws_user_subscription = get_ws_object(user_subscription)
292- ws_subscription_filter = get_ws_object(self.subscription_filter)
293- ws_subscription_filter.structural_subscription = ws_user_subscription
294- error = self.assertRaises(BadRequest, ws_subscription_filter.lp_save)
295- self.assertEqual(400, error.response.status)
296+ user_subscription_url = api_url(user_subscription)
297+ webservice = webservice_for_person(
298+ self.owner, permission=OAuthPermission.WRITE_PUBLIC)
299+ response = webservice.patch(
300+ self.subscription_filter_url, "application/json",
301+ json.dumps(
302+ {"structural_subscription_link": user_subscription_url}))
303+ self.assertEqual(400, response.status)
304 self.assertEqual(
305 self.subscription,
306 self.subscription_filter.structural_subscription)
307@@ -134,14 +129,12 @@ class TestBugSubscriptionFilterAPI(
308 class TestBugSubscriptionFilterAPIModifications(
309 TestBugSubscriptionFilterBase, TestCaseWithFactory):
310
311- layer = AppServerLayer
312+ layer = DatabaseFunctionalLayer
313
314 def setUp(self):
315 super(TestBugSubscriptionFilterAPIModifications, self).setUp()
316- transaction.commit()
317- self.service = self.factory.makeLaunchpadService(self.owner)
318- self.ws_subscription_filter = ws_object(
319- self.service, self.subscription_filter)
320+ self.webservice = webservice_for_person(
321+ self.owner, permission=OAuthPermission.WRITE_PUBLIC)
322
323 def test_modify_tags_fields(self):
324 # Two tags-related fields - find_all_tags and tags - can be
325@@ -154,11 +147,14 @@ class TestBugSubscriptionFilterAPIModifications(
326 self.assertFalse(self.subscription_filter.exclude_any_tags)
327 self.assertEqual(set(), self.subscription_filter.tags)
328
329- # Modify, save, and start a new transaction.
330- self.ws_subscription_filter.find_all_tags = True
331- self.ws_subscription_filter.tags = ["foo", "-bar", "*", "-*"]
332- self.ws_subscription_filter.lp_save()
333- transaction.begin()
334+ # Apply changes.
335+ response = self.webservice.patch(
336+ self.subscription_filter_url, "application/json",
337+ json.dumps({
338+ "find_all_tags": True,
339+ "tags": ["foo", "-bar", "*", "-*"],
340+ }))
341+ self.assertEqual(209, response.status)
342
343 # Updated state.
344 self.assertTrue(self.subscription_filter.find_all_tags)
345@@ -170,13 +166,13 @@ class TestBugSubscriptionFilterAPIModifications(
346
347 def test_modify_description(self):
348 # The description can be modified.
349- self.assertEqual(
350- None, self.subscription_filter.description)
351+ self.assertIsNone(self.subscription_filter.description)
352
353- # Modify, save, and start a new transaction.
354- self.ws_subscription_filter.description = u"It's late."
355- self.ws_subscription_filter.lp_save()
356- transaction.begin()
357+ # Apply changes.
358+ response = self.webservice.patch(
359+ self.subscription_filter_url, "application/json",
360+ json.dumps({"description": u"It's late."}))
361+ self.assertEqual(209, response.status)
362
363 # Updated state.
364 self.assertEqual(
365@@ -186,10 +182,11 @@ class TestBugSubscriptionFilterAPIModifications(
366 # The statuses field can be modified.
367 self.assertEqual(set(), self.subscription_filter.statuses)
368
369- # Modify, save, and start a new transaction.
370- self.ws_subscription_filter.statuses = ["New", "Triaged"]
371- self.ws_subscription_filter.lp_save()
372- transaction.begin()
373+ # Apply changes.
374+ response = self.webservice.patch(
375+ self.subscription_filter_url, "application/json",
376+ json.dumps({"statuses": ["New", "Triaged"]}))
377+ self.assertEqual(209, response.status)
378
379 # Updated state.
380 self.assertEqual(
381@@ -200,10 +197,11 @@ class TestBugSubscriptionFilterAPIModifications(
382 # The importances field can be modified.
383 self.assertEqual(set(), self.subscription_filter.importances)
384
385- # Modify, save, and start a new transaction.
386- self.ws_subscription_filter.importances = ["Low", "High"]
387- self.ws_subscription_filter.lp_save()
388- transaction.begin()
389+ # Apply changes.
390+ response = self.webservice.patch(
391+ self.subscription_filter_url, "application/json",
392+ json.dumps({"importances": ["Low", "High"]}))
393+ self.assertEqual(209, response.status)
394
395 # Updated state.
396 self.assertEqual(
397@@ -212,11 +210,13 @@ class TestBugSubscriptionFilterAPIModifications(
398
399 def test_delete(self):
400 # Subscription filters can be deleted.
401- self.ws_subscription_filter.lp_delete()
402- transaction.begin()
403- self.assertRaises(
404- LostObjectError, getattr, self.subscription_filter,
405- "find_all_tags")
406+ self.assertContentEqual(
407+ [self.initial_filter, self.subscription_filter],
408+ self.subscription.bug_filters)
409+ response = self.webservice.delete(self.subscription_filter_url)
410+ self.assertEqual(200, response.status)
411+ self.assertContentEqual(
412+ [self.initial_filter], self.subscription.bug_filters)
413
414
415 class TestBugSubscriptionFilterView(
416@@ -489,7 +489,7 @@ class TestBugSubscriptionFilterEditView(
417 class TestBugSubscriptionFilterAdvancedFeatures(TestCaseWithFactory):
418 """A base class for testing advanced structural subscription features."""
419
420- layer = LaunchpadFunctionalLayer
421+ layer = DatabaseFunctionalLayer
422
423 def setUp(self):
424 super(TestBugSubscriptionFilterAdvancedFeatures, self).setUp()
425diff --git a/lib/lp/bugs/browser/tests/test_structuralsubscription.py b/lib/lp/bugs/browser/tests/test_structuralsubscription.py
426index db341b5..ff2b160 100644
427--- a/lib/lp/bugs/browser/tests/test_structuralsubscription.py
428+++ b/lib/lp/bugs/browser/tests/test_structuralsubscription.py
429@@ -1,11 +1,10 @@
430-# Copyright 2009 Canonical Ltd. This software is licensed under the
431+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
432 # GNU Affero General Public License version 3 (see the file LICENSE).
433
434 """Tests for structural subscription traversal."""
435
436 from urlparse import urlparse
437
438-import transaction
439 from zope.publisher.interfaces import NotFound
440
441 from lp.registry.browser.distribution import DistributionNavigation
442@@ -17,19 +16,18 @@ from lp.registry.browser.milestone import MilestoneNavigation
443 from lp.registry.browser.product import ProductNavigation
444 from lp.registry.browser.productseries import ProductSeriesNavigation
445 from lp.registry.browser.project import ProjectNavigation
446+from lp.services.webapp.interfaces import OAuthPermission
447 from lp.services.webapp.publisher import canonical_url
448 from lp.testing import (
449+ api_url,
450 FakeLaunchpadRequest,
451 login,
452 logout,
453 person_logged_in,
454 TestCaseWithFactory,
455- ws_object,
456- )
457-from lp.testing.layers import (
458- AppServerLayer,
459- DatabaseFunctionalLayer,
460 )
461+from lp.testing.layers import DatabaseFunctionalLayer
462+from lp.testing.pages import webservice_for_person
463 from lp.testing.views import create_initialized_view
464
465
466@@ -217,7 +215,7 @@ class TestSourcePackageStructuralSubscribersPortletView(
467
468 class TestStructuralSubscriptionAPI(TestCaseWithFactory):
469
470- layer = AppServerLayer
471+ layer = DatabaseFunctionalLayer
472
473 def setUp(self):
474 super(TestStructuralSubscriptionAPI, self).setUp()
475@@ -228,40 +226,59 @@ class TestStructuralSubscriptionAPI(TestCaseWithFactory):
476 self.subscription = self.structure.addBugSubscription(
477 self.owner, self.owner)
478 self.initial_filter = self.subscription.bug_filters[0]
479- transaction.commit()
480- self.service = self.factory.makeLaunchpadService(self.owner)
481- self.ws_subscription = ws_object(self.service, self.subscription)
482- self.ws_subscription_filter = ws_object(
483- self.service, self.initial_filter)
484+ self.subscription_url = api_url(self.subscription)
485+ self.initial_filter_url = api_url(self.initial_filter)
486+ self.webservice = webservice_for_person(
487+ self.owner, permission=OAuthPermission.WRITE_PUBLIC)
488
489 def test_newBugFilter(self):
490 # New bug subscription filters can be created with newBugFilter().
491- ws_subscription_filter = self.ws_subscription.newBugFilter()
492+ ws_subscription = self.getWebserviceJSON(
493+ self.webservice, self.subscription_url)
494+ response = self.webservice.named_post(
495+ self.subscription_url, "newBugFilter")
496+ self.assertEqual(201, response.status)
497+ ws_subscription_filter = self.getWebserviceJSON(
498+ self.webservice, response.getHeader("Location"))
499 self.assertEqual(
500 "bug_subscription_filter",
501- urlparse(ws_subscription_filter.resource_type_link).fragment)
502+ urlparse(ws_subscription_filter["resource_type_link"]).fragment)
503 self.assertEqual(
504- ws_subscription_filter.structural_subscription.self_link,
505- self.ws_subscription.self_link)
506+ ws_subscription["self_link"],
507+ ws_subscription_filter["structural_subscription_link"])
508
509 def test_bug_filters(self):
510 # The bug_filters property is a collection of IBugSubscriptionFilter
511 # instances previously created by newBugFilter().
512- bug_filter_links = lambda: set(
513- bug_filter.self_link for bug_filter in (
514- self.ws_subscription.bug_filters))
515- initial_filter_link = self.ws_subscription_filter.self_link
516+ ws_subscription = self.getWebserviceJSON(
517+ self.webservice, self.subscription_url)
518+ ws_initial_filter = self.getWebserviceJSON(
519+ self.webservice, self.initial_filter_url)
520+
521+ def bug_filter_links():
522+ ws_bug_filters = self.getWebserviceJSON(
523+ self.webservice,
524+ ws_subscription["bug_filters_collection_link"])
525+ return {entry["self_link"] for entry in ws_bug_filters["entries"]}
526+
527+ initial_filter_link = ws_initial_filter["self_link"]
528 self.assertContentEqual(
529 [initial_filter_link], bug_filter_links())
530 # A new filter appears in the bug_filters collection.
531- ws_subscription_filter1 = self.ws_subscription.newBugFilter()
532+ response = self.webservice.named_post(
533+ self.subscription_url, "newBugFilter")
534+ self.assertEqual(201, response.status)
535+ ws_subscription_filter1_link = response.getHeader("Location")
536 self.assertContentEqual(
537- [ws_subscription_filter1.self_link, initial_filter_link],
538+ [ws_subscription_filter1_link, initial_filter_link],
539 bug_filter_links())
540 # A second new filter also appears in the bug_filters collection.
541- ws_subscription_filter2 = self.ws_subscription.newBugFilter()
542+ response = self.webservice.named_post(
543+ self.subscription_url, "newBugFilter")
544+ self.assertEqual(201, response.status)
545+ ws_subscription_filter2_link = response.getHeader("Location")
546 self.assertContentEqual(
547- [ws_subscription_filter1.self_link,
548- ws_subscription_filter2.self_link,
549+ [ws_subscription_filter1_link,
550+ ws_subscription_filter2_link,
551 initial_filter_link],
552 bug_filter_links())
553diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
554index c2874ba..7466f7e 100644
555--- a/lib/lp/bugs/model/tests/test_bugtask.py
556+++ b/lib/lp/bugs/model/tests/test_bugtask.py
557@@ -10,7 +10,6 @@ import subprocess
558 import unittest
559
560 from lazr.lifecycle.snapshot import Snapshot
561-from lazr.restfulclient.errors import Unauthorized
562 from storm.store import Store
563 from testtools.matchers import Equals
564 from testtools.testcase import ExpectedException
565@@ -86,12 +85,16 @@ from lp.services.log.logger import DevNullLogger
566 from lp.services.propertycache import get_property_cache
567 from lp.services.searchbuilder import any
568 from lp.services.webapp.authorization import check_permission
569-from lp.services.webapp.interfaces import ILaunchBag
570+from lp.services.webapp.interfaces import (
571+ ILaunchBag,
572+ OAuthPermission,
573+ )
574 from lp.services.webapp.snapshot import notify_modified
575 from lp.soyuz.interfaces.archive import ArchivePurpose
576 from lp.testing import (
577 admin_logged_in,
578 ANONYMOUS,
579+ api_url,
580 EventRecorder,
581 feature_flags,
582 login,
583@@ -103,15 +106,14 @@ from lp.testing import (
584 StormStatementRecorder,
585 TestCase,
586 TestCaseWithFactory,
587- ws_object,
588 )
589 from lp.testing.fakemethod import FakeMethod
590 from lp.testing.layers import (
591- AppServerLayer,
592 CeleryJobLayer,
593 DatabaseFunctionalLayer,
594 )
595 from lp.testing.matchers import HasQueryCount
596+from lp.testing.pages import webservice_for_person
597
598
599 BugData = namedtuple("BugData", ['owner', 'distro', 'distro_release',
600@@ -2953,29 +2955,32 @@ class TestValidateNewTarget(TestCaseWithFactory, ValidateTargetMixin):
601 class TestWebservice(TestCaseWithFactory):
602 """Tests for the webservice."""
603
604- layer = AppServerLayer
605+ layer = DatabaseFunctionalLayer
606
607 def test_delete_bugtask(self):
608 """Test that a bugtask can be deleted."""
609 owner = self.factory.makePerson()
610 some_person = self.factory.makePerson()
611- db_bug = self.factory.makeBug()
612- db_bugtask = self.factory.makeBugTask(bug=db_bug, owner=owner)
613- transaction.commit()
614- logout()
615+ bug = self.factory.makeBug()
616+ bugtask = self.factory.makeBugTask(bug=bug, owner=owner)
617+ bugtask_url = api_url(bugtask)
618
619 # It will fail for an unauthorised user.
620- launchpad = self.factory.makeLaunchpadService(some_person)
621- bugtask = ws_object(launchpad, db_bugtask)
622- self.assertRaises(Unauthorized, bugtask.lp_delete)
623+ webservice = webservice_for_person(
624+ some_person, permission=OAuthPermission.WRITE_PUBLIC,
625+ default_api_version="devel")
626+ response = webservice.delete(bugtask_url)
627+ self.assertEqual(401, response.status)
628+
629+ webservice = webservice_for_person(
630+ owner, permission=OAuthPermission.WRITE_PUBLIC,
631+ default_api_version="devel")
632+ response = webservice.delete(bugtask_url)
633+ self.assertEqual(200, response.status)
634
635- launchpad = self.factory.makeLaunchpadService(owner)
636- bugtask = ws_object(launchpad, db_bugtask)
637- bugtask.lp_delete()
638- transaction.commit()
639 # Check the delete really worked.
640- with person_logged_in(removeSecurityProxy(db_bug).owner):
641- self.assertEqual([db_bug.default_bugtask], db_bug.bugtasks)
642+ with person_logged_in(removeSecurityProxy(bug).owner):
643+ self.assertEqual([bug.default_bugtask], bug.bugtasks)
644
645
646 class TestBugTaskUserHasBugSupervisorPrivileges(TestCaseWithFactory):
647diff --git a/lib/lp/bugs/tests/test_bug_messages_webservice.py b/lib/lp/bugs/tests/test_bug_messages_webservice.py
648index feec1a7..5faae0e 100644
649--- a/lib/lp/bugs/tests/test_bug_messages_webservice.py
650+++ b/lib/lp/bugs/tests/test_bug_messages_webservice.py
651@@ -1,4 +1,4 @@
652-# Copyright 2011 Canonical Ltd. This software is licensed under the
653+# Copyright 2011-2019 Canonical Ltd. This software is licensed under the
654 # GNU Affero General Public License version 3 (see the file LICENSE).
655
656 """Webservice unit tests related to Launchpad Bug messages."""
657@@ -24,36 +24,43 @@ from lp.testing import (
658 logout,
659 person_logged_in,
660 TestCaseWithFactory,
661- WebServiceTestCase,
662 )
663 from lp.testing.layers import (
664 DatabaseFunctionalLayer,
665 LaunchpadFunctionalLayer,
666 )
667-from lp.testing.pages import LaunchpadWebServiceCaller
668+from lp.testing.pages import (
669+ LaunchpadWebServiceCaller,
670+ webservice_for_person,
671+ )
672
673
674-class TestMessageTraversal(WebServiceTestCase):
675+class TestMessageTraversal(TestCaseWithFactory):
676 """Tests safe traversal of bugs.
677
678 See bug 607438."""
679
680+ layer = LaunchpadFunctionalLayer
681+
682 def test_message_with_attachments(self):
683- bugowner = self.factory.makePerson()
684- bug = self.factory.makeBug(owner=bugowner)
685+ bug = self.factory.makeBug()
686 # Traversal over bug messages attachments has no errors.
687 expected_messages = []
688- with person_logged_in(bugowner):
689+ with person_logged_in(bug.owner):
690 for i in range(3):
691 att = self.factory.makeBugAttachment(bug)
692 expected_messages.append(att.message.subject)
693-
694- lp_user = self.factory.makePerson()
695- lp_bug = self.wsObject(bug, lp_user)
696-
697- attachments = lp_bug.attachments
698- messages = [a.message.subject for a in attachments
699- if a.message is not None]
700+ bug_url = api_url(bug)
701+
702+ webservice = webservice_for_person(self.factory.makePerson())
703+ ws_bug = self.getWebserviceJSON(webservice, bug_url)
704+ ws_bug_attachments = self.getWebserviceJSON(
705+ webservice, ws_bug['attachments_collection_link'])
706+ messages = [
707+ self.getWebserviceJSON(
708+ webservice, attachment['message_link'])['subject']
709+ for attachment in ws_bug_attachments['entries']
710+ if attachment['message_link'] is not None]
711 self.assertContentEqual(
712 messages,
713 expected_messages)
714@@ -67,15 +74,21 @@ class TestMessageTraversal(WebServiceTestCase):
715 message_2 = self.factory.makeMessage()
716 message_2.parent = message_1
717 bug = self.factory.makeBug()
718- bug.linkMessage(message_2)
719+ with person_logged_in(bug.owner):
720+ bug.linkMessage(message_2)
721+ bug_url = api_url(bug)
722+ message_2_url = api_url(message_2)
723 user = self.factory.makePerson()
724- lp_bug = self.wsObject(bug, user)
725- for lp_message in lp_bug.messages:
726+ webservice = webservice_for_person(user)
727+ ws_bug = self.getWebserviceJSON(webservice, bug_url)
728+ ws_bug_messages = self.getWebserviceJSON(
729+ webservice, ws_bug['messages_collection_link'])
730+ for ws_message in ws_bug_messages['entries']:
731 # An IIndexedMessage's representation.
732- self.assertIs(None, lp_message.parent)
733+ self.assertIsNone(ws_message['parent_link'])
734 # An IMessage's representation.
735- lp_message = self.wsObject(message_2, user)
736- self.assertIs(None, lp_message.parent)
737+ ws_message = self.getWebserviceJSON(webservice, message_2_url)
738+ self.assertIsNone(ws_message['parent_link'])
739
740
741 class TestBugMessage(TestCaseWithFactory):
742diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
743index 6a0d9b2..747930d 100644
744--- a/lib/lp/testing/__init__.py
745+++ b/lib/lp/testing/__init__.py
746@@ -827,6 +827,12 @@ class TestCase(testtools.TestCase, fixtures.TestWithFixtures):
747 "\n\n".join(str(n) for n in notifications)))
748 return notifications
749
750+ def getWebserviceJSON(self, webservice, url):
751+ """Get the JSON representation of a webservice object given its URL."""
752+ response = webservice.get(url)
753+ self.assertEqual(200, response.status)
754+ return response.jsonBody()
755+
756
757 class TestCaseWithFactory(TestCase):
758

Subscribers

People subscribed via source and target branches