← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:answers-bugs-webservice-tests into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:answers-bugs-webservice-tests into launchpad:master with ~cjwatson/launchpad:webservice-for-person-anonymous as a prerequisite.

Commit message:
Stop using launchpadlib in most answers and bugs tests

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/400969

Port most of the answers and bugs webservice tests to use in-process webservice calls rather than launchpadlib and AppServerLayer.  This takes the test time for these test suites from 149 seconds to 114 seconds on my laptop.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:answers-bugs-webservice-tests into launchpad:master.
diff --git a/lib/lp/answers/tests/test_question_webservice.py b/lib/lp/answers/tests/test_question_webservice.py
index 1dd2e42..4316206 100644
--- a/lib/lp/answers/tests/test_question_webservice.py
+++ b/lib/lp/answers/tests/test_question_webservice.py
@@ -12,12 +12,9 @@ from datetime import (
     timedelta,
     )
 
-from lazr.restfulclient.errors import HTTPError
 import pytz
 from simplejson import dumps
 from testtools.matchers import EndsWith
-import transaction
-from zope.security.proxy import removeSecurityProxy
 
 from lp.answers.enums import QuestionStatus
 from lp.answers.errors import (
@@ -33,18 +30,15 @@ from lp.services.beautifulsoup import BeautifulSoup
 from lp.services.webapp.interfaces import OAuthPermission
 from lp.testing import (
     admin_logged_in,
+    api_url,
     celebrity_logged_in,
-    launchpadlib_for,
-    logout,
     person_logged_in,
     record_two_runs,
     TestCase,
     TestCaseWithFactory,
     time_counter,
-    ws_object,
     )
 from lp.testing.layers import (
-    AppServerLayer,
     DatabaseFunctionalLayer,
     FunctionalLayer,
     )
@@ -192,47 +186,41 @@ class TestSetCommentVisibility(TestCaseWithFactory):
         self.commenter = self.factory.makePerson()
         with person_logged_in(self.commenter):
             self.question = self.factory.makeQuestion()
+            self.question_url = api_url(self.question)
             self.message = self.question.addComment(
                 self.commenter, 'Some comment')
-        transaction.commit()
-
-    def _get_question_for_user(self, user=None):
-        """Convenience function to get the api question reference."""
-        # End any open lplib instance.
-        logout()
-        lp = launchpadlib_for("test", user)
-        return ws_object(lp, removeSecurityProxy(self.question))
-
-    def _set_visibility(self, question):
-        """Method to set visibility; needed for assertRaises."""
-        question.setCommentVisibility(
-            comment_number=0,
-            visible=False)
 
     def test_random_user_cannot_set_visible(self):
         # Logged in users without privs can't set question comment
         # visibility.
         random_user = self.factory.makePerson()
-        question = self._get_question_for_user(random_user)
-        self.assertRaises(
-            HTTPError,
-            self._set_visibility,
-            question)
+        webservice = webservice_for_person(
+            random_user, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
+        response = webservice.named_post(
+            self.question_url, 'setCommentVisibility',
+            comment_number=0, visible=False)
+        self.assertEqual(401, response.status)
 
     def test_anon_cannot_set_visible(self):
         # Anonymous users can't set question comment
         # visibility.
-        question = self._get_question_for_user()
-        self.assertRaises(
-            HTTPError,
-            self._set_visibility,
-            question)
+        webservice = webservice_for_person(None, default_api_version='devel')
+        response = webservice.named_post(
+            self.question_url, 'setCommentVisibility',
+            comment_number=0, visible=False)
+        self.assertEqual(401, response.status)
 
     def test_comment_owner_can_set_visible(self):
         # Members of registry experts can set question comment
         # visibility.
-        question = self._get_question_for_user(self.commenter)
-        self._set_visibility(question)
+        webservice = webservice_for_person(
+            self.commenter, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
+        response = webservice.named_post(
+            self.question_url, 'setCommentVisibility',
+            comment_number=0, visible=False)
+        self.assertEqual(200, response.status)
         self.assertFalse(self.message.visible)
 
     def test_registry_admin_can_set_visible(self):
@@ -240,8 +228,13 @@ class TestSetCommentVisibility(TestCaseWithFactory):
         # visibility.
         with celebrity_logged_in('registry_experts') as registry:
             person = registry
-        question = self._get_question_for_user(person)
-        self._set_visibility(question)
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
+        response = webservice.named_post(
+            self.question_url, 'setCommentVisibility',
+            comment_number=0, visible=False)
+        self.assertEqual(200, response.status)
         self.assertFalse(self.message.visible)
 
     def test_admin_can_set_visible(self):
@@ -249,47 +242,58 @@ class TestSetCommentVisibility(TestCaseWithFactory):
         # visibility.
         with celebrity_logged_in('admin') as admin:
             person = admin
-        question = self._get_question_for_user(person)
-        self._set_visibility(question)
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
+        response = webservice.named_post(
+            self.question_url, 'setCommentVisibility',
+            comment_number=0, visible=False)
+        self.assertEqual(200, response.status)
         self.assertFalse(self.message.visible)
 
 
 class TestQuestionWebServiceSubscription(TestCaseWithFactory):
 
-    layer = AppServerLayer
+    layer = DatabaseFunctionalLayer
 
     def test_subscribe(self):
         # Test subscribe() API.
         person = self.factory.makePerson()
         with person_logged_in(person):
-            db_question = self.factory.makeQuestion()
-            db_person = self.factory.makePerson()
-            launchpad = self.factory.makeLaunchpadService()
+            question = self.factory.makeQuestion()
+            question_url = api_url(question)
+            person = self.factory.makePerson()
+            person_url = api_url(person)
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
 
-        question = ws_object(launchpad, db_question)
-        person = ws_object(launchpad, db_person)
-        question.subscribe(person=person)
-        transaction.commit()
+        response = webservice.named_post(
+            question_url, 'subscribe', person=person_url)
+        self.assertEqual(200, response.status)
 
         # Check the results.
-        self.assertTrue(db_question.isSubscribed(db_person))
+        self.assertTrue(question.isSubscribed(person))
 
     def test_unsubscribe(self):
         # Test unsubscribe() API.
         person = self.factory.makePerson()
         with person_logged_in(person):
-            db_question = self.factory.makeQuestion()
-            db_person = self.factory.makePerson()
-            db_question.subscribe(person=db_person)
-            launchpad = self.factory.makeLaunchpadService(person=db_person)
+            question = self.factory.makeQuestion()
+            question_url = api_url(question)
+            person = self.factory.makePerson()
+            person_url = api_url(person)
+            question.subscribe(person=person)
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
 
-        question = ws_object(launchpad, db_question)
-        person = ws_object(launchpad, db_person)
-        question.unsubscribe(person=person)
-        transaction.commit()
+        response = webservice.named_post(
+            question_url, 'unsubscribe', person=person_url)
+        self.assertEqual(200, response.status)
 
         # Check the results.
-        self.assertFalse(db_question.isSubscribed(db_person))
+        self.assertFalse(question.isSubscribed(person))
 
 
 class TestQuestionSetWebService(TestCaseWithFactory):
diff --git a/lib/lp/bugs/tests/test_bug_messages_webservice.py b/lib/lp/bugs/tests/test_bug_messages_webservice.py
index dbd3507..c9d1f47 100644
--- a/lib/lp/bugs/tests/test_bug_messages_webservice.py
+++ b/lib/lp/bugs/tests/test_bug_messages_webservice.py
@@ -5,21 +5,18 @@
 
 __metaclass__ = type
 
-from lazr.restfulclient.errors import HTTPError
 from testtools.matchers import HasLength
-import transaction
 from zope.component import getUtility
-from zope.security.management import endInteraction
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.enums import InformationType
 from lp.bugs.interfaces.bugmessage import IBugMessageSet
 from lp.registry.interfaces.accesspolicy import IAccessPolicySource
 from lp.registry.interfaces.person import IPersonSet
+from lp.services.webapp.interfaces import OAuthPermission
 from lp.testing import (
     admin_logged_in,
     api_url,
-    launchpadlib_for,
     login_celebrity,
     person_logged_in,
     TestCaseWithFactory,
@@ -133,28 +130,11 @@ class TestSetCommentVisibility(TestCaseWithFactory):
         self.admin = admins.teamowner
         with person_logged_in(self.admin):
             self.bug = self.factory.makeBug()
+            self.bug_url = api_url(self.bug)
             self.message = self.factory.makeBugComment(
                 bug=self.bug,
                 subject='foo',
                 body='bar')
-        transaction.commit()
-
-    def _get_bug_for_user(self, user=None):
-        """Convenience function to get the api bug reference."""
-        endInteraction()
-        if user is not None:
-            lp = launchpadlib_for("test", user)
-        else:
-            lp = launchpadlib_for("test")
-
-        bug_entry = lp.load('/bugs/%s/' % self.bug.id)
-        return bug_entry
-
-    def _set_visibility(self, bug):
-        """Method to set visibility; needed for assertRaises."""
-        bug.setCommentVisibility(
-            comment_number=1,
-            visible=False)
 
     def _check_comment_hidden(self):
         bug_msg_set = getUtility(IBugMessageSet)
@@ -164,14 +144,15 @@ class TestSetCommentVisibility(TestCaseWithFactory):
             self.assertFalse(bug_message.message.visible)
 
     def _test_hide_comment(self, person, should_fail=False):
-        bug = self._get_bug_for_user(person)
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PUBLIC)
+        response = webservice.named_post(
+            self.bug_url, 'setCommentVisibility',
+            comment_number=1, visible=False)
         if should_fail:
-            self.assertRaises(
-                HTTPError,
-                self._set_visibility,
-                bug)
+            self.assertEqual(401, response.status)
         else:
-            self._set_visibility(bug)
+            self.assertEqual(200, response.status)
             self._check_comment_hidden()
 
     def test_random_user_cannot_set_visible(self):
diff --git a/lib/lp/bugs/tests/test_bugchanges.py b/lib/lp/bugs/tests/test_bugchanges.py
index 7312716..2c43f97 100644
--- a/lib/lp/bugs/tests/test_bugchanges.py
+++ b/lib/lp/bugs/tests/test_bugchanges.py
@@ -24,16 +24,17 @@ from lp.bugs.model.bugnotification import BugNotification
 from lp.bugs.scripts.bugnotification import construct_email_notifications
 from lp.services.database.interfaces import IStore
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
+from lp.services.webapp.interfaces import OAuthPermission
 from lp.services.webapp.publisher import canonical_url
 from lp.services.webapp.snapshot import notify_modified
 from lp.testing import (
     api_url,
-    launchpadlib_for,
     login_person,
     person_logged_in,
     TestCaseWithFactory,
     )
 from lp.testing.layers import LaunchpadFunctionalLayer
+from lp.testing.pages import webservice_for_person
 
 
 class TestBugChanges(TestCaseWithFactory):
@@ -695,10 +696,15 @@ class TestBugChanges(TestCaseWithFactory):
         # log and notifications.
         person = self.factory.makePerson()
         bug = self.factory.makeBug(owner=person)
+        bug_url = api_url(bug)
         self.saveOldChanges(bug=bug)
-        webservice = launchpadlib_for('test', person)
-        lp_bug = webservice.load(api_url(bug))
-        lp_bug.transitionToInformationType(information_type='Private Security')
+        webservice = webservice_for_person(
+            person, permission=OAuthPermission.WRITE_PRIVATE,
+            default_api_version='devel')
+        response = webservice.named_post(
+            bug_url, 'transitionToInformationType',
+            information_type='Private Security')
+        self.assertEqual(200, response.status)
 
         information_type_change_activity = {
             'person': person,
diff --git a/lib/lp/bugs/tests/test_bugs_webservice.py b/lib/lp/bugs/tests/test_bugs_webservice.py
index 099c5ca..0964f1e 100644
--- a/lib/lp/bugs/tests/test_bugs_webservice.py
+++ b/lib/lp/bugs/tests/test_bugs_webservice.py
@@ -9,13 +9,10 @@ from datetime import (
     datetime,
     timedelta,
     )
+import json
 import re
 
 from lazr.lifecycle.interfaces import IDoNotSnapshot
-from lazr.restfulclient.errors import (
-    BadRequest,
-    HTTPError,
-    )
 import pytz
 from simplejson import dumps
 import six
@@ -37,10 +34,9 @@ from lp.services.webapp import snapshot
 from lp.services.webapp.interfaces import OAuthPermission
 from lp.services.webapp.servers import LaunchpadTestRequest
 from lp.testing import (
+    ANONYMOUS,
     api_url,
-    launchpadlib_for,
     login,
-    login_person,
     logout,
     person_logged_in,
     RequestTimelineCollector,
@@ -62,29 +58,30 @@ from lp.testing.sampledata import (
 
 
 class TestBugConstraints(TestCaseWithFactory):
-    """Test constrainsts on bug inputs over the API."""
+    """Test constraints on bug inputs over the API."""
 
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
         super(TestBugConstraints, self).setUp()
         product = self.factory.makeProduct(name='foo')
-        bug = self.factory.makeBug(target=product)
-        lp = launchpadlib_for('testing', product.owner)
-        self.bug = lp.bugs[bug.id]
+        self.bug = self.factory.makeBug(target=product)
+        self.bug_url = api_url(self.bug)
+        self.webservice = webservice_for_person(
+            product.owner, permission=OAuthPermission.WRITE_PUBLIC)
 
     def _update_bug(self, nick):
-        self.bug.name = nick
-        self.bug.lp_save()
+        return self.webservice.patch(
+            self.bug_url, 'application/json', json.dumps({'name': nick}))
 
     def test_numeric_nicknames_fail(self):
-        self.assertRaises(
-            HTTPError,
-            self._update_bug,
-            '1.1')
+        response = self._update_bug('1.1')
+        self.assertEqual(400, response.status)
 
     def test_non_numeric_nicknames_pass(self):
-        self._update_bug('bunny')
+        response = self._update_bug('bunny')
+        self.assertEqual(209, response.status)
+        login(ANONYMOUS)
         self.assertEqual('bunny', self.bug.name)
 
 
@@ -277,25 +274,27 @@ class TestBugMessages(TestCaseWithFactory):
     def setUp(self):
         super(TestBugMessages, self).setUp(USER_EMAIL)
         self.bug = self.factory.makeBug()
+        self.bug_url = api_url(self.bug)
         self.message1 = self.factory.makeMessage()
         self.message2 = self.factory.makeMessage(parent=self.message1)
         # Only link message2 to the bug.
         self.bug.linkMessage(self.message2)
-        self.webservice = launchpadlib_for('launchpad-library', 'salgado')
+        self.webservice = webservice_for_person(self.bug.owner)
 
     def test_messages(self):
         # When one of the messages on a bug is linked to a parent that
         # isn't linked to the bug, the webservice should still include
         # that message in the bug's associated messages.
-        bug = self.webservice.load(api_url(self.bug))
-        messages = bug.messages
-        latest_message = [message for message in messages][-1]
-        self.assertEqual(self.message2.subject, latest_message.subject)
+        ws_bug = self.getWebserviceJSON(self.webservice, self.bug_url)
+        ws_messages = self.getWebserviceJSON(
+            self.webservice, ws_bug['messages_collection_link'])
+        latest_message = ws_messages['entries'][-1]
+        self.assertEqual(self.message2.subject, latest_message['subject'])
 
         # The parent_link for the latest message should be None
         # because the parent is not a member of this bug's messages
         # collection itself.
-        self.assertIsNone(latest_message.parent)
+        self.assertIsNone(latest_message['parent_link'])
 
 
 class TestPostBugWithLargeCollections(TestCaseWithFactory):
@@ -329,23 +328,25 @@ class TestPostBugWithLargeCollections(TestCaseWithFactory):
     def test_many_subscribers(self):
         # Many subscriptions do not cause an OOPS for IBug POSTs.
         bug = self.factory.makeBug()
+        bug_url = api_url(bug)
 
         real_hard_limit_for_snapshot = snapshot.HARD_LIMIT_FOR_SNAPSHOT
         snapshot.HARD_LIMIT_FOR_SNAPSHOT = 3
 
-        webservice = launchpadlib_for('test', 'salgado')
+        webservice = webservice_for_person(
+            bug.owner, permission=OAuthPermission.WRITE_PUBLIC)
         try:
             login(ADMIN_EMAIL)
             for count in range(snapshot.HARD_LIMIT_FOR_SNAPSHOT + 1):
                 person = self.factory.makePerson()
                 bug.subscribe(person, person)
             logout()
-            lp_bug = webservice.load(api_url(bug))
 
             # Adding one more subscriber through the web service
             # doesn't cause an OOPS.
-            person_to_subscribe = webservice.load('/~name12')
-            lp_bug.subscribe(person=person_to_subscribe)
+            response = webservice.named_post(
+                bug_url, 'subscribe', person='/~name12')
+            self.assertEqual(200, response.status)
         finally:
             snapshot.HARD_LIMIT_FOR_SNAPSHOT = real_hard_limit_for_snapshot
 
@@ -356,14 +357,16 @@ class TestErrorHandling(TestCaseWithFactory):
 
     def test_add_duplicate_bugtask_for_project_gives_bad_request(self):
         bug = self.factory.makeBug()
+        bug_url = api_url(bug)
         product = self.factory.makeProduct()
         product_url = api_url(product)
         self.factory.makeBugTask(bug=bug, target=product)
 
-        launchpad = launchpadlib_for('test', bug.owner)
-        lp_bug = launchpad.load(api_url(bug))
-        self.assertRaises(
-            BadRequest, lp_bug.addTask, target=product_url)
+        webservice = webservice_for_person(
+            bug.owner, permission=OAuthPermission.WRITE_PUBLIC)
+        response = webservice.named_post(
+            bug_url, 'addTask', target=product_url)
+        self.assertEqual(400, response.status)
 
     def test_add_invalid_bugtask_to_proprietary_bug_gives_bad_request(self):
         # Test we get an error when we attempt to invalidly add a bug task to
@@ -378,24 +381,27 @@ class TestErrorHandling(TestCaseWithFactory):
         bug = self.factory.makeBug(
             target=product1, owner=owner,
             information_type=InformationType.PROPRIETARY)
+        bug_url = api_url(bug)
 
-        login_person(owner)
-        launchpad = launchpadlib_for('test', owner)
-        lp_bug = launchpad.load(api_url(bug))
-        self.assertRaises(
-            BadRequest, lp_bug.addTask, target=product2_url)
+        webservice = webservice_for_person(
+            owner, permission=OAuthPermission.WRITE_PRIVATE)
+        response = webservice.named_post(
+            bug_url, 'addTask', target=product2_url)
+        self.assertEqual(400, response.status)
 
     def test_add_attachment_with_bad_filename_raises_exception(self):
         # Test that addAttachment raises BadRequest when the filename given
         # contains slashes.
         owner = self.factory.makePerson()
         bug = self.factory.makeBug(owner=owner)
-        login_person(owner)
-        launchpad = launchpadlib_for('test', owner)
-        lp_bug = launchpad.load(api_url(bug))
-        self.assertRaises(
-            BadRequest, lp_bug.addAttachment, comment='foo', data=b'foo',
-            filename='/home/foo/bar.txt')
+        bug_url = api_url(bug)
+
+        webservice = webservice_for_person(
+            owner, permission=OAuthPermission.WRITE_PUBLIC)
+        response = webservice.named_post(
+            bug_url, 'addAttachment',
+            comment='foo', data=b'foo', filename='/home/foo/bar.txt')
+        self.assertEqual(400, response.status)
 
 
 class BugSetTestCase(TestCaseWithFactory):
@@ -408,31 +414,37 @@ class BugSetTestCase(TestCaseWithFactory):
         target_url = api_url(project)
         with person_logged_in(project.owner):
             project.setBugSharingPolicy(bug_policy)
-        return target_url
-
-    def getBugsCollection(self):
-        webservice = launchpadlib_for('test', 'salgado')
-        return webservice.load('/bugs')
+        return project, target_url
 
     def test_default_sharing_policy_proprietary(self):
         # Verify the path through user submission, to MaloneApplication to
         # BugSet, and back to the user creates a private bug according
         # to the project's bug sharing policy.
-        target_url = self.makeAPITarget(BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
-        bugs_collection = self.getBugsCollection()
-        bug = bugs_collection.createBug(
+        target, target_url = self.makeAPITarget(
+            BugSharingPolicy.PROPRIETARY_OR_PUBLIC)
+        webservice = webservice_for_person(
+            target.owner, permission=OAuthPermission.WRITE_PRIVATE)
+        response = webservice.named_post(
+            '/bugs', 'createBug',
             target=target_url, title='title', description='desc')
-        self.assertEqual('Proprietary', bug.information_type)
+        self.assertEqual(201, response.status)
+        ws_bug = webservice.get(response.getHeader('Location')).jsonBody()
+        self.assertEqual('Proprietary', ws_bug['information_type'])
 
     def test_override_default_sharing_policy_proprietary(self):
-        # A Proprietary bug can be created if the porject's bug sharing policy
+        # A Proprietary bug can be created if the project's bug sharing policy
         # permits it.
-        target_url = self.makeAPITarget(BugSharingPolicy.PUBLIC_OR_PROPRIETARY)
-        bugs_collection = self.getBugsCollection()
-        bug = bugs_collection.createBug(
+        target, target_url = self.makeAPITarget(
+            BugSharingPolicy.PUBLIC_OR_PROPRIETARY)
+        webservice = webservice_for_person(
+            target.owner, permission=OAuthPermission.WRITE_PRIVATE)
+        response = webservice.named_post(
+            '/bugs', 'createBug',
             target=target_url, title='title', description='desc',
             information_type='Proprietary')
-        self.assertEqual('Proprietary', bug.information_type)
+        self.assertEqual(201, response.status)
+        ws_bug = webservice.get(response.getHeader('Location')).jsonBody()
+        self.assertEqual('Proprietary', ws_bug['information_type'])
 
 
 class TestBugDateLastUpdated(TestCaseWithFactory):
diff --git a/lib/lp/bugs/tests/test_bugsubscription.py b/lib/lp/bugs/tests/test_bugsubscription.py
index ea2de7c..a554cfc 100644
--- a/lib/lp/bugs/tests/test_bugsubscription.py
+++ b/lib/lp/bugs/tests/test_bugsubscription.py
@@ -3,21 +3,27 @@
 
 """Tests for BugSubscriptions."""
 
+from __future__ import absolute_import, print_function, unicode_literals
+
 __metaclass__ = type
 
+import json
+
 from testtools.matchers import LessThan
 from zope.security.interfaces import Unauthorized
 
 from lp.bugs.enums import BugNotificationLevel
 from lp.registry.interfaces.teammembership import TeamMembershipStatus
+from lp.services.webapp.interfaces import OAuthPermission
 from lp.testing import (
-    launchpadlib_for,
+    api_url,
     person_logged_in,
     RequestTimelineCollector,
     TestCaseWithFactory,
     )
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.matchers import HasQueryCount
+from lp.testing.pages import webservice_for_person
 
 
 class TestBugSubscription(TestCaseWithFactory):
@@ -30,18 +36,26 @@ class TestBugSubscription(TestCaseWithFactory):
         self.bug = self.factory.makeBug()
         self.subscriber = self.factory.makePerson()
 
-    def updateBugNotificationLevelWithWebService(self, bug_id,
-                                                 subscriber_name,
+    def updateBugNotificationLevelWithWebService(self, bug, subscriber,
                                                  update_as):
         """A helper method to update a subscription's bug_notification_level.
         """
-        launchpad = launchpadlib_for("test", update_as, version='devel')
-        lplib_bug = launchpad.bugs[self.bug.id]
-        lplib_subscriber = launchpad.people[subscriber_name]
-        [lplib_subscription] = [
-            subscription for subscription in lplib_bug.subscriptions
-            if subscription.person == lplib_subscriber]
-        lplib_subscription.bug_notification_level = u'Nothing'
+        bug_url = api_url(bug)
+        subscriber_url = api_url(subscriber)
+        webservice = webservice_for_person(
+            update_as, permission=OAuthPermission.WRITE_PUBLIC,
+            default_api_version='devel')
+        ws_bug = self.getWebserviceJSON(webservice, bug_url)
+        ws_subscriptions = self.getWebserviceJSON(
+            webservice, ws_bug['subscriptions_collection_link'])
+        absolute_subscriber_url = webservice.getAbsoluteUrl(subscriber_url)
+        [ws_subscription] = [
+            subscription for subscription in ws_subscriptions['entries']
+            if subscription['person_link'] == absolute_subscriber_url]
+        response = webservice.patch(
+            ws_subscription['self_link'], 'application/json',
+            json.dumps({'bug_notification_level': 'Lifecycle'}))
+        self.assertEqual(209, response.status)
 
     def test_subscribers_can_change_bug_notification_level(self):
         # The bug_notification_level of a subscription can be changed by
@@ -125,7 +139,7 @@ class TestBugSubscription(TestCaseWithFactory):
         self.addCleanup(collector.unregister)
         with person_logged_in(self.subscriber):
             self.updateBugNotificationLevelWithWebService(
-                self.bug.id, team.name, self.subscriber)
+                self.bug, team, self.subscriber)
         # 25 is an entirely arbitrary limit for the number of queries
         # this requires, based on the number run when the code was
         # written; it should give us a nice early warning if the number
@@ -137,6 +151,6 @@ class TestBugSubscription(TestCaseWithFactory):
         # interaction goes away, so we have to set up a new one.
         with person_logged_in(self.subscriber):
             self.updateBugNotificationLevelWithWebService(
-                self.bug.id, team_2.name, self.subscriber)
+                self.bug, team_2, self.subscriber)
         self.assertThat(
             collector, HasQueryCount(LessThan(25)))