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

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 2ed526c11756da6da609280428da3d45366776ab
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:faster-blueprints-webservice-tests
Merge into: launchpad:master
Diff against target: 816 lines (+347/-228)
2 files modified
lib/lp/blueprints/tests/test_webservice.py (+340/-225)
lib/lp/testing/pages.py (+7/-3)
Reviewer Review Type Date Requested Status
Ioana Lasc (community) Approve
Review via email: mp+375664@code.launchpad.net

Commit message

Stop using launchpadlib in lp.blueprints.tests.test_webservice

Description of the change

Port the blueprints 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 this file from 149 seconds to 63 seconds on my laptop. It also removes the last remaining dependency on AppServerLayer's SMTP server outside of the Mailman integration tests.

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
diff --git a/lib/lp/blueprints/tests/test_webservice.py b/lib/lp/blueprints/tests/test_webservice.py
index c1bf31f..3235b05 100644
--- a/lib/lp/blueprints/tests/test_webservice.py
+++ b/lib/lp/blueprints/tests/test_webservice.py
@@ -1,4 +1,4 @@
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"""Webservice unit tests related to Launchpad blueprints."""4"""Webservice unit tests related to Launchpad blueprints."""
@@ -9,12 +9,17 @@ __metaclass__ = type
99
10import json10import json
1111
12from testtools.matchers import MatchesStructure12import iso8601
13import transaction13from testtools.matchers import (
14from zope.security.management import endInteraction14 AfterPreprocessing,
15from zope.security.proxy import removeSecurityProxy15 ContainsDict,
16 Equals,
17 MatchesListwise,
18 )
19from zope.component import getUtility
1620
17from lp.app.enums import InformationType21from lp.app.enums import InformationType
22from lp.app.interfaces.launchpad import ILaunchpadCelebrities
18from lp.blueprints.enums import SpecificationDefinitionStatus23from lp.blueprints.enums import SpecificationDefinitionStatus
19from lp.registry.enums import SpecificationSharingPolicy24from lp.registry.enums import SpecificationSharingPolicy
20from lp.services.webapp.interaction import ANONYMOUS25from lp.services.webapp.interaction import ANONYMOUS
@@ -22,45 +27,21 @@ from lp.services.webapp.interfaces import OAuthPermission
22from lp.testing import (27from lp.testing import (
23 admin_logged_in,28 admin_logged_in,
24 api_url,29 api_url,
25 launchpadlib_for,30 login,
31 logout,
26 person_logged_in,32 person_logged_in,
27 TestCaseWithFactory,33 TestCaseWithFactory,
28 ws_object,
29 )
30from lp.testing.layers import (
31 AppServerLayer,
32 DatabaseFunctionalLayer,
33 )34 )
35from lp.testing.layers import DatabaseFunctionalLayer
34from lp.testing.pages import (36from lp.testing.pages import (
35 LaunchpadWebServiceCaller,37 LaunchpadWebServiceCaller,
36 webservice_for_person,38 webservice_for_person,
37 )39 )
3840
3941
40class SpecificationWebserviceTestCase(TestCaseWithFactory):42class SpecificationWebserviceTests(TestCaseWithFactory):
41
42 def getLaunchpadlib(self):
43 user = self.factory.makePerson()
44 return launchpadlib_for("testing", user, version='devel')
45
46 def getSpecOnWebservice(self, spec_object):
47 launchpadlib = self.getLaunchpadlib()
48 # Ensure that there is an interaction so that the security
49 # checks for spec_object work.
50 with person_logged_in(ANONYMOUS):
51 url = '/%s/+spec/%s' % (spec_object.target.name, spec_object.name)
52 result = launchpadlib.load(url)
53 return result
54
55 def getPillarOnWebservice(self, pillar_obj):
56 pillar_name = pillar_obj.name
57 launchpadlib = self.getLaunchpadlib()
58 return launchpadlib.load(pillar_name)
59
60
61class SpecificationWebserviceTests(SpecificationWebserviceTestCase):
62 """Test accessing specification top-level webservice."""43 """Test accessing specification top-level webservice."""
63 layer = AppServerLayer44 layer = DatabaseFunctionalLayer
6445
65 def test_collection(self):46 def test_collection(self):
66 # `ISpecificationSet` is exposed as a webservice via /specs47 # `ISpecificationSet` is exposed as a webservice via /specs
@@ -132,9 +113,9 @@ class SpecificationWebserviceTests(SpecificationWebserviceTestCase):
132 self.assertEqual(201, response.status)113 self.assertEqual(201, response.status)
133114
134115
135class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):116class SpecificationAttributeWebserviceTests(TestCaseWithFactory):
136 """Test accessing specification attributes over the webservice."""117 """Test accessing specification attributes over the webservice."""
137 layer = AppServerLayer118 layer = DatabaseFunctionalLayer
138119
139 def test_representation_is_empty_on_1_dot_0(self):120 def test_representation_is_empty_on_1_dot_0(self):
140 # ISpecification is exposed on the 1.0 version so that they can be121 # ISpecification is exposed on the 1.0 version so that they can be
@@ -153,81 +134,138 @@ class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
153134
154 def test_representation_basics(self):135 def test_representation_basics(self):
155 spec = self.factory.makeSpecification()136 spec = self.factory.makeSpecification()
156 spec_webservice = self.getSpecOnWebservice(spec)137 spec_url = api_url(spec)
138 webservice = webservice_for_person(
139 spec.owner, default_api_version='devel')
140 response = webservice.get(spec_url)
141 self.assertEqual(200, response.status)
157 with person_logged_in(ANONYMOUS):142 with person_logged_in(ANONYMOUS):
158 self.assertThat(143 self.assertThat(
159 spec_webservice,144 response.jsonBody(),
160 MatchesStructure.byEquality(145 ContainsDict({
161 name=spec.name,146 'name': Equals(spec.name),
162 title=spec.title,147 'title': Equals(spec.title),
163 specification_url=spec.specurl,148 'specification_url': Equals(spec.specurl),
164 summary=spec.summary,149 'summary': Equals(spec.summary),
165 implementation_status=spec.implementation_status.title,150 'implementation_status': Equals(
166 definition_status=spec.definition_status.title,151 spec.implementation_status.title),
167 priority=spec.priority.title,152 'definition_status': Equals(
168 date_created=spec.datecreated,153 spec.definition_status.title),
169 whiteboard=spec.whiteboard,154 'priority': Equals(spec.priority.title),
170 workitems_text=spec.workitems_text))155 'date_created': AfterPreprocessing(
156 iso8601.parse_date, Equals(spec.datecreated)),
157 'whiteboard': Equals(spec.whiteboard),
158 'workitems_text': Equals(spec.workitems_text),
159 }))
171160
172 def test_representation_contains_target(self):161 def test_representation_contains_target(self):
173 spec = self.factory.makeSpecification(162 spec = self.factory.makeSpecification(
174 product=self.factory.makeProduct())163 product=self.factory.makeProduct())
175 spec_target_name = spec.target.name164 spec_url = api_url(spec)
176 spec_webservice = self.getSpecOnWebservice(spec)165 spec_target_url = api_url(spec.target)
177 self.assertEqual(spec_target_name, spec_webservice.target.name)166 webservice = webservice_for_person(
167 spec.owner, default_api_version='devel')
168 response = webservice.get(spec_url)
169 self.assertEqual(200, response.status)
170 self.assertEndsWith(
171 response.jsonBody()['target_link'], spec_target_url)
178172
179 def test_representation_contains_assignee(self):173 def test_representation_contains_assignee(self):
180 # Hard-code the person's name or else we'd need to set up a zope
181 # interaction as IPerson.name is protected.
182 spec = self.factory.makeSpecification(174 spec = self.factory.makeSpecification(
183 assignee=self.factory.makePerson(name='test-person'))175 assignee=self.factory.makePerson())
184 spec_webservice = self.getSpecOnWebservice(spec)176 spec_url = api_url(spec)
185 self.assertEqual('test-person', spec_webservice.assignee.name)177 spec_assignee_url = api_url(spec.assignee)
178 webservice = webservice_for_person(
179 spec.owner, default_api_version='devel')
180 response = webservice.get(spec_url)
181 self.assertEqual(200, response.status)
182 self.assertEndsWith(
183 response.jsonBody()['assignee_link'], spec_assignee_url)
186184
187 def test_representation_contains_drafter(self):185 def test_representation_contains_drafter(self):
188 spec = self.factory.makeSpecification(186 spec = self.factory.makeSpecification(
189 drafter=self.factory.makePerson(name='test-person'))187 drafter=self.factory.makePerson())
190 spec_webservice = self.getSpecOnWebservice(spec)188 spec_url = api_url(spec)
191 self.assertEqual('test-person', spec_webservice.drafter.name)189 spec_drafter_url = api_url(spec.drafter)
190 webservice = webservice_for_person(
191 spec.owner, default_api_version='devel')
192 response = webservice.get(spec_url)
193 self.assertEqual(200, response.status)
194 self.assertEndsWith(
195 response.jsonBody()['drafter_link'], spec_drafter_url)
192196
193 def test_representation_contains_approver(self):197 def test_representation_contains_approver(self):
194 spec = self.factory.makeSpecification(198 spec = self.factory.makeSpecification(
195 approver=self.factory.makePerson(name='test-person'))199 approver=self.factory.makePerson())
196 spec_webservice = self.getSpecOnWebservice(spec)200 spec_url = api_url(spec)
197 self.assertEqual('test-person', spec_webservice.approver.name)201 spec_approver_url = api_url(spec.approver)
202 webservice = webservice_for_person(
203 spec.owner, default_api_version='devel')
204 response = webservice.get(spec_url)
205 self.assertEqual(200, response.status)
206 self.assertEndsWith(
207 response.jsonBody()['approver_link'], spec_approver_url)
198208
199 def test_representation_contains_owner(self):209 def test_representation_contains_owner(self):
200 spec = self.factory.makeSpecification(210 spec = self.factory.makeSpecification(owner=self.factory.makePerson())
201 owner=self.factory.makePerson(name='test-person'))211 spec_url = api_url(spec)
202 spec_webservice = self.getSpecOnWebservice(spec)212 spec_owner_url = api_url(spec.owner)
203 self.assertEqual('test-person', spec_webservice.owner.name)213 webservice = webservice_for_person(
214 spec.owner, default_api_version='devel')
215 response = webservice.get(spec_url)
216 self.assertEqual(200, response.status)
217 self.assertEndsWith(response.jsonBody()['owner_link'], spec_owner_url)
204218
205 def test_representation_contains_milestone(self):219 def test_representation_contains_milestone(self):
206 product = self.factory.makeProduct()220 product = self.factory.makeProduct()
207 productseries = self.factory.makeProductSeries(product=product)221 productseries = self.factory.makeProductSeries(product=product)
208 milestone = self.factory.makeMilestone(222 milestone = self.factory.makeMilestone(
209 name="1.0", product=product, productseries=productseries)223 product=product, productseries=productseries)
224 milestone_url = api_url(milestone)
210 spec_object = self.factory.makeSpecification(225 spec_object = self.factory.makeSpecification(
211 product=product, goal=productseries, milestone=milestone)226 product=product, goal=productseries, milestone=milestone)
212 spec = self.getSpecOnWebservice(spec_object)227 spec_object_url = api_url(spec_object)
213 self.assertEqual("1.0", spec.milestone.name)228 webservice = webservice_for_person(
229 spec_object.owner, default_api_version='devel')
230 response = webservice.get(spec_object_url)
231 self.assertEqual(200, response.status)
232 self.assertEndsWith(
233 response.jsonBody()['milestone_link'], milestone_url)
214234
215 def test_representation_contains_dependencies(self):235 def test_representation_contains_dependencies(self):
216 spec = self.factory.makeSpecification()236 spec = self.factory.makeSpecification()
217 spec2 = self.factory.makeSpecification()237 spec2 = self.factory.makeSpecification()
218 spec2_name = spec2.name238 spec2_name = spec2.name
219 spec.createDependency(spec2)239 spec.createDependency(spec2)
220 spec_webservice = self.getSpecOnWebservice(spec)240 spec_url = api_url(spec)
221 self.assertEqual(1, spec_webservice.dependencies.total_size)241 webservice = webservice_for_person(
222 self.assertEqual(spec2_name, spec_webservice.dependencies[0].name)242 spec.owner, default_api_version='devel')
243 response = webservice.get(spec_url)
244 self.assertEqual(200, response.status)
245 response = webservice.get(
246 response.jsonBody()['dependencies_collection_link'])
247 self.assertEqual(200, response.status)
248 self.assertThat(response.jsonBody(), ContainsDict({
249 'total_size': Equals(1),
250 'entries': MatchesListwise([
251 ContainsDict({'name': Equals(spec2_name)}),
252 ]),
253 }))
223254
224 def test_representation_contains_linked_branches(self):255 def test_representation_contains_linked_branches(self):
225 spec = self.factory.makeSpecification()256 spec = self.factory.makeSpecification()
226 branch = self.factory.makeBranch()257 branch = self.factory.makeBranch()
227 person = self.factory.makePerson()258 person = self.factory.makePerson()
228 spec.linkBranch(branch, person)259 spec.linkBranch(branch, person)
229 spec_webservice = self.getSpecOnWebservice(spec)260 spec_url = api_url(spec)
230 self.assertEqual(1, spec_webservice.linked_branches.total_size)261 webservice = webservice_for_person(
262 spec.owner, default_api_version='devel')
263 response = webservice.get(spec_url)
264 self.assertEqual(200, response.status)
265 response = webservice.get(
266 response.jsonBody()['linked_branches_collection_link'])
267 self.assertEqual(200, response.status)
268 self.assertEqual(1, response.jsonBody()['total_size'])
231269
232 def test_representation_contains_bug_links(self):270 def test_representation_contains_bug_links(self):
233 spec = self.factory.makeSpecification()271 spec = self.factory.makeSpecification()
@@ -235,9 +273,17 @@ class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
235 person = self.factory.makePerson()273 person = self.factory.makePerson()
236 with person_logged_in(person):274 with person_logged_in(person):
237 spec.linkBug(bug)275 spec.linkBug(bug)
238 spec_webservice = self.getSpecOnWebservice(spec)276 spec_url = api_url(spec)
239 self.assertEqual(1, spec_webservice.bugs.total_size)277 webservice = webservice_for_person(
240 self.assertEqual(bug.id, spec_webservice.bugs[0].id)278 spec.owner, default_api_version='devel')
279 response = webservice.get(spec_url)
280 self.assertEqual(200, response.status)
281 response = webservice.get(
282 response.jsonBody()['bugs_collection_link'])
283 self.assertThat(response.jsonBody(), ContainsDict({
284 'total_size': Equals(1),
285 'entries': MatchesListwise([ContainsDict({'id': Equals(bug.id)})]),
286 }))
241287
242288
243class SpecificationMutationTests(TestCaseWithFactory):289class SpecificationMutationTests(TestCaseWithFactory):
@@ -289,89 +335,121 @@ class SpecificationMutationTests(TestCaseWithFactory):
289 "There is already a blueprint named foo for Fooix.", response.body)335 "There is already a blueprint named foo for Fooix.", response.body)
290336
291337
292class SpecificationTargetTests(SpecificationWebserviceTestCase):338class SpecificationTargetTests(TestCaseWithFactory):
293 """Tests for accessing specifications via their targets."""339 """Tests for accessing specifications via their targets."""
294 layer = AppServerLayer340 layer = DatabaseFunctionalLayer
295341
296 def test_get_specification_on_product(self):342 def test_get_specification_on_product(self):
297 product = self.factory.makeProduct(name="fooix")343 product = self.factory.makeProduct(name="fooix")
298 self.factory.makeSpecification(344 self.factory.makeSpecification(
299 product=product, name="some-spec")345 product=product, name="some-spec")
300 product_on_webservice = self.getPillarOnWebservice(product)346 product_url = api_url(product)
301 spec = product_on_webservice.getSpecification(name="some-spec")347 webservice = webservice_for_person(
302 self.assertEqual("some-spec", spec.name)348 self.factory.makePerson(), default_api_version="devel")
303 self.assertEqual("fooix", spec.target.name)349 response = webservice.named_get(
350 product_url, "getSpecification", name="some-spec")
351 self.assertEqual(200, response.status)
352 self.assertEqual("some-spec", response.jsonBody()["name"])
353 response = webservice.get(response.jsonBody()["target_link"])
354 self.assertEqual(200, response.status)
355 self.assertEqual("fooix", response.jsonBody()["name"])
304356
305 def test_get_specification_on_distribution(self):357 def test_get_specification_on_distribution(self):
306 distribution = self.factory.makeDistribution(name="foobuntu")358 distribution = self.factory.makeDistribution(name="foobuntu")
307 self.factory.makeSpecification(359 self.factory.makeSpecification(
308 distribution=distribution, name="some-spec")360 distribution=distribution, name="some-spec")
309 distro_on_webservice = self.getPillarOnWebservice(distribution)361 distribution_url = api_url(distribution)
310 spec = distro_on_webservice.getSpecification(name="some-spec")362 webservice = webservice_for_person(
311 self.assertEqual("some-spec", spec.name)363 self.factory.makePerson(), default_api_version="devel")
312 self.assertEqual("foobuntu", spec.target.name)364 response = webservice.named_get(
365 distribution_url, "getSpecification", name="some-spec")
366 self.assertEqual(200, response.status)
367 self.assertEqual("some-spec", response.jsonBody()["name"])
368 response = webservice.get(response.jsonBody()["target_link"])
369 self.assertEqual(200, response.status)
370 self.assertEqual("foobuntu", response.jsonBody()["name"])
313371
314 def test_get_specification_on_productseries(self):372 def test_get_specification_on_productseries(self):
315 product = self.factory.makeProduct(name="fooix")373 product = self.factory.makeProduct(name="fooix")
316 productseries = self.factory.makeProductSeries(374 productseries = self.factory.makeProductSeries(product=product)
317 product=product, name="fooix-dev")
318 self.factory.makeSpecification(375 self.factory.makeSpecification(
319 product=product, name="some-spec", goal=productseries)376 product=product, name="some-spec", goal=productseries)
320 product_on_webservice = self.getPillarOnWebservice(product)377 productseries_url = api_url(productseries)
321 productseries_on_webservice = product_on_webservice.getSeries(378 webservice = webservice_for_person(
322 name="fooix-dev")379 self.factory.makePerson(), default_api_version="devel")
323 spec = productseries_on_webservice.getSpecification(name="some-spec")380 response = webservice.named_get(
324 self.assertEqual("some-spec", spec.name)381 productseries_url, "getSpecification", name="some-spec")
325 self.assertEqual("fooix", spec.target.name)382 self.assertEqual(200, response.status)
383 self.assertEqual("some-spec", response.jsonBody()["name"])
384 response = webservice.get(response.jsonBody()["target_link"])
385 self.assertEqual(200, response.status)
386 self.assertEqual("fooix", response.jsonBody()["name"])
326387
327 def test_get_specification_on_distroseries(self):388 def test_get_specification_on_distroseries(self):
328 distribution = self.factory.makeDistribution(name="foobuntu")389 distribution = self.factory.makeDistribution(name="foobuntu")
329 distroseries = self.factory.makeDistroSeries(390 distroseries = self.factory.makeDistroSeries(
330 distribution=distribution, name="maudlin")391 distribution=distribution)
331 self.factory.makeSpecification(392 self.factory.makeSpecification(
332 distribution=distribution, name="some-spec",393 distribution=distribution, name="some-spec",
333 goal=distroseries)394 goal=distroseries)
334 distro_on_webservice = self.getPillarOnWebservice(distribution)395 distroseries_url = api_url(distroseries)
335 distroseries_on_webservice = distro_on_webservice.getSeries(396 webservice = webservice_for_person(
336 name_or_version="maudlin")397 self.factory.makePerson(), default_api_version="devel")
337 spec = distroseries_on_webservice.getSpecification(name="some-spec")398 response = webservice.named_get(
338 self.assertEqual("some-spec", spec.name)399 distroseries_url, "getSpecification", name="some-spec")
339 self.assertEqual("foobuntu", spec.target.name)400 self.assertEqual(200, response.status)
401 self.assertEqual("some-spec", response.jsonBody()["name"])
402 response = webservice.get(response.jsonBody()["target_link"])
403 self.assertEqual(200, response.status)
404 self.assertEqual("foobuntu", response.jsonBody()["name"])
340405
341 def test_get_specification_not_found(self):406 def test_get_specification_not_found(self):
342 product = self.factory.makeProduct()407 product = self.factory.makeProduct()
343 product_on_webservice = self.getPillarOnWebservice(product)408 product_url = api_url(product)
344 spec = product_on_webservice.getSpecification(name="nonexistant")409 webservice = webservice_for_person(
345 self.assertEqual(None, spec)410 self.factory.makePerson(), default_api_version="devel")
411 response = webservice.named_get(
412 product_url, "getSpecification", name="nonexistent")
413 self.assertEqual(200, response.status)
414 self.assertIsNone(response.jsonBody())
346415
347416
348class IHasSpecificationsTests(SpecificationWebserviceTestCase):417class IHasSpecificationsTests(TestCaseWithFactory):
349 """Tests for accessing IHasSpecifications methods over the webservice."""418 """Tests for accessing IHasSpecifications methods over the webservice."""
350 layer = DatabaseFunctionalLayer419 layer = DatabaseFunctionalLayer
351420
352 def assertNamesOfSpecificationsAre(self, expected_names, specifications):
353 names = [s.name for s in specifications]
354 self.assertContentEqual(expected_names, names)
355
356 def test_anonymous_access_to_collection(self):421 def test_anonymous_access_to_collection(self):
357 product = self.factory.makeProduct()422 product = self.factory.makeProduct()
358 self.factory.makeSpecification(product=product, name="spec1")423 self.factory.makeSpecification(product=product, name="spec1")
359 self.factory.makeSpecification(product=product, name="spec2")424 self.factory.makeSpecification(product=product, name="spec2")
360 # Need to endInteraction() because launchpadlib_for_anonymous() will425 product_url = api_url(product)
361 # setup a new one.426 logout()
362 endInteraction()427 webservice = LaunchpadWebServiceCaller(
363 lplib = launchpadlib_for('lplib-test', person=None, version='devel')428 "test", "", default_api_version="devel")
364 ws_product = ws_object(lplib, removeSecurityProxy(product))429 response = webservice.get(product_url)
365 self.assertNamesOfSpecificationsAre(430 self.assertEqual(200, response.status)
366 ["spec1", "spec2"], ws_product.all_specifications)431 response = webservice.get(
432 response.jsonBody()["all_specifications_collection_link"])
433 self.assertEqual(200, response.status)
434 self.assertContentEqual(
435 ["spec1", "spec2"],
436 [entry["name"] for entry in response.jsonBody()["entries"]])
367437
368 def test_product_all_specifications(self):438 def test_product_all_specifications(self):
369 product = self.factory.makeProduct()439 product = self.factory.makeProduct()
370 self.factory.makeSpecification(product=product, name="spec1")440 self.factory.makeSpecification(product=product, name="spec1")
371 self.factory.makeSpecification(product=product, name="spec2")441 self.factory.makeSpecification(product=product, name="spec2")
372 product_on_webservice = self.getPillarOnWebservice(product)442 product_url = api_url(product)
373 self.assertNamesOfSpecificationsAre(443 webservice = webservice_for_person(
374 ["spec1", "spec2"], product_on_webservice.all_specifications)444 self.factory.makePerson(), default_api_version="devel")
445 response = webservice.get(product_url)
446 self.assertEqual(200, response.status)
447 response = webservice.get(
448 response.jsonBody()["all_specifications_collection_link"])
449 self.assertEqual(200, response.status)
450 self.assertContentEqual(
451 ["spec1", "spec2"],
452 [entry["name"] for entry in response.jsonBody()["entries"]])
375453
376 def test_distribution_valid_specifications(self):454 def test_distribution_valid_specifications(self):
377 distribution = self.factory.makeDistribution()455 distribution = self.factory.makeDistribution()
@@ -380,120 +458,146 @@ class IHasSpecificationsTests(SpecificationWebserviceTestCase):
380 self.factory.makeSpecification(458 self.factory.makeSpecification(
381 distribution=distribution, name="spec2",459 distribution=distribution, name="spec2",
382 status=SpecificationDefinitionStatus.OBSOLETE)460 status=SpecificationDefinitionStatus.OBSOLETE)
383 distro_on_webservice = self.getPillarOnWebservice(distribution)461 distribution_url = api_url(distribution)
384 self.assertNamesOfSpecificationsAre(462 webservice = webservice_for_person(
385 ["spec1"], distro_on_webservice.valid_specifications)463 self.factory.makePerson(), default_api_version="devel")
464 response = webservice.get(distribution_url)
465 self.assertEqual(200, response.status)
466 response = webservice.get(
467 response.jsonBody()["valid_specifications_collection_link"])
468 self.assertEqual(200, response.status)
469 self.assertContentEqual(
470 ["spec1"],
471 [entry["name"] for entry in response.jsonBody()["entries"]])
386472
387473
388class TestSpecificationSubscription(SpecificationWebserviceTestCase):474class TestSpecificationSubscription(TestCaseWithFactory):
389475
390 layer = AppServerLayer476 layer = DatabaseFunctionalLayer
391477
392 def test_subscribe(self):478 def test_subscribe(self):
393 # Test subscribe() API.479 # Test subscribe() API.
394 with person_logged_in(ANONYMOUS):480 spec = self.factory.makeSpecification()
395 db_spec = self.factory.makeSpecification()481 person = self.factory.makePerson()
396 db_person = self.factory.makePerson()482 spec_url = api_url(spec)
397 launchpad = self.factory.makeLaunchpadService()483 person_url = api_url(person)
398484 webservice = webservice_for_person(
399 spec = ws_object(launchpad, db_spec)485 person, permission=OAuthPermission.WRITE_PUBLIC,
400 person = ws_object(launchpad, db_person)486 default_api_version="devel")
401 spec.subscribe(person=person, essential=True)487 response = webservice.named_post(
402 transaction.commit()488 spec_url, "subscribe", person=person_url, essential=True)
489 self.assertEqual(200, response.status)
403490
404 # Check the results.491 # Check the results.
405 sub = db_spec.subscription(db_person)492 login(ANONYMOUS)
493 sub = spec.subscription(person)
406 self.assertIsNot(None, sub)494 self.assertIsNot(None, sub)
407 self.assertTrue(sub.essential)495 self.assertTrue(sub.essential)
408496
409 def test_unsubscribe(self):497 def test_unsubscribe(self):
410 # Test unsubscribe() API.498 # Test unsubscribe() API.
411 with person_logged_in(ANONYMOUS):499 spec = self.factory.makeBlueprint()
412 db_spec = self.factory.makeBlueprint()500 person = self.factory.makePerson()
413 db_person = self.factory.makePerson()501 spec.subscribe(person=person)
414 db_spec.subscribe(person=db_person)502 spec_url = api_url(spec)
415 launchpad = self.factory.makeLaunchpadService(person=db_person)503 person_url = api_url(person)
416504 webservice = webservice_for_person(
417 spec = ws_object(launchpad, db_spec)505 person, permission=OAuthPermission.WRITE_PUBLIC,
418 person = ws_object(launchpad, db_person)506 default_api_version="devel")
419 spec.unsubscribe(person=person)507 response = webservice.named_post(
420 transaction.commit()508 spec_url, "unsubscribe", person=person_url)
509 self.assertEqual(200, response.status)
421510
422 # Check the results.511 # Check the results.
423 self.assertFalse(db_spec.isSubscribed(db_person))512 login(ANONYMOUS)
513 self.assertFalse(spec.isSubscribed(person))
424514
425 def test_canBeUnsubscribedByUser(self):515 def test_canBeUnsubscribedByUser(self):
426 # Test canBeUnsubscribedByUser() API.516 # Test canBeUnsubscribedByUser() API.
427 webservice = LaunchpadWebServiceCaller(517 spec = self.factory.makeSpecification()
428 'launchpad-library', 'salgado-change-anything',518 person = self.factory.makePerson()
429 domain='api.launchpad.test:8085')519 with person_logged_in(person):
430520 subscription = spec.subscribe(
431 with person_logged_in(ANONYMOUS):521 person=person, subscribed_by=person, essential=True)
432 db_spec = self.factory.makeSpecification()522 subscription_url = api_url(subscription)
433 db_person = self.factory.makePerson()523 admin_webservice = webservice_for_person(
434 launchpad = self.factory.makeLaunchpadService()524 getUtility(ILaunchpadCelebrities).admin.teamowner,
435525 default_api_version="devel")
436 spec = ws_object(launchpad, db_spec)526 response = admin_webservice.named_get(
437 person = ws_object(launchpad, db_person)527 subscription_url, "canBeUnsubscribedByUser")
438 subscription = spec.subscribe(person=person, essential=True)528 self.assertEqual(200, response.status)
439 transaction.commit()529 self.assertIs(True, response.jsonBody())
440
441 result = webservice.named_get(
442 subscription['self_link'], 'canBeUnsubscribedByUser').jsonBody()
443 self.assertTrue(result)
444530
445531
446class TestSpecificationBugLinks(SpecificationWebserviceTestCase):532class TestSpecificationBugLinks(TestCaseWithFactory):
447533
448 layer = AppServerLayer534 layer = DatabaseFunctionalLayer
449535
450 def test_bug_linking(self):536 def test_bug_linking(self):
451 # Set up a spec, person, and bug.537 # Set up a spec, person, and bug.
452 with person_logged_in(ANONYMOUS):538 spec = self.factory.makeSpecification()
453 db_spec = self.factory.makeSpecification()539 person = self.factory.makePerson()
454 db_person = self.factory.makePerson()540 bug = self.factory.makeBug()
455 db_bug = self.factory.makeBug()541 spec_url = api_url(spec)
456 launchpad = self.factory.makeLaunchpadService()542 bug_url = api_url(bug)
543 webservice = webservice_for_person(
544 person, permission=OAuthPermission.WRITE_PUBLIC,
545 default_api_version="devel")
546
547 # There are no bugs associated with the spec/blueprint yet.
548 response = webservice.get(spec_url)
549 self.assertEqual(200, response.status)
550 spec_bugs_url = response.jsonBody()["bugs_collection_link"]
551 response = webservice.get(spec_bugs_url)
552 self.assertEqual(200, response.status)
553 self.assertEqual(0, response.jsonBody()["total_size"])
457554
458 # Link the bug to the spec via the web service.555 # Link the bug to the spec via the web service.
459 with person_logged_in(db_person):556 response = webservice.named_post(spec_url, "linkBug", bug=bug_url)
460 spec = ws_object(launchpad, db_spec)557 self.assertEqual(200, response.status)
461 bug = ws_object(launchpad, db_bug)
462 # There are no bugs associated with the spec/blueprint yet.
463 self.assertEqual(0, spec.bugs.total_size)
464 spec.linkBug(bug=bug)
465 transaction.commit()
466558
467 # The spec now has one bug associated with it and that bug is the one559 # The spec now has one bug associated with it and that bug is the one
468 # we linked.560 # we linked.
469 self.assertEqual(1, spec.bugs.total_size)561 response = webservice.get(spec_bugs_url)
470 self.assertEqual(bug.id, spec.bugs[0].id)562 self.assertEqual(200, response.status)
563 self.assertThat(response.jsonBody(), ContainsDict({
564 "total_size": Equals(1),
565 "entries": MatchesListwise([ContainsDict({"id": Equals(bug.id)})]),
566 }))
471567
472 def test_bug_unlinking(self):568 def test_bug_unlinking(self):
473 # Set up a spec, person, and bug, then link the bug to the spec.569 # Set up a spec, person, and bug, then link the bug to the spec.
474 with person_logged_in(ANONYMOUS):570 spec = self.factory.makeBlueprint()
475 db_spec = self.factory.makeBlueprint()571 person = self.factory.makePerson()
476 db_person = self.factory.makePerson()572 bug = self.factory.makeBug()
477 db_bug = self.factory.makeBug()573 spec_url = api_url(spec)
478 launchpad = self.factory.makeLaunchpadService(person=db_person)574 bug_url = api_url(bug)
479575 with person_logged_in(spec.owner):
480 spec = ws_object(launchpad, db_spec)576 spec.linkBug(bug)
481 bug = ws_object(launchpad, db_bug)577 webservice = webservice_for_person(
482 spec.linkBug(bug=bug)578 person, permission=OAuthPermission.WRITE_PUBLIC,
579 default_api_version="devel")
483580
484 # There is only one bug linked at the moment.581 # There is only one bug linked at the moment.
485 self.assertEqual(1, spec.bugs.total_size)582 response = webservice.get(spec_url)
583 self.assertEqual(200, response.status)
584 spec_bugs_url = response.jsonBody()["bugs_collection_link"]
585 response = webservice.get(spec_bugs_url)
586 self.assertEqual(200, response.status)
587 self.assertEqual(1, response.jsonBody()["total_size"])
486588
487 spec.unlinkBug(bug=bug)589 response = webservice.named_post(spec_url, "unlinkBug", bug=bug_url)
488 transaction.commit()590 self.assertEqual(200, response.status)
489591
490 # Now that we've unlinked the bug, there are no linked bugs at all.592 # Now that we've unlinked the bug, there are no linked bugs at all.
491 self.assertEqual(0, spec.bugs.total_size)593 response = webservice.get(spec_bugs_url)
594 self.assertEqual(200, response.status)
595 self.assertEqual(0, response.jsonBody()["total_size"])
492596
493597
494class TestSpecificationGoalHandling(SpecificationWebserviceTestCase):598class TestSpecificationGoalHandling(TestCaseWithFactory):
495599
496 layer = AppServerLayer600 layer = DatabaseFunctionalLayer
497601
498 def setUp(self):602 def setUp(self):
499 super(TestSpecificationGoalHandling, self).setUp()603 super(TestSpecificationGoalHandling, self).setUp()
@@ -501,53 +605,64 @@ class TestSpecificationGoalHandling(SpecificationWebserviceTestCase):
501 self.proposer = self.factory.makePerson()605 self.proposer = self.factory.makePerson()
502 self.product = self.factory.makeProduct(driver=self.driver)606 self.product = self.factory.makeProduct(driver=self.driver)
503 self.series = self.factory.makeProductSeries(product=self.product)607 self.series = self.factory.makeProductSeries(product=self.product)
608 self.series_url = api_url(self.series)
504609
505 def test_goal_propose_and_accept(self):610 def test_goal_propose_and_accept(self):
506 # Webservice clients can propose and accept spec series goals.611 # Webservice clients can propose and accept spec series goals.
507 db_spec = self.factory.makeBlueprint(product=self.product,612 spec = self.factory.makeBlueprint(
508 owner=self.proposer)613 product=self.product, owner=self.proposer)
614 spec_url = api_url(spec)
615
509 # Propose for series goal616 # Propose for series goal
617 proposer_webservice = webservice_for_person(
618 self.proposer, permission=OAuthPermission.WRITE_PUBLIC,
619 default_api_version="devel")
620 response = proposer_webservice.named_post(
621 spec_url, "proposeGoal", goal=self.series_url)
622 self.assertEqual(200, response.status)
510 with person_logged_in(self.proposer):623 with person_logged_in(self.proposer):
511 launchpad = self.factory.makeLaunchpadService(person=self.proposer)624 self.assertEqual(spec.goal, self.series)
512 spec = ws_object(launchpad, db_spec)
513 series = ws_object(launchpad, self.series)
514 spec.proposeGoal(goal=series)
515 transaction.commit()
516 self.assertEqual(db_spec.goal, self.series)
517 self.assertFalse(spec.has_accepted_goal)625 self.assertFalse(spec.has_accepted_goal)
518626
519 # Accept series goal627 # Accept series goal
628 driver_webservice = webservice_for_person(
629 self.driver, permission=OAuthPermission.WRITE_PUBLIC,
630 default_api_version="devel")
631 response = driver_webservice.named_post(spec_url, "acceptGoal")
632 self.assertEqual(200, response.status)
520 with person_logged_in(self.driver):633 with person_logged_in(self.driver):
521 launchpad = self.factory.makeLaunchpadService(person=self.driver)
522 spec = ws_object(launchpad, db_spec)
523 spec.acceptGoal()
524 transaction.commit()
525 self.assertTrue(spec.has_accepted_goal)634 self.assertTrue(spec.has_accepted_goal)
526635
527 def test_goal_propose_decline_and_clear(self):636 def test_goal_propose_decline_and_clear(self):
528 # Webservice clients can decline and clear spec series goals.637 # Webservice clients can decline and clear spec series goals.
529 db_spec = self.factory.makeBlueprint(product=self.product,638 spec = self.factory.makeBlueprint(
530 owner=self.proposer)639 product=self.product, owner=self.proposer)
640 spec_url = api_url(spec)
641
531 # Propose for series goal642 # Propose for series goal
643 proposer_webservice = webservice_for_person(
644 self.proposer, permission=OAuthPermission.WRITE_PUBLIC,
645 default_api_version="devel")
646 response = proposer_webservice.named_post(
647 spec_url, "proposeGoal", goal=self.series_url)
648 self.assertEqual(200, response.status)
532 with person_logged_in(self.proposer):649 with person_logged_in(self.proposer):
533 launchpad = self.factory.makeLaunchpadService(person=self.proposer)650 self.assertEqual(spec.goal, self.series)
534 spec = ws_object(launchpad, db_spec)
535 series = ws_object(launchpad, self.series)
536 spec.proposeGoal(goal=series)
537 transaction.commit()
538 self.assertEqual(db_spec.goal, self.series)
539 self.assertFalse(spec.has_accepted_goal)651 self.assertFalse(spec.has_accepted_goal)
540652
653 # Decline series goal
654 driver_webservice = webservice_for_person(
655 self.driver, permission=OAuthPermission.WRITE_PUBLIC,
656 default_api_version="devel")
657 response = driver_webservice.named_post(spec_url, "declineGoal")
658 self.assertEqual(200, response.status)
541 with person_logged_in(self.driver):659 with person_logged_in(self.driver):
542 # Decline series goal
543 launchpad = self.factory.makeLaunchpadService(person=self.driver)
544 spec = ws_object(launchpad, db_spec)
545 spec.declineGoal()
546 transaction.commit()
547 self.assertFalse(spec.has_accepted_goal)660 self.assertFalse(spec.has_accepted_goal)
548 self.assertEqual(db_spec.goal, self.series)661 self.assertEqual(spec.goal, self.series)
549662
550 # Clear series goal as a driver663 # Clear series goal as a driver
551 spec.proposeGoal(goal=None)664 response = driver_webservice.named_post(
552 transaction.commit()665 spec_url, "proposeGoal", goal=None)
553 self.assertIsNone(db_spec.goal)666 self.assertEqual(200, response.status)
667 with person_logged_in(self.driver):
668 self.assertIsNone(spec.goal)
diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
index cff8368..b24f141 100644
--- a/lib/lp/testing/pages.py
+++ b/lib/lp/testing/pages.py
@@ -158,7 +158,8 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
158158
159 def __init__(self, oauth_consumer_key=None, oauth_access_key=None,159 def __init__(self, oauth_consumer_key=None, oauth_access_key=None,
160 oauth_access_secret=None, handle_errors=True,160 oauth_access_secret=None, handle_errors=True,
161 domain='api.launchpad.test', protocol='http'):161 domain='api.launchpad.test', protocol='http',
162 default_api_version=None):
162 """Create a LaunchpadWebServiceCaller.163 """Create a LaunchpadWebServiceCaller.
163 :param oauth_consumer_key: The OAuth consumer key to use.164 :param oauth_consumer_key: The OAuth consumer key to use.
164 :param oauth_access_key: The OAuth access key to use for the request.165 :param oauth_access_key: The OAuth access key to use for the request.
@@ -184,6 +185,8 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
184 self.consumer = None185 self.consumer = None
185 self.access_token = None186 self.access_token = None
186 self.handle_errors = handle_errors187 self.handle_errors = handle_errors
188 if default_api_version is not None:
189 self.default_api_version = default_api_version
187 WebServiceCaller.__init__(self, handle_errors, domain, protocol)190 WebServiceCaller.__init__(self, handle_errors, domain, protocol)
188191
189 default_api_version = "beta"192 default_api_version = "beta"
@@ -745,7 +748,7 @@ def safe_canonical_url(*args, **kwargs):
745748
746def webservice_for_person(person, consumer_key=u'launchpad-library',749def webservice_for_person(person, consumer_key=u'launchpad-library',
747 permission=OAuthPermission.READ_PUBLIC,750 permission=OAuthPermission.READ_PUBLIC,
748 context=None):751 context=None, default_api_version=None):
749 """Return a valid LaunchpadWebServiceCaller for the person.752 """Return a valid LaunchpadWebServiceCaller for the person.
750753
751 Use this method to create a way to test the webservice that doesn't depend754 Use this method to create a way to test the webservice that doesn't depend
@@ -763,7 +766,8 @@ def webservice_for_person(person, consumer_key=u'launchpad-library',
763 access_token, access_secret = request_token.createAccessToken()766 access_token, access_secret = request_token.createAccessToken()
764 logout()767 logout()
765 service = LaunchpadWebServiceCaller(768 service = LaunchpadWebServiceCaller(
766 consumer_key, access_token.key, access_secret)769 consumer_key, access_token.key, access_secret,
770 default_api_version=default_api_version)
767 service.user = person771 service.user = person
768 return service772 return service
769773

Subscribers

People subscribed via source and target branches

to status/vote changes: