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
1diff --git a/lib/lp/blueprints/tests/test_webservice.py b/lib/lp/blueprints/tests/test_webservice.py
2index c1bf31f..3235b05 100644
3--- a/lib/lp/blueprints/tests/test_webservice.py
4+++ b/lib/lp/blueprints/tests/test_webservice.py
5@@ -1,4 +1,4 @@
6-# Copyright 2009 Canonical Ltd. This software is licensed under the
7+# Copyright 2009-2019 Canonical Ltd. This software is licensed under the
8 # GNU Affero General Public License version 3 (see the file LICENSE).
9
10 """Webservice unit tests related to Launchpad blueprints."""
11@@ -9,12 +9,17 @@ __metaclass__ = type
12
13 import json
14
15-from testtools.matchers import MatchesStructure
16-import transaction
17-from zope.security.management import endInteraction
18-from zope.security.proxy import removeSecurityProxy
19+import iso8601
20+from testtools.matchers import (
21+ AfterPreprocessing,
22+ ContainsDict,
23+ Equals,
24+ MatchesListwise,
25+ )
26+from zope.component import getUtility
27
28 from lp.app.enums import InformationType
29+from lp.app.interfaces.launchpad import ILaunchpadCelebrities
30 from lp.blueprints.enums import SpecificationDefinitionStatus
31 from lp.registry.enums import SpecificationSharingPolicy
32 from lp.services.webapp.interaction import ANONYMOUS
33@@ -22,45 +27,21 @@ from lp.services.webapp.interfaces import OAuthPermission
34 from lp.testing import (
35 admin_logged_in,
36 api_url,
37- launchpadlib_for,
38+ login,
39+ logout,
40 person_logged_in,
41 TestCaseWithFactory,
42- ws_object,
43- )
44-from lp.testing.layers import (
45- AppServerLayer,
46- DatabaseFunctionalLayer,
47 )
48+from lp.testing.layers import DatabaseFunctionalLayer
49 from lp.testing.pages import (
50 LaunchpadWebServiceCaller,
51 webservice_for_person,
52 )
53
54
55-class SpecificationWebserviceTestCase(TestCaseWithFactory):
56-
57- def getLaunchpadlib(self):
58- user = self.factory.makePerson()
59- return launchpadlib_for("testing", user, version='devel')
60-
61- def getSpecOnWebservice(self, spec_object):
62- launchpadlib = self.getLaunchpadlib()
63- # Ensure that there is an interaction so that the security
64- # checks for spec_object work.
65- with person_logged_in(ANONYMOUS):
66- url = '/%s/+spec/%s' % (spec_object.target.name, spec_object.name)
67- result = launchpadlib.load(url)
68- return result
69-
70- def getPillarOnWebservice(self, pillar_obj):
71- pillar_name = pillar_obj.name
72- launchpadlib = self.getLaunchpadlib()
73- return launchpadlib.load(pillar_name)
74-
75-
76-class SpecificationWebserviceTests(SpecificationWebserviceTestCase):
77+class SpecificationWebserviceTests(TestCaseWithFactory):
78 """Test accessing specification top-level webservice."""
79- layer = AppServerLayer
80+ layer = DatabaseFunctionalLayer
81
82 def test_collection(self):
83 # `ISpecificationSet` is exposed as a webservice via /specs
84@@ -132,9 +113,9 @@ class SpecificationWebserviceTests(SpecificationWebserviceTestCase):
85 self.assertEqual(201, response.status)
86
87
88-class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
89+class SpecificationAttributeWebserviceTests(TestCaseWithFactory):
90 """Test accessing specification attributes over the webservice."""
91- layer = AppServerLayer
92+ layer = DatabaseFunctionalLayer
93
94 def test_representation_is_empty_on_1_dot_0(self):
95 # ISpecification is exposed on the 1.0 version so that they can be
96@@ -153,81 +134,138 @@ class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
97
98 def test_representation_basics(self):
99 spec = self.factory.makeSpecification()
100- spec_webservice = self.getSpecOnWebservice(spec)
101+ spec_url = api_url(spec)
102+ webservice = webservice_for_person(
103+ spec.owner, default_api_version='devel')
104+ response = webservice.get(spec_url)
105+ self.assertEqual(200, response.status)
106 with person_logged_in(ANONYMOUS):
107 self.assertThat(
108- spec_webservice,
109- MatchesStructure.byEquality(
110- name=spec.name,
111- title=spec.title,
112- specification_url=spec.specurl,
113- summary=spec.summary,
114- implementation_status=spec.implementation_status.title,
115- definition_status=spec.definition_status.title,
116- priority=spec.priority.title,
117- date_created=spec.datecreated,
118- whiteboard=spec.whiteboard,
119- workitems_text=spec.workitems_text))
120+ response.jsonBody(),
121+ ContainsDict({
122+ 'name': Equals(spec.name),
123+ 'title': Equals(spec.title),
124+ 'specification_url': Equals(spec.specurl),
125+ 'summary': Equals(spec.summary),
126+ 'implementation_status': Equals(
127+ spec.implementation_status.title),
128+ 'definition_status': Equals(
129+ spec.definition_status.title),
130+ 'priority': Equals(spec.priority.title),
131+ 'date_created': AfterPreprocessing(
132+ iso8601.parse_date, Equals(spec.datecreated)),
133+ 'whiteboard': Equals(spec.whiteboard),
134+ 'workitems_text': Equals(spec.workitems_text),
135+ }))
136
137 def test_representation_contains_target(self):
138 spec = self.factory.makeSpecification(
139 product=self.factory.makeProduct())
140- spec_target_name = spec.target.name
141- spec_webservice = self.getSpecOnWebservice(spec)
142- self.assertEqual(spec_target_name, spec_webservice.target.name)
143+ spec_url = api_url(spec)
144+ spec_target_url = api_url(spec.target)
145+ webservice = webservice_for_person(
146+ spec.owner, default_api_version='devel')
147+ response = webservice.get(spec_url)
148+ self.assertEqual(200, response.status)
149+ self.assertEndsWith(
150+ response.jsonBody()['target_link'], spec_target_url)
151
152 def test_representation_contains_assignee(self):
153- # Hard-code the person's name or else we'd need to set up a zope
154- # interaction as IPerson.name is protected.
155 spec = self.factory.makeSpecification(
156- assignee=self.factory.makePerson(name='test-person'))
157- spec_webservice = self.getSpecOnWebservice(spec)
158- self.assertEqual('test-person', spec_webservice.assignee.name)
159+ assignee=self.factory.makePerson())
160+ spec_url = api_url(spec)
161+ spec_assignee_url = api_url(spec.assignee)
162+ webservice = webservice_for_person(
163+ spec.owner, default_api_version='devel')
164+ response = webservice.get(spec_url)
165+ self.assertEqual(200, response.status)
166+ self.assertEndsWith(
167+ response.jsonBody()['assignee_link'], spec_assignee_url)
168
169 def test_representation_contains_drafter(self):
170 spec = self.factory.makeSpecification(
171- drafter=self.factory.makePerson(name='test-person'))
172- spec_webservice = self.getSpecOnWebservice(spec)
173- self.assertEqual('test-person', spec_webservice.drafter.name)
174+ drafter=self.factory.makePerson())
175+ spec_url = api_url(spec)
176+ spec_drafter_url = api_url(spec.drafter)
177+ webservice = webservice_for_person(
178+ spec.owner, default_api_version='devel')
179+ response = webservice.get(spec_url)
180+ self.assertEqual(200, response.status)
181+ self.assertEndsWith(
182+ response.jsonBody()['drafter_link'], spec_drafter_url)
183
184 def test_representation_contains_approver(self):
185 spec = self.factory.makeSpecification(
186- approver=self.factory.makePerson(name='test-person'))
187- spec_webservice = self.getSpecOnWebservice(spec)
188- self.assertEqual('test-person', spec_webservice.approver.name)
189+ approver=self.factory.makePerson())
190+ spec_url = api_url(spec)
191+ spec_approver_url = api_url(spec.approver)
192+ webservice = webservice_for_person(
193+ spec.owner, default_api_version='devel')
194+ response = webservice.get(spec_url)
195+ self.assertEqual(200, response.status)
196+ self.assertEndsWith(
197+ response.jsonBody()['approver_link'], spec_approver_url)
198
199 def test_representation_contains_owner(self):
200- spec = self.factory.makeSpecification(
201- owner=self.factory.makePerson(name='test-person'))
202- spec_webservice = self.getSpecOnWebservice(spec)
203- self.assertEqual('test-person', spec_webservice.owner.name)
204+ spec = self.factory.makeSpecification(owner=self.factory.makePerson())
205+ spec_url = api_url(spec)
206+ spec_owner_url = api_url(spec.owner)
207+ webservice = webservice_for_person(
208+ spec.owner, default_api_version='devel')
209+ response = webservice.get(spec_url)
210+ self.assertEqual(200, response.status)
211+ self.assertEndsWith(response.jsonBody()['owner_link'], spec_owner_url)
212
213 def test_representation_contains_milestone(self):
214 product = self.factory.makeProduct()
215 productseries = self.factory.makeProductSeries(product=product)
216 milestone = self.factory.makeMilestone(
217- name="1.0", product=product, productseries=productseries)
218+ product=product, productseries=productseries)
219+ milestone_url = api_url(milestone)
220 spec_object = self.factory.makeSpecification(
221 product=product, goal=productseries, milestone=milestone)
222- spec = self.getSpecOnWebservice(spec_object)
223- self.assertEqual("1.0", spec.milestone.name)
224+ spec_object_url = api_url(spec_object)
225+ webservice = webservice_for_person(
226+ spec_object.owner, default_api_version='devel')
227+ response = webservice.get(spec_object_url)
228+ self.assertEqual(200, response.status)
229+ self.assertEndsWith(
230+ response.jsonBody()['milestone_link'], milestone_url)
231
232 def test_representation_contains_dependencies(self):
233 spec = self.factory.makeSpecification()
234 spec2 = self.factory.makeSpecification()
235 spec2_name = spec2.name
236 spec.createDependency(spec2)
237- spec_webservice = self.getSpecOnWebservice(spec)
238- self.assertEqual(1, spec_webservice.dependencies.total_size)
239- self.assertEqual(spec2_name, spec_webservice.dependencies[0].name)
240+ spec_url = api_url(spec)
241+ webservice = webservice_for_person(
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+ }))
254
255 def test_representation_contains_linked_branches(self):
256 spec = self.factory.makeSpecification()
257 branch = self.factory.makeBranch()
258 person = self.factory.makePerson()
259 spec.linkBranch(branch, person)
260- spec_webservice = self.getSpecOnWebservice(spec)
261- self.assertEqual(1, spec_webservice.linked_branches.total_size)
262+ spec_url = api_url(spec)
263+ webservice = webservice_for_person(
264+ spec.owner, default_api_version='devel')
265+ response = webservice.get(spec_url)
266+ self.assertEqual(200, response.status)
267+ response = webservice.get(
268+ response.jsonBody()['linked_branches_collection_link'])
269+ self.assertEqual(200, response.status)
270+ self.assertEqual(1, response.jsonBody()['total_size'])
271
272 def test_representation_contains_bug_links(self):
273 spec = self.factory.makeSpecification()
274@@ -235,9 +273,17 @@ class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase):
275 person = self.factory.makePerson()
276 with person_logged_in(person):
277 spec.linkBug(bug)
278- spec_webservice = self.getSpecOnWebservice(spec)
279- self.assertEqual(1, spec_webservice.bugs.total_size)
280- self.assertEqual(bug.id, spec_webservice.bugs[0].id)
281+ spec_url = api_url(spec)
282+ webservice = webservice_for_person(
283+ spec.owner, default_api_version='devel')
284+ response = webservice.get(spec_url)
285+ self.assertEqual(200, response.status)
286+ response = webservice.get(
287+ response.jsonBody()['bugs_collection_link'])
288+ self.assertThat(response.jsonBody(), ContainsDict({
289+ 'total_size': Equals(1),
290+ 'entries': MatchesListwise([ContainsDict({'id': Equals(bug.id)})]),
291+ }))
292
293
294 class SpecificationMutationTests(TestCaseWithFactory):
295@@ -289,89 +335,121 @@ class SpecificationMutationTests(TestCaseWithFactory):
296 "There is already a blueprint named foo for Fooix.", response.body)
297
298
299-class SpecificationTargetTests(SpecificationWebserviceTestCase):
300+class SpecificationTargetTests(TestCaseWithFactory):
301 """Tests for accessing specifications via their targets."""
302- layer = AppServerLayer
303+ layer = DatabaseFunctionalLayer
304
305 def test_get_specification_on_product(self):
306 product = self.factory.makeProduct(name="fooix")
307 self.factory.makeSpecification(
308 product=product, name="some-spec")
309- product_on_webservice = self.getPillarOnWebservice(product)
310- spec = product_on_webservice.getSpecification(name="some-spec")
311- self.assertEqual("some-spec", spec.name)
312- self.assertEqual("fooix", spec.target.name)
313+ product_url = api_url(product)
314+ webservice = webservice_for_person(
315+ self.factory.makePerson(), default_api_version="devel")
316+ response = webservice.named_get(
317+ product_url, "getSpecification", name="some-spec")
318+ self.assertEqual(200, response.status)
319+ self.assertEqual("some-spec", response.jsonBody()["name"])
320+ response = webservice.get(response.jsonBody()["target_link"])
321+ self.assertEqual(200, response.status)
322+ self.assertEqual("fooix", response.jsonBody()["name"])
323
324 def test_get_specification_on_distribution(self):
325 distribution = self.factory.makeDistribution(name="foobuntu")
326 self.factory.makeSpecification(
327 distribution=distribution, name="some-spec")
328- distro_on_webservice = self.getPillarOnWebservice(distribution)
329- spec = distro_on_webservice.getSpecification(name="some-spec")
330- self.assertEqual("some-spec", spec.name)
331- self.assertEqual("foobuntu", spec.target.name)
332+ distribution_url = api_url(distribution)
333+ webservice = webservice_for_person(
334+ self.factory.makePerson(), default_api_version="devel")
335+ response = webservice.named_get(
336+ distribution_url, "getSpecification", name="some-spec")
337+ self.assertEqual(200, response.status)
338+ self.assertEqual("some-spec", response.jsonBody()["name"])
339+ response = webservice.get(response.jsonBody()["target_link"])
340+ self.assertEqual(200, response.status)
341+ self.assertEqual("foobuntu", response.jsonBody()["name"])
342
343 def test_get_specification_on_productseries(self):
344 product = self.factory.makeProduct(name="fooix")
345- productseries = self.factory.makeProductSeries(
346- product=product, name="fooix-dev")
347+ productseries = self.factory.makeProductSeries(product=product)
348 self.factory.makeSpecification(
349 product=product, name="some-spec", goal=productseries)
350- product_on_webservice = self.getPillarOnWebservice(product)
351- productseries_on_webservice = product_on_webservice.getSeries(
352- name="fooix-dev")
353- spec = productseries_on_webservice.getSpecification(name="some-spec")
354- self.assertEqual("some-spec", spec.name)
355- self.assertEqual("fooix", spec.target.name)
356+ productseries_url = api_url(productseries)
357+ webservice = webservice_for_person(
358+ self.factory.makePerson(), default_api_version="devel")
359+ response = webservice.named_get(
360+ productseries_url, "getSpecification", name="some-spec")
361+ self.assertEqual(200, response.status)
362+ self.assertEqual("some-spec", response.jsonBody()["name"])
363+ response = webservice.get(response.jsonBody()["target_link"])
364+ self.assertEqual(200, response.status)
365+ self.assertEqual("fooix", response.jsonBody()["name"])
366
367 def test_get_specification_on_distroseries(self):
368 distribution = self.factory.makeDistribution(name="foobuntu")
369 distroseries = self.factory.makeDistroSeries(
370- distribution=distribution, name="maudlin")
371+ distribution=distribution)
372 self.factory.makeSpecification(
373 distribution=distribution, name="some-spec",
374 goal=distroseries)
375- distro_on_webservice = self.getPillarOnWebservice(distribution)
376- distroseries_on_webservice = distro_on_webservice.getSeries(
377- name_or_version="maudlin")
378- spec = distroseries_on_webservice.getSpecification(name="some-spec")
379- self.assertEqual("some-spec", spec.name)
380- self.assertEqual("foobuntu", spec.target.name)
381+ distroseries_url = api_url(distroseries)
382+ webservice = webservice_for_person(
383+ self.factory.makePerson(), default_api_version="devel")
384+ response = webservice.named_get(
385+ distroseries_url, "getSpecification", name="some-spec")
386+ self.assertEqual(200, response.status)
387+ self.assertEqual("some-spec", response.jsonBody()["name"])
388+ response = webservice.get(response.jsonBody()["target_link"])
389+ self.assertEqual(200, response.status)
390+ self.assertEqual("foobuntu", response.jsonBody()["name"])
391
392 def test_get_specification_not_found(self):
393 product = self.factory.makeProduct()
394- product_on_webservice = self.getPillarOnWebservice(product)
395- spec = product_on_webservice.getSpecification(name="nonexistant")
396- self.assertEqual(None, spec)
397+ product_url = api_url(product)
398+ webservice = webservice_for_person(
399+ self.factory.makePerson(), default_api_version="devel")
400+ response = webservice.named_get(
401+ product_url, "getSpecification", name="nonexistent")
402+ self.assertEqual(200, response.status)
403+ self.assertIsNone(response.jsonBody())
404
405
406-class IHasSpecificationsTests(SpecificationWebserviceTestCase):
407+class IHasSpecificationsTests(TestCaseWithFactory):
408 """Tests for accessing IHasSpecifications methods over the webservice."""
409 layer = DatabaseFunctionalLayer
410
411- def assertNamesOfSpecificationsAre(self, expected_names, specifications):
412- names = [s.name for s in specifications]
413- self.assertContentEqual(expected_names, names)
414-
415 def test_anonymous_access_to_collection(self):
416 product = self.factory.makeProduct()
417 self.factory.makeSpecification(product=product, name="spec1")
418 self.factory.makeSpecification(product=product, name="spec2")
419- # Need to endInteraction() because launchpadlib_for_anonymous() will
420- # setup a new one.
421- endInteraction()
422- lplib = launchpadlib_for('lplib-test', person=None, version='devel')
423- ws_product = ws_object(lplib, removeSecurityProxy(product))
424- self.assertNamesOfSpecificationsAre(
425- ["spec1", "spec2"], ws_product.all_specifications)
426+ product_url = api_url(product)
427+ logout()
428+ webservice = LaunchpadWebServiceCaller(
429+ "test", "", default_api_version="devel")
430+ response = webservice.get(product_url)
431+ self.assertEqual(200, response.status)
432+ response = webservice.get(
433+ response.jsonBody()["all_specifications_collection_link"])
434+ self.assertEqual(200, response.status)
435+ self.assertContentEqual(
436+ ["spec1", "spec2"],
437+ [entry["name"] for entry in response.jsonBody()["entries"]])
438
439 def test_product_all_specifications(self):
440 product = self.factory.makeProduct()
441 self.factory.makeSpecification(product=product, name="spec1")
442 self.factory.makeSpecification(product=product, name="spec2")
443- product_on_webservice = self.getPillarOnWebservice(product)
444- self.assertNamesOfSpecificationsAre(
445- ["spec1", "spec2"], product_on_webservice.all_specifications)
446+ product_url = api_url(product)
447+ webservice = webservice_for_person(
448+ self.factory.makePerson(), default_api_version="devel")
449+ response = webservice.get(product_url)
450+ self.assertEqual(200, response.status)
451+ response = webservice.get(
452+ response.jsonBody()["all_specifications_collection_link"])
453+ self.assertEqual(200, response.status)
454+ self.assertContentEqual(
455+ ["spec1", "spec2"],
456+ [entry["name"] for entry in response.jsonBody()["entries"]])
457
458 def test_distribution_valid_specifications(self):
459 distribution = self.factory.makeDistribution()
460@@ -380,120 +458,146 @@ class IHasSpecificationsTests(SpecificationWebserviceTestCase):
461 self.factory.makeSpecification(
462 distribution=distribution, name="spec2",
463 status=SpecificationDefinitionStatus.OBSOLETE)
464- distro_on_webservice = self.getPillarOnWebservice(distribution)
465- self.assertNamesOfSpecificationsAre(
466- ["spec1"], distro_on_webservice.valid_specifications)
467+ distribution_url = api_url(distribution)
468+ webservice = webservice_for_person(
469+ self.factory.makePerson(), default_api_version="devel")
470+ response = webservice.get(distribution_url)
471+ self.assertEqual(200, response.status)
472+ response = webservice.get(
473+ response.jsonBody()["valid_specifications_collection_link"])
474+ self.assertEqual(200, response.status)
475+ self.assertContentEqual(
476+ ["spec1"],
477+ [entry["name"] for entry in response.jsonBody()["entries"]])
478
479
480-class TestSpecificationSubscription(SpecificationWebserviceTestCase):
481+class TestSpecificationSubscription(TestCaseWithFactory):
482
483- layer = AppServerLayer
484+ layer = DatabaseFunctionalLayer
485
486 def test_subscribe(self):
487 # Test subscribe() API.
488- with person_logged_in(ANONYMOUS):
489- db_spec = self.factory.makeSpecification()
490- db_person = self.factory.makePerson()
491- launchpad = self.factory.makeLaunchpadService()
492-
493- spec = ws_object(launchpad, db_spec)
494- person = ws_object(launchpad, db_person)
495- spec.subscribe(person=person, essential=True)
496- transaction.commit()
497+ spec = self.factory.makeSpecification()
498+ person = self.factory.makePerson()
499+ spec_url = api_url(spec)
500+ person_url = api_url(person)
501+ webservice = webservice_for_person(
502+ person, permission=OAuthPermission.WRITE_PUBLIC,
503+ default_api_version="devel")
504+ response = webservice.named_post(
505+ spec_url, "subscribe", person=person_url, essential=True)
506+ self.assertEqual(200, response.status)
507
508 # Check the results.
509- sub = db_spec.subscription(db_person)
510+ login(ANONYMOUS)
511+ sub = spec.subscription(person)
512 self.assertIsNot(None, sub)
513 self.assertTrue(sub.essential)
514
515 def test_unsubscribe(self):
516 # Test unsubscribe() API.
517- with person_logged_in(ANONYMOUS):
518- db_spec = self.factory.makeBlueprint()
519- db_person = self.factory.makePerson()
520- db_spec.subscribe(person=db_person)
521- launchpad = self.factory.makeLaunchpadService(person=db_person)
522-
523- spec = ws_object(launchpad, db_spec)
524- person = ws_object(launchpad, db_person)
525- spec.unsubscribe(person=person)
526- transaction.commit()
527+ spec = self.factory.makeBlueprint()
528+ person = self.factory.makePerson()
529+ spec.subscribe(person=person)
530+ spec_url = api_url(spec)
531+ person_url = api_url(person)
532+ webservice = webservice_for_person(
533+ person, permission=OAuthPermission.WRITE_PUBLIC,
534+ default_api_version="devel")
535+ response = webservice.named_post(
536+ spec_url, "unsubscribe", person=person_url)
537+ self.assertEqual(200, response.status)
538
539 # Check the results.
540- self.assertFalse(db_spec.isSubscribed(db_person))
541+ login(ANONYMOUS)
542+ self.assertFalse(spec.isSubscribed(person))
543
544 def test_canBeUnsubscribedByUser(self):
545 # Test canBeUnsubscribedByUser() API.
546- webservice = LaunchpadWebServiceCaller(
547- 'launchpad-library', 'salgado-change-anything',
548- domain='api.launchpad.test:8085')
549-
550- with person_logged_in(ANONYMOUS):
551- db_spec = self.factory.makeSpecification()
552- db_person = self.factory.makePerson()
553- launchpad = self.factory.makeLaunchpadService()
554-
555- spec = ws_object(launchpad, db_spec)
556- person = ws_object(launchpad, db_person)
557- subscription = spec.subscribe(person=person, essential=True)
558- transaction.commit()
559-
560- result = webservice.named_get(
561- subscription['self_link'], 'canBeUnsubscribedByUser').jsonBody()
562- self.assertTrue(result)
563+ spec = self.factory.makeSpecification()
564+ person = self.factory.makePerson()
565+ with person_logged_in(person):
566+ subscription = spec.subscribe(
567+ person=person, subscribed_by=person, essential=True)
568+ subscription_url = api_url(subscription)
569+ admin_webservice = webservice_for_person(
570+ getUtility(ILaunchpadCelebrities).admin.teamowner,
571+ default_api_version="devel")
572+ response = admin_webservice.named_get(
573+ subscription_url, "canBeUnsubscribedByUser")
574+ self.assertEqual(200, response.status)
575+ self.assertIs(True, response.jsonBody())
576
577
578-class TestSpecificationBugLinks(SpecificationWebserviceTestCase):
579+class TestSpecificationBugLinks(TestCaseWithFactory):
580
581- layer = AppServerLayer
582+ layer = DatabaseFunctionalLayer
583
584 def test_bug_linking(self):
585 # Set up a spec, person, and bug.
586- with person_logged_in(ANONYMOUS):
587- db_spec = self.factory.makeSpecification()
588- db_person = self.factory.makePerson()
589- db_bug = self.factory.makeBug()
590- launchpad = self.factory.makeLaunchpadService()
591+ spec = self.factory.makeSpecification()
592+ person = self.factory.makePerson()
593+ bug = self.factory.makeBug()
594+ spec_url = api_url(spec)
595+ bug_url = api_url(bug)
596+ webservice = webservice_for_person(
597+ person, permission=OAuthPermission.WRITE_PUBLIC,
598+ default_api_version="devel")
599+
600+ # There are no bugs associated with the spec/blueprint yet.
601+ response = webservice.get(spec_url)
602+ self.assertEqual(200, response.status)
603+ spec_bugs_url = response.jsonBody()["bugs_collection_link"]
604+ response = webservice.get(spec_bugs_url)
605+ self.assertEqual(200, response.status)
606+ self.assertEqual(0, response.jsonBody()["total_size"])
607
608 # Link the bug to the spec via the web service.
609- with person_logged_in(db_person):
610- spec = ws_object(launchpad, db_spec)
611- bug = ws_object(launchpad, db_bug)
612- # There are no bugs associated with the spec/blueprint yet.
613- self.assertEqual(0, spec.bugs.total_size)
614- spec.linkBug(bug=bug)
615- transaction.commit()
616+ response = webservice.named_post(spec_url, "linkBug", bug=bug_url)
617+ self.assertEqual(200, response.status)
618
619 # The spec now has one bug associated with it and that bug is the one
620 # we linked.
621- self.assertEqual(1, spec.bugs.total_size)
622- self.assertEqual(bug.id, spec.bugs[0].id)
623+ response = webservice.get(spec_bugs_url)
624+ self.assertEqual(200, response.status)
625+ self.assertThat(response.jsonBody(), ContainsDict({
626+ "total_size": Equals(1),
627+ "entries": MatchesListwise([ContainsDict({"id": Equals(bug.id)})]),
628+ }))
629
630 def test_bug_unlinking(self):
631 # Set up a spec, person, and bug, then link the bug to the spec.
632- with person_logged_in(ANONYMOUS):
633- db_spec = self.factory.makeBlueprint()
634- db_person = self.factory.makePerson()
635- db_bug = self.factory.makeBug()
636- launchpad = self.factory.makeLaunchpadService(person=db_person)
637-
638- spec = ws_object(launchpad, db_spec)
639- bug = ws_object(launchpad, db_bug)
640- spec.linkBug(bug=bug)
641+ spec = self.factory.makeBlueprint()
642+ person = self.factory.makePerson()
643+ bug = self.factory.makeBug()
644+ spec_url = api_url(spec)
645+ bug_url = api_url(bug)
646+ with person_logged_in(spec.owner):
647+ spec.linkBug(bug)
648+ webservice = webservice_for_person(
649+ person, permission=OAuthPermission.WRITE_PUBLIC,
650+ default_api_version="devel")
651
652 # There is only one bug linked at the moment.
653- self.assertEqual(1, spec.bugs.total_size)
654+ response = webservice.get(spec_url)
655+ self.assertEqual(200, response.status)
656+ spec_bugs_url = response.jsonBody()["bugs_collection_link"]
657+ response = webservice.get(spec_bugs_url)
658+ self.assertEqual(200, response.status)
659+ self.assertEqual(1, response.jsonBody()["total_size"])
660
661- spec.unlinkBug(bug=bug)
662- transaction.commit()
663+ response = webservice.named_post(spec_url, "unlinkBug", bug=bug_url)
664+ self.assertEqual(200, response.status)
665
666 # Now that we've unlinked the bug, there are no linked bugs at all.
667- self.assertEqual(0, spec.bugs.total_size)
668+ response = webservice.get(spec_bugs_url)
669+ self.assertEqual(200, response.status)
670+ self.assertEqual(0, response.jsonBody()["total_size"])
671
672
673-class TestSpecificationGoalHandling(SpecificationWebserviceTestCase):
674+class TestSpecificationGoalHandling(TestCaseWithFactory):
675
676- layer = AppServerLayer
677+ layer = DatabaseFunctionalLayer
678
679 def setUp(self):
680 super(TestSpecificationGoalHandling, self).setUp()
681@@ -501,53 +605,64 @@ class TestSpecificationGoalHandling(SpecificationWebserviceTestCase):
682 self.proposer = self.factory.makePerson()
683 self.product = self.factory.makeProduct(driver=self.driver)
684 self.series = self.factory.makeProductSeries(product=self.product)
685+ self.series_url = api_url(self.series)
686
687 def test_goal_propose_and_accept(self):
688 # Webservice clients can propose and accept spec series goals.
689- db_spec = self.factory.makeBlueprint(product=self.product,
690- owner=self.proposer)
691+ spec = self.factory.makeBlueprint(
692+ product=self.product, owner=self.proposer)
693+ spec_url = api_url(spec)
694+
695 # Propose for series goal
696+ proposer_webservice = webservice_for_person(
697+ self.proposer, permission=OAuthPermission.WRITE_PUBLIC,
698+ default_api_version="devel")
699+ response = proposer_webservice.named_post(
700+ spec_url, "proposeGoal", goal=self.series_url)
701+ self.assertEqual(200, response.status)
702 with person_logged_in(self.proposer):
703- launchpad = self.factory.makeLaunchpadService(person=self.proposer)
704- spec = ws_object(launchpad, db_spec)
705- series = ws_object(launchpad, self.series)
706- spec.proposeGoal(goal=series)
707- transaction.commit()
708- self.assertEqual(db_spec.goal, self.series)
709+ self.assertEqual(spec.goal, self.series)
710 self.assertFalse(spec.has_accepted_goal)
711
712 # Accept series goal
713+ driver_webservice = webservice_for_person(
714+ self.driver, permission=OAuthPermission.WRITE_PUBLIC,
715+ default_api_version="devel")
716+ response = driver_webservice.named_post(spec_url, "acceptGoal")
717+ self.assertEqual(200, response.status)
718 with person_logged_in(self.driver):
719- launchpad = self.factory.makeLaunchpadService(person=self.driver)
720- spec = ws_object(launchpad, db_spec)
721- spec.acceptGoal()
722- transaction.commit()
723 self.assertTrue(spec.has_accepted_goal)
724
725 def test_goal_propose_decline_and_clear(self):
726 # Webservice clients can decline and clear spec series goals.
727- db_spec = self.factory.makeBlueprint(product=self.product,
728- owner=self.proposer)
729+ spec = self.factory.makeBlueprint(
730+ product=self.product, owner=self.proposer)
731+ spec_url = api_url(spec)
732+
733 # Propose for series goal
734+ proposer_webservice = webservice_for_person(
735+ self.proposer, permission=OAuthPermission.WRITE_PUBLIC,
736+ default_api_version="devel")
737+ response = proposer_webservice.named_post(
738+ spec_url, "proposeGoal", goal=self.series_url)
739+ self.assertEqual(200, response.status)
740 with person_logged_in(self.proposer):
741- launchpad = self.factory.makeLaunchpadService(person=self.proposer)
742- spec = ws_object(launchpad, db_spec)
743- series = ws_object(launchpad, self.series)
744- spec.proposeGoal(goal=series)
745- transaction.commit()
746- self.assertEqual(db_spec.goal, self.series)
747+ self.assertEqual(spec.goal, self.series)
748 self.assertFalse(spec.has_accepted_goal)
749
750+ # Decline series goal
751+ driver_webservice = webservice_for_person(
752+ self.driver, permission=OAuthPermission.WRITE_PUBLIC,
753+ default_api_version="devel")
754+ response = driver_webservice.named_post(spec_url, "declineGoal")
755+ self.assertEqual(200, response.status)
756 with person_logged_in(self.driver):
757- # Decline series goal
758- launchpad = self.factory.makeLaunchpadService(person=self.driver)
759- spec = ws_object(launchpad, db_spec)
760- spec.declineGoal()
761- transaction.commit()
762 self.assertFalse(spec.has_accepted_goal)
763- self.assertEqual(db_spec.goal, self.series)
764+ self.assertEqual(spec.goal, self.series)
765
766- # Clear series goal as a driver
767- spec.proposeGoal(goal=None)
768- transaction.commit()
769- self.assertIsNone(db_spec.goal)
770+ # Clear series goal as a driver
771+ response = driver_webservice.named_post(
772+ spec_url, "proposeGoal", goal=None)
773+ self.assertEqual(200, response.status)
774+ with person_logged_in(self.driver):
775+ self.assertIsNone(spec.goal)
776diff --git a/lib/lp/testing/pages.py b/lib/lp/testing/pages.py
777index cff8368..b24f141 100644
778--- a/lib/lp/testing/pages.py
779+++ b/lib/lp/testing/pages.py
780@@ -158,7 +158,8 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
781
782 def __init__(self, oauth_consumer_key=None, oauth_access_key=None,
783 oauth_access_secret=None, handle_errors=True,
784- domain='api.launchpad.test', protocol='http'):
785+ domain='api.launchpad.test', protocol='http',
786+ default_api_version=None):
787 """Create a LaunchpadWebServiceCaller.
788 :param oauth_consumer_key: The OAuth consumer key to use.
789 :param oauth_access_key: The OAuth access key to use for the request.
790@@ -184,6 +185,8 @@ class LaunchpadWebServiceCaller(WebServiceCaller):
791 self.consumer = None
792 self.access_token = None
793 self.handle_errors = handle_errors
794+ if default_api_version is not None:
795+ self.default_api_version = default_api_version
796 WebServiceCaller.__init__(self, handle_errors, domain, protocol)
797
798 default_api_version = "beta"
799@@ -745,7 +748,7 @@ def safe_canonical_url(*args, **kwargs):
800
801 def webservice_for_person(person, consumer_key=u'launchpad-library',
802 permission=OAuthPermission.READ_PUBLIC,
803- context=None):
804+ context=None, default_api_version=None):
805 """Return a valid LaunchpadWebServiceCaller for the person.
806
807 Use this method to create a way to test the webservice that doesn't depend
808@@ -763,7 +766,8 @@ def webservice_for_person(person, consumer_key=u'launchpad-library',
809 access_token, access_secret = request_token.createAccessToken()
810 logout()
811 service = LaunchpadWebServiceCaller(
812- consumer_key, access_token.key, access_secret)
813+ consumer_key, access_token.key, access_secret,
814+ default_api_version=default_api_version)
815 service.user = person
816 return service
817

Subscribers

People subscribed via source and target branches

to status/vote changes: