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

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
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 (community) Approve
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.
Revision history for this message
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
index f43e922..30e6cca 100644
--- a/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
+++ b/lib/lp/bugs/browser/tests/test_bugattachment_file_access.py
@@ -1,15 +1,16 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the1# Copyright 2010-2019 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__metaclass__ = type4__metaclass__ = type
55
6import re6import re
7from urlparse import (7
8import requests
9from six.moves.urllib.parse import (
8 parse_qs,10 parse_qs,
9 urlparse,11 urlparse,
12 urlunparse,
10 )13 )
11
12from lazr.restfulclient.errors import NotFound as RestfulNotFound
13import transaction14import transaction
14from zope.component import (15from zope.component import (
15 getMultiAdapter,16 getMultiAdapter,
@@ -17,24 +18,27 @@ from zope.component import (
17 )18 )
18from zope.publisher.interfaces import NotFound19from zope.publisher.interfaces import NotFound
19from zope.security.interfaces import Unauthorized20from zope.security.interfaces import Unauthorized
20from zope.security.management import endInteraction
2121
22from lp.bugs.browser.bugattachment import BugAttachmentFileNavigation22from lp.bugs.browser.bugattachment import BugAttachmentFileNavigation
23from lp.services.config import config
23from lp.services.librarian.interfaces import ILibraryFileAliasWithParent24from lp.services.librarian.interfaces import ILibraryFileAliasWithParent
24from lp.services.webapp.interfaces import ILaunchBag25from lp.services.webapp.interfaces import (
26 ILaunchBag,
27 OAuthPermission,
28 )
25from lp.services.webapp.publisher import RedirectionView29from lp.services.webapp.publisher import RedirectionView
26from lp.services.webapp.servers import LaunchpadTestRequest30from lp.services.webapp.servers import LaunchpadTestRequest
27from lp.testing import (31from lp.testing import (
28 launchpadlib_for,32 api_url,
29 login_person,33 login_person,
34 logout,
30 TestCaseWithFactory,35 TestCaseWithFactory,
31 ws_object,
32 )36 )
33from lp.testing.layers import (37from lp.testing.layers import LaunchpadFunctionalLayer
34 AppServerLayer,38from lp.testing.pages import (
35 LaunchpadFunctionalLayer,39 LaunchpadWebServiceCaller,
40 webservice_for_person,
36 )41 )
37from lp.testing.pages import LaunchpadWebServiceCaller
3842
3943
40class TestAccessToBugAttachmentFiles(TestCaseWithFactory):44class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
@@ -119,7 +123,7 @@ class TestAccessToBugAttachmentFiles(TestCaseWithFactory):
119class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):123class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
120 """Tests access to bug attachments via the webservice."""124 """Tests access to bug attachments via the webservice."""
121125
122 layer = AppServerLayer126 layer = LaunchpadFunctionalLayer
123127
124 def setUp(self):128 def setUp(self):
125 super(TestWebserviceAccessToBugAttachmentFiles, self).setUp()129 super(TestWebserviceAccessToBugAttachmentFiles, self).setUp()
@@ -127,48 +131,41 @@ class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
127 getUtility(ILaunchBag).clear()131 getUtility(ILaunchBag).clear()
128 login_person(self.bug_owner)132 login_person(self.bug_owner)
129 self.bug = self.factory.makeBug(owner=self.bug_owner)133 self.bug = self.factory.makeBug(owner=self.bug_owner)
130 self.bugattachment = self.factory.makeBugAttachment(134 self.factory.makeBugAttachment(
131 bug=self.bug, filename='foo.txt', data='file content')135 bug=self.bug, filename='foo.txt', data='file content')
136 self.bug_url = api_url(self.bug)
132137
133 def test_anon_access_to_public_bug_attachment(self):138 def test_anon_access_to_public_bug_attachment(self):
134 # Attachments of public bugs can be accessed by anonymous users.139 # Attachments of public bugs can be accessed by anonymous users.
135 #140 logout()
136 # Need to endInteraction() because launchpadlib_for_anonymous() will141 webservice = LaunchpadWebServiceCaller(
137 # setup a new one.142 'test', '', default_api_version='devel')
138 endInteraction()143 ws_bug = self.getWebserviceJSON(webservice, self.bug_url)
139 launchpad = launchpadlib_for('test', None, version='devel')144 ws_bug_attachment = self.getWebserviceJSON(
140 ws_bug = ws_object(launchpad, self.bug)145 webservice, ws_bug['attachments_collection_link'])['entries'][0]
141 ws_bugattachment = ws_bug.attachments[0]146 response = webservice.get(ws_bug_attachment['data_link'])
142 self.assertEqual(147 self.assertEqual(303, response.status)
143 'file content', ws_bugattachment.data.open().read())148 response = requests.get(response.getHeader('Location'))
149 response.raise_for_status()
150 self.assertEqual(b'file content', response.content)
144151
145 def test_user_access_to_private_bug_attachment(self):152 def test_user_access_to_private_bug_attachment(self):
146 # Users having access to private bugs can also read attachments153 # Users having access to private bugs can also read attachments
147 # of these bugs.154 # of these bugs.
148 self.bug.setPrivate(True, self.bug_owner)155 self.bug.setPrivate(True, self.bug_owner)
149 other_user = self.factory.makePerson()156 other_user = self.factory.makePerson()
150 launchpad = launchpadlib_for('test', self.bug_owner, version='devel')157 webservice = webservice_for_person(
151 ws_bug = ws_object(launchpad, self.bug)158 self.bug_owner, permission=OAuthPermission.READ_PRIVATE)
152 ws_bugattachment = ws_bug.attachments[0]159 ws_bug = self.getWebserviceJSON(webservice, self.bug_url)
153160 ws_bug_attachment = self.getWebserviceJSON(
154 # The attachment contains a link to a HostedBytes resource;161 webservice, ws_bug['attachments_collection_link'])['entries'][0]
155 # the response to a GET request of this URL is a redirect to a162 response = webservice.get(ws_bug_attachment['data_link'])
156 # Librarian URL. We cannot simply access these Librarian URLs
157 # for restricted Librarian files because the host name used in
158 # the URLs is different for each file, and our test envireonment
159 # does not support wildcard DNS, and because the Launchpadlib
160 # browser automatically follows redirects.
161 # LaunchpadWebServiceCaller, on the other hand, gives us
162 # access to a raw HTTPResonse object.
163 webservice = LaunchpadWebServiceCaller(
164 'launchpad-library', 'salgado-change-anything')
165 response = webservice.get(ws_bugattachment.data._wadl_resource._url)
166 self.assertEqual(303, response.status)163 self.assertEqual(303, response.status)
167164
168 # The Librarian URL has, for our test case, the form165 # The Librarian URL has, for our test case, the form
169 # "https://NNNN.restricted.launchpad.test:PORT/NNNN/foo.txt?token=..."166 # "https://NNNN.restricted.launchpad.test:PORT/NNNN/foo.txt?token=..."
170 # where NNNN and PORT are integers.167 # where NNNN and PORT are integers.
171 parsed_url = urlparse(response.getHeader('location'))168 parsed_url = urlparse(response.getHeader('Location'))
172 self.assertEqual('https', parsed_url.scheme)169 self.assertEqual('https', parsed_url.scheme)
173 mo = re.search(170 mo = re.search(
174 r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc)171 r'^i\d+\.restricted\..+:\d+$', parsed_url.netloc)
@@ -178,10 +175,20 @@ class TestWebserviceAccessToBugAttachmentFiles(TestCaseWithFactory):
178 params = parse_qs(parsed_url.query)175 params = parse_qs(parsed_url.query)
179 self.assertEqual(['token'], params.keys())176 self.assertEqual(['token'], params.keys())
180177
178 # Our test environment does not support wildcard DNS. Work around
179 # this.
180 librarian_netloc = '%s:%d' % (
181 config.librarian.download_host, config.librarian.download_port)
182 url = urlunparse(
183 ('http', librarian_netloc, parsed_url.path, parsed_url.params,
184 parsed_url.query, parsed_url.fragment))
185 response = requests.get(url, headers={'Host': parsed_url.netloc})
186 response.raise_for_status()
187 self.assertEqual(b'file content', response.content)
188
181 # If a user which cannot access the private bug itself tries to189 # If a user which cannot access the private bug itself tries to
182 # to access the attachment, an NotFound error is raised.190 # to access the attachment, we deny its existence.
183 other_launchpad = launchpadlib_for(191 other_webservice = webservice_for_person(
184 'test_unauthenticated', other_user, version='devel')192 other_user, permission=OAuthPermission.READ_PRIVATE)
185 self.assertRaises(193 response = other_webservice.get(ws_bug_attachment['data_link'])
186 RestfulNotFound, other_launchpad._browser.get,194 self.assertEqual(404, response.status)
187 ws_bugattachment.data._wadl_resource._url)
diff --git a/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py b/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
index cd5cacb..1ae3b1e 100644
--- a/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
+++ b/lib/lp/bugs/browser/tests/test_bugsubscriptionfilter.py
@@ -1,18 +1,15 @@
1# Copyright 2010-2012 Canonical Ltd. This software is licensed under the1# Copyright 2010-2019 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"""Tests for bug subscription filter browser code."""4"""Tests for bug subscription filter browser code."""
55
6__metaclass__ = type6__metaclass__ = type
77
8from functools import partial8import json
9from urlparse import urlparse9from urlparse import urlparse
1010
11from lazr.restfulclient.errors import BadRequest
12from lxml import html11from lxml import html
13from storm.exceptions import LostObjectError
14from testtools.matchers import StartsWith12from testtools.matchers import StartsWith
15import transaction
1613
17from lp.app.enums import InformationType14from lp.app.enums import InformationType
18from lp.bugs.browser.structuralsubscription import (15from lp.bugs.browser.structuralsubscription import (
@@ -23,21 +20,19 @@ from lp.bugs.interfaces.bugtask import (
23 BugTaskImportance,20 BugTaskImportance,
24 BugTaskStatus,21 BugTaskStatus,
25 )22 )
23from lp.services.webapp.interfaces import OAuthPermission
26from lp.services.webapp.publisher import canonical_url24from lp.services.webapp.publisher import canonical_url
27from lp.services.webapp.servers import LaunchpadTestRequest25from lp.services.webapp.servers import LaunchpadTestRequest
28from lp.testing import (26from lp.testing import (
29 anonymous_logged_in,27 anonymous_logged_in,
28 api_url,
30 login_person,29 login_person,
31 normalize_whitespace,30 normalize_whitespace,
32 person_logged_in,31 person_logged_in,
33 TestCaseWithFactory,32 TestCaseWithFactory,
34 ws_object,
35 )
36from lp.testing.layers import (
37 AppServerLayer,
38 DatabaseFunctionalLayer,
39 LaunchpadFunctionalLayer,
40 )33 )
34from lp.testing.layers import DatabaseFunctionalLayer
35from lp.testing.pages import webservice_for_person
41from lp.testing.views import create_initialized_view36from lp.testing.views import create_initialized_view
4237
4338
@@ -53,12 +48,14 @@ class TestBugSubscriptionFilterBase:
53 self.owner, self.owner)48 self.owner, self.owner)
54 self.initial_filter = self.subscription.bug_filters.one()49 self.initial_filter = self.subscription.bug_filters.one()
55 self.subscription_filter = self.subscription.newBugFilter()50 self.subscription_filter = self.subscription.newBugFilter()
51 self.subscription_url = api_url(self.subscription)
52 self.subscription_filter_url = api_url(self.subscription_filter)
5653
5754
58class TestBugSubscriptionFilterNavigation(55class TestBugSubscriptionFilterNavigation(
59 TestBugSubscriptionFilterBase, TestCaseWithFactory):56 TestBugSubscriptionFilterBase, TestCaseWithFactory):
6057
61 layer = LaunchpadFunctionalLayer58 layer = DatabaseFunctionalLayer
6259
63 def test_canonical_url(self):60 def test_canonical_url(self):
64 url = urlparse(canonical_url(self.subscription_filter))61 url = urlparse(canonical_url(self.subscription_filter))
@@ -80,35 +77,34 @@ class TestBugSubscriptionFilterNavigation(
80class TestBugSubscriptionFilterAPI(77class TestBugSubscriptionFilterAPI(
81 TestBugSubscriptionFilterBase, TestCaseWithFactory):78 TestBugSubscriptionFilterBase, TestCaseWithFactory):
8279
83 layer = AppServerLayer80 layer = DatabaseFunctionalLayer
8481
85 def test_visible_attributes(self):82 def test_visible_attributes(self):
86 # Bug subscription filters are not private objects. All attributes are83 # Bug subscription filters are not private objects. All attributes are
87 # visible to everyone.84 # visible to everyone.
88 transaction.commit()85 webservice = webservice_for_person(self.factory.makePerson())
89 # Create a service for a new person.86 ws_subscription = self.getWebserviceJSON(
90 service = self.factory.makeLaunchpadService()87 webservice, self.subscription_url)
91 get_ws_object = partial(ws_object, service)88 ws_subscription_filter = self.getWebserviceJSON(
92 ws_subscription = get_ws_object(self.subscription)89 webservice, self.subscription_filter_url)
93 ws_subscription_filter = get_ws_object(self.subscription_filter)
94 self.assertEqual(90 self.assertEqual(
95 ws_subscription.self_link,91 ws_subscription["self_link"],
96 ws_subscription_filter.structural_subscription_link)92 ws_subscription_filter["structural_subscription_link"])
97 self.assertEqual(93 self.assertEqual(
98 self.subscription_filter.find_all_tags,94 self.subscription_filter.find_all_tags,
99 ws_subscription_filter.find_all_tags)95 ws_subscription_filter["find_all_tags"])
100 self.assertEqual(96 self.assertEqual(
101 self.subscription_filter.description,97 self.subscription_filter.description,
102 ws_subscription_filter.description)98 ws_subscription_filter["description"])
103 self.assertEqual(99 self.assertEqual(
104 list(self.subscription_filter.statuses),100 list(self.subscription_filter.statuses),
105 ws_subscription_filter.statuses)101 ws_subscription_filter["statuses"])
106 self.assertEqual(102 self.assertEqual(
107 list(self.subscription_filter.importances),103 list(self.subscription_filter.importances),
108 ws_subscription_filter.importances)104 ws_subscription_filter["importances"])
109 self.assertEqual(105 self.assertEqual(
110 list(self.subscription_filter.tags),106 list(self.subscription_filter.tags),
111 ws_subscription_filter.tags)107 ws_subscription_filter["tags"])
112108
113 def test_structural_subscription_cannot_be_modified(self):109 def test_structural_subscription_cannot_be_modified(self):
114 # Bug filters cannot be moved from one structural subscription to110 # Bug filters cannot be moved from one structural subscription to
@@ -117,15 +113,14 @@ class TestBugSubscriptionFilterAPI(
117 user = self.factory.makePerson(name=u"baz")113 user = self.factory.makePerson(name=u"baz")
118 with person_logged_in(self.owner):114 with person_logged_in(self.owner):
119 user_subscription = self.structure.addBugSubscription(user, user)115 user_subscription = self.structure.addBugSubscription(user, user)
120 transaction.commit()116 user_subscription_url = api_url(user_subscription)
121 # Create a service for the structure owner.117 webservice = webservice_for_person(
122 service = self.factory.makeLaunchpadService(self.owner)118 self.owner, permission=OAuthPermission.WRITE_PUBLIC)
123 get_ws_object = partial(ws_object, service)119 response = webservice.patch(
124 ws_user_subscription = get_ws_object(user_subscription)120 self.subscription_filter_url, "application/json",
125 ws_subscription_filter = get_ws_object(self.subscription_filter)121 json.dumps(
126 ws_subscription_filter.structural_subscription = ws_user_subscription122 {"structural_subscription_link": user_subscription_url}))
127 error = self.assertRaises(BadRequest, ws_subscription_filter.lp_save)123 self.assertEqual(400, response.status)
128 self.assertEqual(400, error.response.status)
129 self.assertEqual(124 self.assertEqual(
130 self.subscription,125 self.subscription,
131 self.subscription_filter.structural_subscription)126 self.subscription_filter.structural_subscription)
@@ -134,14 +129,12 @@ class TestBugSubscriptionFilterAPI(
134class TestBugSubscriptionFilterAPIModifications(129class TestBugSubscriptionFilterAPIModifications(
135 TestBugSubscriptionFilterBase, TestCaseWithFactory):130 TestBugSubscriptionFilterBase, TestCaseWithFactory):
136131
137 layer = AppServerLayer132 layer = DatabaseFunctionalLayer
138133
139 def setUp(self):134 def setUp(self):
140 super(TestBugSubscriptionFilterAPIModifications, self).setUp()135 super(TestBugSubscriptionFilterAPIModifications, self).setUp()
141 transaction.commit()136 self.webservice = webservice_for_person(
142 self.service = self.factory.makeLaunchpadService(self.owner)137 self.owner, permission=OAuthPermission.WRITE_PUBLIC)
143 self.ws_subscription_filter = ws_object(
144 self.service, self.subscription_filter)
145138
146 def test_modify_tags_fields(self):139 def test_modify_tags_fields(self):
147 # Two tags-related fields - find_all_tags and tags - can be140 # Two tags-related fields - find_all_tags and tags - can be
@@ -154,11 +147,14 @@ class TestBugSubscriptionFilterAPIModifications(
154 self.assertFalse(self.subscription_filter.exclude_any_tags)147 self.assertFalse(self.subscription_filter.exclude_any_tags)
155 self.assertEqual(set(), self.subscription_filter.tags)148 self.assertEqual(set(), self.subscription_filter.tags)
156149
157 # Modify, save, and start a new transaction.150 # Apply changes.
158 self.ws_subscription_filter.find_all_tags = True151 response = self.webservice.patch(
159 self.ws_subscription_filter.tags = ["foo", "-bar", "*", "-*"]152 self.subscription_filter_url, "application/json",
160 self.ws_subscription_filter.lp_save()153 json.dumps({
161 transaction.begin()154 "find_all_tags": True,
155 "tags": ["foo", "-bar", "*", "-*"],
156 }))
157 self.assertEqual(209, response.status)
162158
163 # Updated state.159 # Updated state.
164 self.assertTrue(self.subscription_filter.find_all_tags)160 self.assertTrue(self.subscription_filter.find_all_tags)
@@ -170,13 +166,13 @@ class TestBugSubscriptionFilterAPIModifications(
170166
171 def test_modify_description(self):167 def test_modify_description(self):
172 # The description can be modified.168 # The description can be modified.
173 self.assertEqual(169 self.assertIsNone(self.subscription_filter.description)
174 None, self.subscription_filter.description)
175170
176 # Modify, save, and start a new transaction.171 # Apply changes.
177 self.ws_subscription_filter.description = u"It's late."172 response = self.webservice.patch(
178 self.ws_subscription_filter.lp_save()173 self.subscription_filter_url, "application/json",
179 transaction.begin()174 json.dumps({"description": u"It's late."}))
175 self.assertEqual(209, response.status)
180176
181 # Updated state.177 # Updated state.
182 self.assertEqual(178 self.assertEqual(
@@ -186,10 +182,11 @@ class TestBugSubscriptionFilterAPIModifications(
186 # The statuses field can be modified.182 # The statuses field can be modified.
187 self.assertEqual(set(), self.subscription_filter.statuses)183 self.assertEqual(set(), self.subscription_filter.statuses)
188184
189 # Modify, save, and start a new transaction.185 # Apply changes.
190 self.ws_subscription_filter.statuses = ["New", "Triaged"]186 response = self.webservice.patch(
191 self.ws_subscription_filter.lp_save()187 self.subscription_filter_url, "application/json",
192 transaction.begin()188 json.dumps({"statuses": ["New", "Triaged"]}))
189 self.assertEqual(209, response.status)
193190
194 # Updated state.191 # Updated state.
195 self.assertEqual(192 self.assertEqual(
@@ -200,10 +197,11 @@ class TestBugSubscriptionFilterAPIModifications(
200 # The importances field can be modified.197 # The importances field can be modified.
201 self.assertEqual(set(), self.subscription_filter.importances)198 self.assertEqual(set(), self.subscription_filter.importances)
202199
203 # Modify, save, and start a new transaction.200 # Apply changes.
204 self.ws_subscription_filter.importances = ["Low", "High"]201 response = self.webservice.patch(
205 self.ws_subscription_filter.lp_save()202 self.subscription_filter_url, "application/json",
206 transaction.begin()203 json.dumps({"importances": ["Low", "High"]}))
204 self.assertEqual(209, response.status)
207205
208 # Updated state.206 # Updated state.
209 self.assertEqual(207 self.assertEqual(
@@ -212,11 +210,13 @@ class TestBugSubscriptionFilterAPIModifications(
212210
213 def test_delete(self):211 def test_delete(self):
214 # Subscription filters can be deleted.212 # Subscription filters can be deleted.
215 self.ws_subscription_filter.lp_delete()213 self.assertContentEqual(
216 transaction.begin()214 [self.initial_filter, self.subscription_filter],
217 self.assertRaises(215 self.subscription.bug_filters)
218 LostObjectError, getattr, self.subscription_filter,216 response = self.webservice.delete(self.subscription_filter_url)
219 "find_all_tags")217 self.assertEqual(200, response.status)
218 self.assertContentEqual(
219 [self.initial_filter], self.subscription.bug_filters)
220220
221221
222class TestBugSubscriptionFilterView(222class TestBugSubscriptionFilterView(
@@ -489,7 +489,7 @@ class TestBugSubscriptionFilterEditView(
489class TestBugSubscriptionFilterAdvancedFeatures(TestCaseWithFactory):489class TestBugSubscriptionFilterAdvancedFeatures(TestCaseWithFactory):
490 """A base class for testing advanced structural subscription features."""490 """A base class for testing advanced structural subscription features."""
491491
492 layer = LaunchpadFunctionalLayer492 layer = DatabaseFunctionalLayer
493493
494 def setUp(self):494 def setUp(self):
495 super(TestBugSubscriptionFilterAdvancedFeatures, self).setUp()495 super(TestBugSubscriptionFilterAdvancedFeatures, self).setUp()
diff --git a/lib/lp/bugs/browser/tests/test_structuralsubscription.py b/lib/lp/bugs/browser/tests/test_structuralsubscription.py
index db341b5..ff2b160 100644
--- a/lib/lp/bugs/browser/tests/test_structuralsubscription.py
+++ b/lib/lp/bugs/browser/tests/test_structuralsubscription.py
@@ -1,11 +1,10 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2019 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"""Tests for structural subscription traversal."""4"""Tests for structural subscription traversal."""
55
6from urlparse import urlparse6from urlparse import urlparse
77
8import transaction
9from zope.publisher.interfaces import NotFound8from zope.publisher.interfaces import NotFound
109
11from lp.registry.browser.distribution import DistributionNavigation10from lp.registry.browser.distribution import DistributionNavigation
@@ -17,19 +16,18 @@ from lp.registry.browser.milestone import MilestoneNavigation
17from lp.registry.browser.product import ProductNavigation16from lp.registry.browser.product import ProductNavigation
18from lp.registry.browser.productseries import ProductSeriesNavigation17from lp.registry.browser.productseries import ProductSeriesNavigation
19from lp.registry.browser.project import ProjectNavigation18from lp.registry.browser.project import ProjectNavigation
19from lp.services.webapp.interfaces import OAuthPermission
20from lp.services.webapp.publisher import canonical_url20from lp.services.webapp.publisher import canonical_url
21from lp.testing import (21from lp.testing import (
22 api_url,
22 FakeLaunchpadRequest,23 FakeLaunchpadRequest,
23 login,24 login,
24 logout,25 logout,
25 person_logged_in,26 person_logged_in,
26 TestCaseWithFactory,27 TestCaseWithFactory,
27 ws_object,
28 )
29from lp.testing.layers import (
30 AppServerLayer,
31 DatabaseFunctionalLayer,
32 )28 )
29from lp.testing.layers import DatabaseFunctionalLayer
30from lp.testing.pages import webservice_for_person
33from lp.testing.views import create_initialized_view31from lp.testing.views import create_initialized_view
3432
3533
@@ -217,7 +215,7 @@ class TestSourcePackageStructuralSubscribersPortletView(
217215
218class TestStructuralSubscriptionAPI(TestCaseWithFactory):216class TestStructuralSubscriptionAPI(TestCaseWithFactory):
219217
220 layer = AppServerLayer218 layer = DatabaseFunctionalLayer
221219
222 def setUp(self):220 def setUp(self):
223 super(TestStructuralSubscriptionAPI, self).setUp()221 super(TestStructuralSubscriptionAPI, self).setUp()
@@ -228,40 +226,59 @@ class TestStructuralSubscriptionAPI(TestCaseWithFactory):
228 self.subscription = self.structure.addBugSubscription(226 self.subscription = self.structure.addBugSubscription(
229 self.owner, self.owner)227 self.owner, self.owner)
230 self.initial_filter = self.subscription.bug_filters[0]228 self.initial_filter = self.subscription.bug_filters[0]
231 transaction.commit()229 self.subscription_url = api_url(self.subscription)
232 self.service = self.factory.makeLaunchpadService(self.owner)230 self.initial_filter_url = api_url(self.initial_filter)
233 self.ws_subscription = ws_object(self.service, self.subscription)231 self.webservice = webservice_for_person(
234 self.ws_subscription_filter = ws_object(232 self.owner, permission=OAuthPermission.WRITE_PUBLIC)
235 self.service, self.initial_filter)
236233
237 def test_newBugFilter(self):234 def test_newBugFilter(self):
238 # New bug subscription filters can be created with newBugFilter().235 # New bug subscription filters can be created with newBugFilter().
239 ws_subscription_filter = self.ws_subscription.newBugFilter()236 ws_subscription = self.getWebserviceJSON(
237 self.webservice, self.subscription_url)
238 response = self.webservice.named_post(
239 self.subscription_url, "newBugFilter")
240 self.assertEqual(201, response.status)
241 ws_subscription_filter = self.getWebserviceJSON(
242 self.webservice, response.getHeader("Location"))
240 self.assertEqual(243 self.assertEqual(
241 "bug_subscription_filter",244 "bug_subscription_filter",
242 urlparse(ws_subscription_filter.resource_type_link).fragment)245 urlparse(ws_subscription_filter["resource_type_link"]).fragment)
243 self.assertEqual(246 self.assertEqual(
244 ws_subscription_filter.structural_subscription.self_link,247 ws_subscription["self_link"],
245 self.ws_subscription.self_link)248 ws_subscription_filter["structural_subscription_link"])
246249
247 def test_bug_filters(self):250 def test_bug_filters(self):
248 # The bug_filters property is a collection of IBugSubscriptionFilter251 # The bug_filters property is a collection of IBugSubscriptionFilter
249 # instances previously created by newBugFilter().252 # instances previously created by newBugFilter().
250 bug_filter_links = lambda: set(253 ws_subscription = self.getWebserviceJSON(
251 bug_filter.self_link for bug_filter in (254 self.webservice, self.subscription_url)
252 self.ws_subscription.bug_filters))255 ws_initial_filter = self.getWebserviceJSON(
253 initial_filter_link = self.ws_subscription_filter.self_link256 self.webservice, self.initial_filter_url)
257
258 def bug_filter_links():
259 ws_bug_filters = self.getWebserviceJSON(
260 self.webservice,
261 ws_subscription["bug_filters_collection_link"])
262 return {entry["self_link"] for entry in ws_bug_filters["entries"]}
263
264 initial_filter_link = ws_initial_filter["self_link"]
254 self.assertContentEqual(265 self.assertContentEqual(
255 [initial_filter_link], bug_filter_links())266 [initial_filter_link], bug_filter_links())
256 # A new filter appears in the bug_filters collection.267 # A new filter appears in the bug_filters collection.
257 ws_subscription_filter1 = self.ws_subscription.newBugFilter()268 response = self.webservice.named_post(
269 self.subscription_url, "newBugFilter")
270 self.assertEqual(201, response.status)
271 ws_subscription_filter1_link = response.getHeader("Location")
258 self.assertContentEqual(272 self.assertContentEqual(
259 [ws_subscription_filter1.self_link, initial_filter_link],273 [ws_subscription_filter1_link, initial_filter_link],
260 bug_filter_links())274 bug_filter_links())
261 # A second new filter also appears in the bug_filters collection.275 # A second new filter also appears in the bug_filters collection.
262 ws_subscription_filter2 = self.ws_subscription.newBugFilter()276 response = self.webservice.named_post(
277 self.subscription_url, "newBugFilter")
278 self.assertEqual(201, response.status)
279 ws_subscription_filter2_link = response.getHeader("Location")
263 self.assertContentEqual(280 self.assertContentEqual(
264 [ws_subscription_filter1.self_link,281 [ws_subscription_filter1_link,
265 ws_subscription_filter2.self_link,282 ws_subscription_filter2_link,
266 initial_filter_link],283 initial_filter_link],
267 bug_filter_links())284 bug_filter_links())
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index c2874ba..7466f7e 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -10,7 +10,6 @@ import subprocess
10import unittest10import unittest
1111
12from lazr.lifecycle.snapshot import Snapshot12from lazr.lifecycle.snapshot import Snapshot
13from lazr.restfulclient.errors import Unauthorized
14from storm.store import Store13from storm.store import Store
15from testtools.matchers import Equals14from testtools.matchers import Equals
16from testtools.testcase import ExpectedException15from testtools.testcase import ExpectedException
@@ -86,12 +85,16 @@ from lp.services.log.logger import DevNullLogger
86from lp.services.propertycache import get_property_cache85from lp.services.propertycache import get_property_cache
87from lp.services.searchbuilder import any86from lp.services.searchbuilder import any
88from lp.services.webapp.authorization import check_permission87from lp.services.webapp.authorization import check_permission
89from lp.services.webapp.interfaces import ILaunchBag88from lp.services.webapp.interfaces import (
89 ILaunchBag,
90 OAuthPermission,
91 )
90from lp.services.webapp.snapshot import notify_modified92from lp.services.webapp.snapshot import notify_modified
91from lp.soyuz.interfaces.archive import ArchivePurpose93from lp.soyuz.interfaces.archive import ArchivePurpose
92from lp.testing import (94from lp.testing import (
93 admin_logged_in,95 admin_logged_in,
94 ANONYMOUS,96 ANONYMOUS,
97 api_url,
95 EventRecorder,98 EventRecorder,
96 feature_flags,99 feature_flags,
97 login,100 login,
@@ -103,15 +106,14 @@ from lp.testing import (
103 StormStatementRecorder,106 StormStatementRecorder,
104 TestCase,107 TestCase,
105 TestCaseWithFactory,108 TestCaseWithFactory,
106 ws_object,
107 )109 )
108from lp.testing.fakemethod import FakeMethod110from lp.testing.fakemethod import FakeMethod
109from lp.testing.layers import (111from lp.testing.layers import (
110 AppServerLayer,
111 CeleryJobLayer,112 CeleryJobLayer,
112 DatabaseFunctionalLayer,113 DatabaseFunctionalLayer,
113 )114 )
114from lp.testing.matchers import HasQueryCount115from lp.testing.matchers import HasQueryCount
116from lp.testing.pages import webservice_for_person
115117
116118
117BugData = namedtuple("BugData", ['owner', 'distro', 'distro_release',119BugData = namedtuple("BugData", ['owner', 'distro', 'distro_release',
@@ -2953,29 +2955,32 @@ class TestValidateNewTarget(TestCaseWithFactory, ValidateTargetMixin):
2953class TestWebservice(TestCaseWithFactory):2955class TestWebservice(TestCaseWithFactory):
2954 """Tests for the webservice."""2956 """Tests for the webservice."""
29552957
2956 layer = AppServerLayer2958 layer = DatabaseFunctionalLayer
29572959
2958 def test_delete_bugtask(self):2960 def test_delete_bugtask(self):
2959 """Test that a bugtask can be deleted."""2961 """Test that a bugtask can be deleted."""
2960 owner = self.factory.makePerson()2962 owner = self.factory.makePerson()
2961 some_person = self.factory.makePerson()2963 some_person = self.factory.makePerson()
2962 db_bug = self.factory.makeBug()2964 bug = self.factory.makeBug()
2963 db_bugtask = self.factory.makeBugTask(bug=db_bug, owner=owner)2965 bugtask = self.factory.makeBugTask(bug=bug, owner=owner)
2964 transaction.commit()2966 bugtask_url = api_url(bugtask)
2965 logout()
29662967
2967 # It will fail for an unauthorised user.2968 # It will fail for an unauthorised user.
2968 launchpad = self.factory.makeLaunchpadService(some_person)2969 webservice = webservice_for_person(
2969 bugtask = ws_object(launchpad, db_bugtask)2970 some_person, permission=OAuthPermission.WRITE_PUBLIC,
2970 self.assertRaises(Unauthorized, bugtask.lp_delete)2971 default_api_version="devel")
2972 response = webservice.delete(bugtask_url)
2973 self.assertEqual(401, response.status)
2974
2975 webservice = webservice_for_person(
2976 owner, permission=OAuthPermission.WRITE_PUBLIC,
2977 default_api_version="devel")
2978 response = webservice.delete(bugtask_url)
2979 self.assertEqual(200, response.status)
29712980
2972 launchpad = self.factory.makeLaunchpadService(owner)
2973 bugtask = ws_object(launchpad, db_bugtask)
2974 bugtask.lp_delete()
2975 transaction.commit()
2976 # Check the delete really worked.2981 # Check the delete really worked.
2977 with person_logged_in(removeSecurityProxy(db_bug).owner):2982 with person_logged_in(removeSecurityProxy(bug).owner):
2978 self.assertEqual([db_bug.default_bugtask], db_bug.bugtasks)2983 self.assertEqual([bug.default_bugtask], bug.bugtasks)
29792984
29802985
2981class TestBugTaskUserHasBugSupervisorPrivileges(TestCaseWithFactory):2986class TestBugTaskUserHasBugSupervisorPrivileges(TestCaseWithFactory):
diff --git a/lib/lp/bugs/tests/test_bug_messages_webservice.py b/lib/lp/bugs/tests/test_bug_messages_webservice.py
index feec1a7..5faae0e 100644
--- a/lib/lp/bugs/tests/test_bug_messages_webservice.py
+++ b/lib/lp/bugs/tests/test_bug_messages_webservice.py
@@ -1,4 +1,4 @@
1# Copyright 2011 Canonical Ltd. This software is licensed under the1# Copyright 2011-2019 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"""Webservice unit tests related to Launchpad Bug messages."""4"""Webservice unit tests related to Launchpad Bug messages."""
@@ -24,36 +24,43 @@ from lp.testing import (
24 logout,24 logout,
25 person_logged_in,25 person_logged_in,
26 TestCaseWithFactory,26 TestCaseWithFactory,
27 WebServiceTestCase,
28 )27 )
29from lp.testing.layers import (28from lp.testing.layers import (
30 DatabaseFunctionalLayer,29 DatabaseFunctionalLayer,
31 LaunchpadFunctionalLayer,30 LaunchpadFunctionalLayer,
32 )31 )
33from lp.testing.pages import LaunchpadWebServiceCaller32from lp.testing.pages import (
33 LaunchpadWebServiceCaller,
34 webservice_for_person,
35 )
3436
3537
36class TestMessageTraversal(WebServiceTestCase):38class TestMessageTraversal(TestCaseWithFactory):
37 """Tests safe traversal of bugs.39 """Tests safe traversal of bugs.
3840
39 See bug 607438."""41 See bug 607438."""
4042
43 layer = LaunchpadFunctionalLayer
44
41 def test_message_with_attachments(self):45 def test_message_with_attachments(self):
42 bugowner = self.factory.makePerson()46 bug = self.factory.makeBug()
43 bug = self.factory.makeBug(owner=bugowner)
44 # Traversal over bug messages attachments has no errors.47 # Traversal over bug messages attachments has no errors.
45 expected_messages = []48 expected_messages = []
46 with person_logged_in(bugowner):49 with person_logged_in(bug.owner):
47 for i in range(3):50 for i in range(3):
48 att = self.factory.makeBugAttachment(bug)51 att = self.factory.makeBugAttachment(bug)
49 expected_messages.append(att.message.subject)52 expected_messages.append(att.message.subject)
5053 bug_url = api_url(bug)
51 lp_user = self.factory.makePerson()54
52 lp_bug = self.wsObject(bug, lp_user)55 webservice = webservice_for_person(self.factory.makePerson())
5356 ws_bug = self.getWebserviceJSON(webservice, bug_url)
54 attachments = lp_bug.attachments57 ws_bug_attachments = self.getWebserviceJSON(
55 messages = [a.message.subject for a in attachments58 webservice, ws_bug['attachments_collection_link'])
56 if a.message is not None]59 messages = [
60 self.getWebserviceJSON(
61 webservice, attachment['message_link'])['subject']
62 for attachment in ws_bug_attachments['entries']
63 if attachment['message_link'] is not None]
57 self.assertContentEqual(64 self.assertContentEqual(
58 messages,65 messages,
59 expected_messages)66 expected_messages)
@@ -67,15 +74,21 @@ class TestMessageTraversal(WebServiceTestCase):
67 message_2 = self.factory.makeMessage()74 message_2 = self.factory.makeMessage()
68 message_2.parent = message_175 message_2.parent = message_1
69 bug = self.factory.makeBug()76 bug = self.factory.makeBug()
70 bug.linkMessage(message_2)77 with person_logged_in(bug.owner):
78 bug.linkMessage(message_2)
79 bug_url = api_url(bug)
80 message_2_url = api_url(message_2)
71 user = self.factory.makePerson()81 user = self.factory.makePerson()
72 lp_bug = self.wsObject(bug, user)82 webservice = webservice_for_person(user)
73 for lp_message in lp_bug.messages:83 ws_bug = self.getWebserviceJSON(webservice, bug_url)
84 ws_bug_messages = self.getWebserviceJSON(
85 webservice, ws_bug['messages_collection_link'])
86 for ws_message in ws_bug_messages['entries']:
74 # An IIndexedMessage's representation.87 # An IIndexedMessage's representation.
75 self.assertIs(None, lp_message.parent)88 self.assertIsNone(ws_message['parent_link'])
76 # An IMessage's representation.89 # An IMessage's representation.
77 lp_message = self.wsObject(message_2, user)90 ws_message = self.getWebserviceJSON(webservice, message_2_url)
78 self.assertIs(None, lp_message.parent)91 self.assertIsNone(ws_message['parent_link'])
7992
8093
81class TestBugMessage(TestCaseWithFactory):94class TestBugMessage(TestCaseWithFactory):
diff --git a/lib/lp/testing/__init__.py b/lib/lp/testing/__init__.py
index 6a0d9b2..747930d 100644
--- a/lib/lp/testing/__init__.py
+++ b/lib/lp/testing/__init__.py
@@ -827,6 +827,12 @@ class TestCase(testtools.TestCase, fixtures.TestWithFixtures):
827 "\n\n".join(str(n) for n in notifications)))827 "\n\n".join(str(n) for n in notifications)))
828 return notifications828 return notifications
829829
830 def getWebserviceJSON(self, webservice, url):
831 """Get the JSON representation of a webservice object given its URL."""
832 response = webservice.get(url)
833 self.assertEqual(200, response.status)
834 return response.jsonBody()
835
830836
831class TestCaseWithFactory(TestCase):837class TestCaseWithFactory(TestCase):
832838

Subscribers

People subscribed via source and target branches

to status/vote changes: