Merge lp:~danilo/launchpad/bug-772763-remove-unmute-dialog-part1 into lp:launchpad/db-devel

Proposed by Данило Шеган
Status: Merged
Approved by: Данило Шеган
Approved revision: no longer in the source branch.
Merged at revision: 10582
Proposed branch: lp:~danilo/launchpad/bug-772763-remove-unmute-dialog-part1
Merge into: lp:launchpad/db-devel
Diff against target: 518 lines (+204/-54)
9 files modified
database/schema/security.cfg (+4/-0)
lib/lp/bugs/browser/bug.py (+2/-4)
lib/lp/bugs/browser/bugsubscription.py (+16/-7)
lib/lp/bugs/browser/tests/test_bug_views.py (+24/-1)
lib/lp/bugs/browser/tests/test_bugsubscription_views.py (+46/-13)
lib/lp/bugs/interfaces/bug.py (+5/-3)
lib/lp/bugs/model/bug.py (+15/-5)
lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py (+76/-21)
lib/lp/bugs/tests/test_bug.py (+16/-0)
To merge this branch: bzr merge lp:~danilo/launchpad/bug-772763-remove-unmute-dialog-part1
Reviewer Review Type Date Requested Status
Abel Deuring (community) code Approve
Данило Шеган (community) db Abstain
Review via email: mp+61775@code.launchpad.net

Commit message

[r=adeuring][bug=772763][incr] Ground-work for making "Unmute bug mail" unmute directly (and restoring any previous subscription) without popping up a dialog.

Description of the change

= Bug 772763: remove unmute dialog, part1 =

As part of solving 772763, we remove the pop-up unmute dialog (to directly unmute instead). To prepare for that, we first do some server-side cleanups and make unmute() method return the previously masked subscription (if any).

== Proposed fix ==

This branch does a few things:

 - Make "static" (i.e. non-JS) page IBugTask:+mute not redirect to +subscribe to offer unmute but instead allow direct unmuting
 - Ensures Mute/Unmute link is shown when bug is muted
 - Takes mutes into consideration for all subscribers-calculation (direct, duplicate, also-notified) (changes in model/bug.py)
 - Make unmute() method return a previous subscription (if any)

== Pre-implementation notes ==

This is mostly Gary's branch. I am only shepherding it and providing a few tests of my own.

== Implementation details ==

Not cleaning up the lint for the model/bug.py since it'd taint the diff.

== Tests ==

bin/test -cvvt TestBugSubscriptionInfo -t TestBugSubscriptionMethods -t BugMuteSelfViewTestCase -t TestBugPortletSubscribers

== Demo and Q/A ==

Check that IBugTask:+mute page behaves as expected (iow, allows unmuting as well). Rest of the QA will be possible only when the follow-up branch lands.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py
  lib/lp/bugs/browser/tests/test_bug_views.py
  lib/lp/bugs/browser/bug.py
  lib/lp/bugs/model/bug.py
  lib/lp/bugs/browser/tests/test_bugsubscription_views.py
  lib/lp/bugs/browser/bugsubscription.py
  lib/lp/bugs/tests/test_bug.py
  lib/lp/bugs/interfaces/bug.py

./lib/lp/bugs/model/bug.py
     575: E225 missing whitespace around operator
     747: E225 missing whitespace around operator
     751: E225 missing whitespace around operator
     766: E225 missing whitespace around operator
    1461: E225 missing whitespace around operator
    1674: E261 at least two spaces before inline comment
    1676: E261 at least two spaces before inline comment
    1687: E261 at least two spaces before inline comment
    1689: E261 at least two spaces before inline comment
    1707: E225 missing whitespace around operator
    2262: E225 missing whitespace around operator
    2277: E225 missing whitespace around operator
    2287: E261 at least two spaces before inline comment
    2325: E225 missing whitespace around operator
    2587: E225 missing whitespace around operator
    2628: E225 missing whitespace around operator

To post a comment you must log in.
Revision history for this message
Данило Шеган (danilo) :
review: Abstain (db)
Revision history for this message
Abel Deuring (adeuring) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/schema/security.cfg'
2--- database/schema/security.cfg 2011-05-20 16:15:35 +0000
3+++ database/schema/security.cfg 2011-05-23 09:01:25 +0000
4@@ -546,6 +546,7 @@
5 public.bugcve = SELECT, INSERT
6 public.bugjob = SELECT, INSERT
7 public.bugmessage = SELECT, INSERT, UPDATE
8+public.bugmute = SELECT
9 public.bugnomination = SELECT
10 public.bugnotification = SELECT, INSERT
11 public.bugnotificationfilter = SELECT, INSERT
12@@ -1238,6 +1239,7 @@
13 public.bugcve = SELECT, INSERT
14 public.bugjob = SELECT, INSERT
15 public.bugmessage = SELECT, INSERT
16+public.bugmute = SELECT
17 public.bugnomination = SELECT
18 public.bugnotification = SELECT, INSERT
19 public.bugnotificationfilter = SELECT, INSERT
20@@ -1341,6 +1343,7 @@
21 public.bugcve = SELECT, INSERT
22 public.bugjob = SELECT, INSERT
23 public.bugmessage = SELECT, INSERT
24+public.bugmute = SELECT
25 public.bugnomination = SELECT
26 public.bugnotification = SELECT, INSERT
27 public.bugnotificationfilter = SELECT, INSERT
28@@ -1638,6 +1641,7 @@
29 public.bugcve = SELECT, INSERT
30 public.bugjob = SELECT, INSERT
31 public.bugmessage = SELECT, INSERT
32+public.bugmute = SELECT
33 public.bugnomination = SELECT, INSERT, UPDATE
34 public.bugnotification = SELECT, INSERT
35 public.bugnotificationattachment = SELECT
36
37=== modified file 'lib/lp/bugs/browser/bug.py'
38--- lib/lp/bugs/browser/bug.py 2011-05-17 14:54:56 +0000
39+++ lib/lp/bugs/browser/bug.py 2011-05-23 09:01:25 +0000
40@@ -292,7 +292,7 @@
41
42 return Link(
43 link, text, icon='remove', summary=(
44- "Mute this bug so that you will never receive emails "
45+ "Mute this bug so that you will not receive emails "
46 "about it."))
47
48 def nominate(self):
49@@ -538,9 +538,7 @@
50 """Return True if the user should see the Mute link."""
51 if features.getFeatureFlag('malone.advanced-subscriptions.enabled'):
52 user_is_subscribed = (
53- # Note that we don't have to check for isMuted(), since
54- # if isMuted() is True isSubscribed() will also be
55- # True.
56+ self.context.isMuted(self.user) or
57 self.context.isSubscribed(self.user) or
58 self.context.isSubscribedToDupes(self.user) or
59 self.context.personIsAlsoNotifiedSubscriber(self.user))
60
61=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
62--- lib/lp/bugs/browser/bugsubscription.py 2011-05-18 08:17:35 +0000
63+++ lib/lp/bugs/browser/bugsubscription.py 2011-05-23 09:01:25 +0000
64@@ -630,7 +630,10 @@
65
66 @property
67 def label(self):
68- return "Mute bug mail for bug %s" % self.context.bug.id
69+ if self.context.bug.isMuted(self.user):
70+ return "Unmute bug mail for bug %s" % self.context.bug.id
71+ else:
72+ return "Mute bug mail for bug %s" % self.context.bug.id
73
74 page_title = label
75
76@@ -641,15 +644,21 @@
77 cancel_url = next_url
78
79 def initialize(self):
80+ self.is_muted = self.context.bug.isMuted(self.user)
81 super(BugMuteSelfView, self).initialize()
82- # If the user is already muted, redirect them to the +subscribe
83- # page, since there's no point doing its work twice.
84- if self.context.bug.isMuted(self.user):
85- self.request.response.redirect(
86- canonical_url(self.context, view_name="+subscribe"))
87
88- @action('Mute bug mail', name='mute')
89+ @action('Mute bug mail',
90+ name='mute',
91+ condition=lambda form, action: not form.is_muted)
92 def mute_action(self, action, data):
93 self.context.bug.mute(self.user, self.user)
94 self.request.response.addInfoNotification(
95 "Mail for bug #%s has been muted." % self.context.bug.id)
96+
97+ @action('Unmute bug mail',
98+ name='unmute',
99+ condition=lambda form, action: form.is_muted)
100+ def unmute_action(self, action, data):
101+ self.context.bug.unmute(self.user, self.user)
102+ self.request.response.addInfoNotification(
103+ "Mail for bug #%s has been unmuted." % self.context.bug.id)
104
105=== modified file 'lib/lp/bugs/browser/tests/test_bug_views.py'
106--- lib/lp/bugs/browser/tests/test_bug_views.py 2011-05-17 14:54:56 +0000
107+++ lib/lp/bugs/browser/tests/test_bug_views.py 2011-05-23 09:01:25 +0000
108@@ -173,7 +173,30 @@
109 self.assertTrue('mute_subscription' in html)
110 # The template uses user_should_see_mute_link to decide
111 # whether or not to display the mute link.
112- soup = BeautifulSoup(html)
113 self.assertTrue(
114 self._hasCSSClass(html, 'mute-link-container', 'hidden'),
115 'No "hidden" CSS class in mute-link-container.')
116+
117+ def test_mute_subscription_link_shown_if_muted(self):
118+ # If a person is muted but not otherwise subscribed, they should still
119+ # see the (un)mute link.
120+ person = self.factory.makePerson()
121+ with person_logged_in(person):
122+ with FeatureFixture({self.feature_flag_1: 'on'}):
123+ self.bug.mute(person, person)
124+ # The user isn't subscribed already, but is muted.
125+ self.assertFalse(self.bug.isSubscribed(person))
126+ self.assertFalse(
127+ self.bug.personIsAlsoNotifiedSubscriber(
128+ person))
129+ self.assertTrue(self.bug.isMuted(person))
130+ view = create_initialized_view(
131+ self.bug, name="+portlet-subscribers")
132+ self.assertTrue(view.user_should_see_mute_link,
133+ "User should see mute link.")
134+ contents = view.render()
135+ self.assertTrue('mute_subscription' in contents,
136+ "'mute_subscription' not in contents.")
137+ self.assertFalse(
138+ self._hasCSSClass(
139+ contents, 'mute-link-container', 'hidden'))
140
141=== modified file 'lib/lp/bugs/browser/tests/test_bugsubscription_views.py'
142--- lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-05-17 11:44:33 +0000
143+++ lib/lp/bugs/browser/tests/test_bugsubscription_views.py 2011-05-23 09:01:25 +0000
144@@ -8,7 +8,6 @@
145 import transaction
146
147 from canonical.launchpad.ftests import LaunchpadFormHarness
148-from canonical.launchpad.webapp import canonical_url
149 from canonical.testing.layers import LaunchpadFunctionalLayer
150
151 from lp.bugs.browser.bugsubscription import (
152@@ -409,6 +408,44 @@
153 self.bug = self.factory.makeBug()
154 self.person = self.factory.makePerson()
155
156+ def test_is_muted_false(self):
157+ # BugMuteSelfView initialization sets the is_muted property.
158+ # When the person has not muted the bug, it's false.
159+ with person_logged_in(self.person):
160+ self.assertFalse(self.bug.isMuted(self.person))
161+ view = create_initialized_view(
162+ self.bug.default_bugtask, name="+mute")
163+ self.assertFalse(view.is_muted)
164+
165+ def test_is_muted_true(self):
166+ # BugMuteSelfView initialization sets the is_muted property.
167+ # When the person has muted the bug, it's true.
168+ with person_logged_in(self.person):
169+ self.bug.mute(self.person, self.person)
170+ self.assertTrue(self.bug.isMuted(self.person))
171+ view = create_initialized_view(
172+ self.bug.default_bugtask, name="+mute")
173+ self.assertTrue(view.is_muted)
174+
175+ def test_label_nonmuted(self):
176+ # Label to use for the button.
177+ with person_logged_in(self.person):
178+ self.assertFalse(self.bug.isMuted(self.person))
179+ expected_label = "Mute bug mail for bug %s" % self.bug.id
180+ view = create_initialized_view(
181+ self.bug.default_bugtask, name="+mute")
182+ self.assertEqual(expected_label, view.label)
183+
184+ def test_label_muted(self):
185+ # Label to use for the button.
186+ with person_logged_in(self.person):
187+ self.bug.mute(self.person, self.person)
188+ self.assertTrue(self.bug.isMuted(self.person))
189+ expected_label = "Unmute bug mail for bug %s" % self.bug.id
190+ view = create_initialized_view(
191+ self.bug.default_bugtask, name="+mute")
192+ self.assertEqual(expected_label, view.label)
193+
194 def test_bug_mute_self_view_mutes_bug(self):
195 # The BugMuteSelfView mutes bug mail for the current user when
196 # its form is submitted.
197@@ -419,17 +456,13 @@
198 form={'field.actions.mute': 'Mute bug mail'})
199 self.assertTrue(self.bug.isMuted(self.person))
200
201- def test_bug_mute_self_view_redirects_muted_users(self):
202- # The BugMuteSelfView redirects muted users to the +subscribe
203- # page, where they can remove their muted subscription or change
204- # their BugNotificationLevel.
205+ def test_bug_mute_self_view_unmutes_bug(self):
206+ # The BugMuteSelfView unmutes bug mail for the current user when
207+ # its form is submitted and the bug was already muted.
208 with person_logged_in(self.person):
209 self.bug.mute(self.person, self.person)
210- mute_view = create_initialized_view(
211- self.bug.default_bugtask, name="+mute")
212- response = mute_view.request.response
213- self.assertEqual(302, response.getStatus())
214- self.assertEqual(
215- canonical_url(self.bug.default_bugtask,
216- view_name="+subscribe"),
217- response.getHeader('Location'))
218+ self.assertTrue(self.bug.isMuted(self.person))
219+ create_initialized_view(
220+ self.bug.default_bugtask, name="+mute",
221+ form={'field.actions.unmute': 'Unmute bug mail'})
222+ self.assertFalse(self.bug.isMuted(self.person))
223
224=== modified file 'lib/lp/bugs/interfaces/bug.py'
225--- lib/lp/bugs/interfaces/bug.py 2011-05-18 08:17:35 +0000
226+++ lib/lp/bugs/interfaces/bug.py 2011-05-23 09:01:25 +0000
227@@ -509,7 +509,9 @@
228 @export_write_operation()
229 @operation_for_version('devel')
230 def unmute(person, unmuted_by):
231- """Remove a muted subscription for `person`."""
232+ """Remove a muted subscription for `person`.
233+
234+ Returns previously muted direct subscription, if any."""
235
236 def getDirectSubscriptions():
237 """A sequence of IBugSubscriptions directly linked to this bug."""
238@@ -779,9 +781,9 @@
239 schema=Interface, title=_('Target'), required=False),
240 nominations=List(
241 title=_("Nominations to search through."),
242- value_type=Reference(schema=Interface), # IBugNomination
243+ value_type=Reference(schema=Interface), # IBugNomination
244 required=False))
245- @operation_returns_collection_of(Interface) # IBugNomination
246+ @operation_returns_collection_of(Interface) # IBugNomination
247 @export_read_operation()
248 def getNominations(target=None, nominations=None):
249 """Return a list of all IBugNominations for this bug.
250
251=== modified file 'lib/lp/bugs/model/bug.py'
252--- lib/lp/bugs/model/bug.py 2011-05-18 08:17:35 +0000
253+++ lib/lp/bugs/model/bug.py 2011-05-23 09:01:25 +0000
254@@ -54,6 +54,7 @@
255 Count,
256 Desc,
257 Exists,
258+ In,
259 Join,
260 LeftJoin,
261 Max,
262@@ -531,7 +532,7 @@
263 parent = message_by_id.get(parent.id, parent)
264 else:
265 message, bugmessage = row
266- parent = None # parent attribute is not going to be accessed.
267+ parent = None # parent attribute is not going to be accessed.
268 index = bugmessage.index
269 result = IndexedMessage(message, inside, index, parent)
270 if include_parents:
271@@ -891,6 +892,7 @@
272 person = unmuted_by
273 mutes = self._getMutes(person)
274 store.remove(mutes.one())
275+ return self.getSubscriptionForPerson(person)
276
277 @property
278 def subscriptions(self):
279@@ -2249,7 +2251,9 @@
280 return IStore(BugSubscription).find(
281 BugSubscription,
282 BugSubscription.bug_notification_level >= self.level,
283- BugSubscription.bug == self.bug)
284+ BugSubscription.bug == self.bug,
285+ Not(In(BugSubscription.person_id,
286+ Select(BugMute.person_id, BugMute.bug_id==self.bug.id))))
287
288 @cachedproperty
289 @freeze(BugSubscriptionSet)
290@@ -2262,12 +2266,14 @@
291 BugSubscription,
292 BugSubscription.bug_notification_level >= self.level,
293 BugSubscription.bug_id == Bug.id,
294- Bug.duplicateof == self.bug)
295+ Bug.duplicateof == self.bug,
296+ Not(In(BugSubscription.person_id,
297+ Select(BugMute.person_id, BugMute.bug_id==Bug.id))))
298
299 @cachedproperty
300 @freeze(BugSubscriptionSet)
301 def duplicate_only_subscriptions(self):
302- """Subscripitions to duplicates of the bug.
303+ """Subscriptions to duplicates of the bug.
304
305 Excludes subscriptions for people who have a direct subscription or
306 are also notified for another reason.
307@@ -2308,11 +2314,15 @@
308 if self.bug.private:
309 return BugSubscriberSet()
310 else:
311+ muted = IStore(BugMute).find(
312+ Person,
313+ BugMute.person_id==Person.id,
314+ BugMute.bug==self.bug)
315 return BugSubscriberSet().union(
316 self.structural_subscriptions.subscribers,
317 self.all_pillar_owners_without_bug_supervisors,
318 self.all_assignees).difference(
319- self.direct_subscriptions.subscribers)
320+ self.direct_subscriptions.subscribers).difference(muted)
321
322 @cachedproperty
323 def indirect_subscribers(self):
324
325=== modified file 'lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py'
326--- lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py 2011-05-16 16:57:55 +0000
327+++ lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py 2011-05-23 09:01:25 +0000
328@@ -132,21 +132,42 @@
329 return BugSubscriptionInfo(
330 self.bug, BugNotificationLevel.LIFECYCLE)
331
332+ def _create_direct_subscriptions(self):
333+ subscribers = (
334+ self.factory.makePerson(),
335+ self.factory.makePerson())
336+ with person_logged_in(self.bug.owner):
337+ subscriptions = tuple(
338+ self.bug.subscribe(subscriber, subscriber)
339+ for subscriber in subscribers)
340+ return subscribers, subscriptions
341+
342 def test_direct(self):
343 # The set of direct subscribers.
344- subscribers = (
345- self.factory.makePerson(),
346- self.factory.makePerson())
347- with person_logged_in(self.bug.owner):
348- subscriptions = tuple(
349- self.bug.subscribe(subscriber, subscriber)
350- for subscriber in subscribers)
351+ subscribers, subscriptions = self._create_direct_subscriptions()
352 found_subscriptions = self.getInfo().direct_subscriptions
353 self.assertEqual(set(subscriptions), found_subscriptions)
354 self.assertEqual(subscriptions, found_subscriptions.sorted)
355 self.assertEqual(set(subscribers), found_subscriptions.subscribers)
356 self.assertEqual(subscribers, found_subscriptions.subscribers.sorted)
357
358+ def test_muted_direct(self):
359+ # If a direct is muted, it is not listed.
360+ subscribers, subscriptions = self._create_direct_subscriptions()
361+ with person_logged_in(subscribers[0]):
362+ self.bug.mute(subscribers[0], subscribers[0])
363+ found_subscriptions = self.getInfo().direct_subscriptions
364+ self.assertEqual(set([subscriptions[1]]), found_subscriptions)
365+
366+ def _create_duplicate_subscription(self):
367+ duplicate_bug = self.factory.makeBug(product=self.target)
368+ with person_logged_in(duplicate_bug.owner):
369+ duplicate_bug.markAsDuplicate(self.bug)
370+ duplicate_bug_subscription = (
371+ duplicate_bug.getSubscriptionForPerson(
372+ duplicate_bug.owner))
373+ return duplicate_bug, duplicate_bug_subscription
374+
375 def test_duplicate(self):
376 # The set of subscribers from duplicate bugs.
377 found_subscriptions = self.getInfo().duplicate_subscriptions
378@@ -154,12 +175,8 @@
379 self.assertEqual((), found_subscriptions.sorted)
380 self.assertEqual(set(), found_subscriptions.subscribers)
381 self.assertEqual((), found_subscriptions.subscribers.sorted)
382- duplicate_bug = self.factory.makeBug(product=self.target)
383- with person_logged_in(duplicate_bug.owner):
384- duplicate_bug.markAsDuplicate(self.bug)
385- duplicate_bug_subscription = (
386- duplicate_bug.getSubscriptionForPerson(
387- duplicate_bug.owner))
388+ duplicate_bug, duplicate_bug_subscription = (
389+ self._create_duplicate_subscription())
390 found_subscriptions = self.getInfo().duplicate_subscriptions
391 self.assertEqual(
392 set([duplicate_bug_subscription]),
393@@ -174,6 +191,15 @@
394 (duplicate_bug.owner,),
395 found_subscriptions.subscribers.sorted)
396
397+ def test_muted_duplicate(self):
398+ # If a duplicate is muted, it is not listed.
399+ duplicate_bug, duplicate_bug_subscription = (
400+ self._create_duplicate_subscription())
401+ with person_logged_in(duplicate_bug.owner):
402+ self.bug.mute(duplicate_bug.owner, duplicate_bug.owner)
403+ found_subscriptions = self.getInfo().duplicate_subscriptions
404+ self.assertEqual(set(), found_subscriptions)
405+
406 def test_duplicate_for_private_bug(self):
407 # The set of subscribers from duplicate bugs is always empty when the
408 # master bug is private.
409@@ -274,11 +300,7 @@
410 (bugtask.pillar.owner, bugtask2.pillar.owner),
411 found_owners.sorted)
412
413- def test_also_notified_subscribers(self):
414- # The set of also notified subscribers.
415- found_subscribers = self.getInfo().also_notified_subscribers
416- self.assertEqual(set(), found_subscribers)
417- self.assertEqual((), found_subscribers.sorted)
418+ def _create_also_notified_subscribers(self):
419 # Add an assignee, a bug supervisor and a structural subscriber.
420 bugtask = self.bug.default_bugtask
421 assignee = self.factory.makePerson()
422@@ -291,6 +313,15 @@
423 with person_logged_in(structural_subscriber):
424 bugtask.target.addSubscription(
425 structural_subscriber, structural_subscriber)
426+ return assignee, supervisor, structural_subscriber
427+
428+ def test_also_notified_subscribers(self):
429+ # The set of also notified subscribers.
430+ found_subscribers = self.getInfo().also_notified_subscribers
431+ self.assertEqual(set(), found_subscribers)
432+ self.assertEqual((), found_subscribers.sorted)
433+ assignee, supervisor, structural_subscriber = (
434+ self._create_also_notified_subscribers())
435 # Add a direct subscription.
436 direct_subscriber = self.factory.makePerson()
437 with person_logged_in(self.bug.owner):
438@@ -305,6 +336,30 @@
439 (assignee, supervisor, structural_subscriber),
440 found_subscribers.sorted)
441
442+ def test_muted_also_notified_subscribers(self):
443+ # If someone is muted, they are not listed in the
444+ # also_notified_subscribers.
445+ assignee, supervisor, structural_subscriber = (
446+ self._create_also_notified_subscribers())
447+ # As a control, we first show that the
448+ # the assignee, supervisor and structural subscriber do show up
449+ # when they are not muted.
450+ found_subscribers = self.getInfo().also_notified_subscribers
451+ self.assertEqual(
452+ set([assignee, supervisor, structural_subscriber]),
453+ found_subscribers)
454+ # Now we mute all of the subscribers.
455+ with person_logged_in(assignee):
456+ self.bug.mute(assignee, assignee)
457+ with person_logged_in(supervisor):
458+ self.bug.mute(supervisor, supervisor)
459+ with person_logged_in(structural_subscriber):
460+ self.bug.mute(structural_subscriber, structural_subscriber)
461+ # Now we don't see them.
462+ found_subscribers = self.getInfo().also_notified_subscribers
463+ self.assertEqual(
464+ set(), found_subscribers)
465+
466 def test_also_notified_subscribers_for_private_bug(self):
467 # The set of also notified subscribers is always empty when the master
468 # bug is private.
469@@ -472,7 +527,7 @@
470 self.info.all_pillar_owners_without_bug_supervisors
471
472 def test_also_notified_subscribers(self):
473- with self.exactly_x_queries(5):
474+ with self.exactly_x_queries(6):
475 self.info.also_notified_subscribers
476
477 def test_also_notified_subscribers_later(self):
478@@ -482,11 +537,11 @@
479 self.info.all_pillar_owners_without_bug_supervisors
480 self.info.direct_subscriptions.subscribers
481 self.info.structural_subscriptions.subscribers
482- with self.exactly_x_queries(0):
483+ with self.exactly_x_queries(1):
484 self.info.also_notified_subscribers
485
486 def test_indirect_subscribers(self):
487- with self.exactly_x_queries(6):
488+ with self.exactly_x_queries(7):
489 self.info.indirect_subscribers
490
491 def test_indirect_subscribers_later(self):
492
493=== modified file 'lib/lp/bugs/tests/test_bug.py'
494--- lib/lp/bugs/tests/test_bug.py 2011-05-18 08:17:35 +0000
495+++ lib/lp/bugs/tests/test_bug.py 2011-05-23 09:01:25 +0000
496@@ -105,6 +105,22 @@
497 self.bug.unmute(self.person, self.person)
498 self.assertFalse(self.bug.isMuted(self.person))
499
500+ def test_unmute_returns_direct_subscription(self):
501+ # Bug.unmute() returns the previously muted direct subscription, if
502+ # any.
503+ with person_logged_in(self.person):
504+ self.bug.mute(self.person, self.person)
505+ self.assertEqual(True, self.bug.isMuted(self.person))
506+ self.assertEqual(None, self.bug.unmute(self.person, self.person))
507+ self.assertEqual(False, self.bug.isMuted(self.person))
508+ subscription = self.bug.subscribe(
509+ self.person, self.person,
510+ level=BugNotificationLevel.METADATA)
511+ self.bug.mute(self.person, self.person)
512+ self.assertEqual(True, self.bug.isMuted(self.person))
513+ self.assertEqual(
514+ subscription, self.bug.unmute(self.person, self.person))
515+
516 def test_unmute_mutes_unmuter(self):
517 # When exposed in the web API, the unmute method regards the
518 # first, `person` argument as optional, and the second

Subscribers

People subscribed via source and target branches

to status/vote changes: