Merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub into lp:launchpad

Proposed by Brian Murray
Status: Merged
Approved by: Deryck Hodge
Approved revision: no longer in the source branch.
Merged at revision: 11385
Proposed branch: lp:~brian-murray/launchpad/bug-556489-distro-struct-sub
Merge into: lp:launchpad
Diff against target: 673 lines (+295/-148)
10 files modified
lib/lp/bugs/browser/bug.py (+1/-1)
lib/lp/bugs/browser/bugtask.py (+3/-1)
lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt (+1/-1)
lib/lp/registry/browser/structuralsubscription.py (+5/-1)
lib/lp/registry/configure.zcml (+14/-7)
lib/lp/registry/interfaces/distribution.py (+1/-1)
lib/lp/registry/interfaces/structuralsubscription.py (+3/-0)
lib/lp/registry/model/structuralsubscription.py (+24/-1)
lib/lp/registry/tests/structural-subscription-target.txt (+5/-134)
lib/lp/registry/tests/test_structuralsubscriptiontarget.py (+238/-1)
To merge this branch: bzr merge lp:~brian-murray/launchpad/bug-556489-distro-struct-sub
Reviewer Review Type Date Requested Status
Deryck Hodge (community) code Approve
Review via email: mp+31090@code.launchpad.net

Commit message

If a distribution has a bug supervisor set, only allow the bug supervisor or members of that team to subscribe to all of the distribution's bug reports.

Description of the change

= Summary =

The Ubuntu distribution has a large number of subscribers whose accounts are
deactivated, https://bugs.edge.launchpad.net/ubuntu/+subscribe, probably due
to the volume of the bug mail they ended up receiving. This is problematic to
some degree as all the subscribers show up on every bug report about Ubuntu.
Subscribing to all of Ubuntu bugs can also cause an unpleasant user experience
due to the volume of email they end up receiving and it not being clear how to
stop all of it. Additionally, if someone really wants to subscribe to every
bug report about Ubuntu there is a mailing list
(https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs) for that.

== Proposed fix ==

To prevent people from over subscribing themselves a permissions check should
be added to distribution stuctural subscriptions to ensure that only admins or
bug supervisors, if one exists, can subscribe to the distribution.

== Pre-implementation notes ==

This was discussed with Deryck who reported bug 556489 regarding this issue
and we discussed the process for removing existing subscribers to Ubuntu.

== Implementation details ==

While writing a test for the special case of distribution structural
subscriptions, I discovered that test_structuralsubscriptiontarget.py really
used structural-subscription-target.txt which needed some rewriting as a unit
test.

lib/lp/registry/model/structuralsubscription.py:
 * Added distribution check to addBugSubscription

lib/lp/registry/tests/test_structuralsubscriptiontarget.py:
 * Refactored most of structural-subscription-target.txt as a unit test

lib/lp/registry/tests/structural-subscription-target.txt:
 * Removed portions implemented in test_structuralsubscriptiontarget.py

== Tests ==

bin/test -vv -t test_structuralsubscriptiontarget -t structural-subscription-target.txt

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

I'm not clear why this makes sense.

 - users can subscribe to the bugs list [same amount of mail]
 - there are uses to being notified of every bug, same as things like
gmane are subscribed to lists
 - this makes lp less flexible not more.

I suspect you're doing a bandaid to the real issues:
 - subscriber lists take up a lot of space and are rarely important
 - we don't prune deactivated accounts effectively
 - the implications of structural subscription are not clear to users

Could you please enlarge on why the approach you've put forward makes
sense long term? If we do need a bandaid, I'm positive doing that, but
I get the impression you don't consider this a bandaid, and given the
downsides: more interrupts from valid subscribers, more code to handle
special-only-in-scale problems, more ui complexity that is hard to
explain, I'd really prefer to see a path to a long term unified fix!

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

On Wed, Jul 28, 2010 at 05:13:42AM -0000, Robert Collins wrote:
> I'm not clear why this makes sense.
>
> - users can subscribe to the bugs list [same amount of mail]

However, it is harder to subscribe to the mailing list than structurally
subscribe to Ubuntu.

> - there are uses to being notified of every bug, same as things like
> gmane are subscribed to lists
> - this makes lp less flexible not more.

Yes, but this only applies to distribution's using Launchpad that have a
bug supervisor. Additionally, it just so happens that Ubuntu has a team
set as the bug supervisor which has the email address set to a mailing
list. Other distributions do not, or should not, have to have a bug
supervisor set and in the event that they do that could be a team
without a mailing list set for the contact address.

> I suspect you're doing a bandaid to the real issues:
> - subscriber lists take up a lot of space and are rarely important
> - we don't prune deactivated accounts effectively
> - the implications of structural subscription are not clear to users
>
> Could you please enlarge on why the approach you've put forward makes
> sense long term? If we do need a bandaid, I'm positive doing that, but
> I get the impression you don't consider this a bandaid, and given the
> downsides: more interrupts from valid subscribers, more code to handle
> special-only-in-scale problems, more ui complexity that is hard to
> explain, I'd really prefer to see a path to a long term unified fix!

I do agree that is a band-aid in that we are preventing people from
doing something they don't really understand the consequences of.
Looking at my personal email archive I have some emails from people who
mistakenly subscribed to all bug reports about Ubuntu, did not know how
to unsubscribe and were receiving hundreds of emails a day. While
emails provide an unsubscribe link for individual bug reports there is
no link for how to remove a structural subscription.

I'd guess that we will get less requests from people wanting to
subscribe to all bug reports about Ubuntu than we will about people
wanting to be unsubscribed. Perhaps we should track this somehow?

--
Brian Murray

Revision history for this message
Brian Murray (brian-murray) wrote :

A structural subscription unsubscribe link is covered in bug 484319.

Revision history for this message
Abel Deuring (adeuring) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 28.07.2010 19:41, Brian Murray wrote:
> I do agree that is a band-aid in that we are preventing people from
> doing something they don't really understand the consequences of.
> Looking at my personal email archive I have some emails from people who
> mistakenly subscribed to all bug reports about Ubuntu, did not know how
> to unsubscribe and were receiving hundreds of emails a day. While
> emails provide an unsubscribe link for individual bug reports there is
> no link for how to remove a structural subscription.

Another example: <email address hidden> receives more or less regulary
complaints from people who feel "drowning" by the amount of mails they
get from bug subscriptions.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iD8DBQFMUIF/ekBPhm8NrtARAtQqAKCQ2872DAsH0hByPaK8dzTlLk/jiwCeOjwE
LI/vZ8xrFAShGAD7DE6MBjY=
=50CY
-----END PGP SIGNATURE-----

Revision history for this message
Deryck Hodge (deryck) wrote :

While I understand Rob's concerns, I think in this case we do want to be opinionated and keep people from getting themselves into trouble. "less flexible" is a feature not a bug in this case. :-)

Yes, we should fix the surrounding issues, some of which will be fixed by our current work to improve subscriptions. But there is rarely a case where subscribing to all of a distribution's bugs is what people actually want to do. For the times where there is -- e.g., someone wants to archive all bug activity for a distribution -- there is an easy work around by subscribing to the mailing list or join the bug supervisor team. Perhaps this adds some UI complexity to communicate how one would go about this, but I think this risk is acceptable given the alternatives and the few number of people who desire this.

I'll also take a look at the code now to review this for Brian, to get him unblocked on this work.

Cheers,
deryck

Revision history for this message
Deryck Hodge (deryck) wrote :

Hi, Brian.

I've had a chance to review the code itself now. Thanks for the great job here you did refactoring the tests! Well done!

I do, however, see a couple problems that need to be addressed before this is ready to land.

First, you do a great job of avoiding sample data in the test except for your admin test, which uses Foo Bar. Your commented out attempt at using a with block and logging in the admin celebrity is the right way to go. You can reference the currently logged in user inside that block. Look in lp.testing.tests.test_login and look at test_with_celebrity_logged_in and getLoggedInPerson for ideas of how to do this. If you need more help working this out, ping me on IRC.

Second, this work ensures that addBugSubscription won't succeed, but the user will still have a subscribe to bug mail link, which will then OOPS if that click-path is followed and the user tries to subscribe. The code that checks that the user can subscribe should be refactored into a helper method which can be called from the model and the view so that the link is never presented in the first place. See lp.bugs.browser.bugtask.BugTaskSearchListingMenu.subscribe for where to do this check in the view.

Again, feel free to ping me if you need to talk more about these changes.

Cheers,
deryck

review: Needs Fixing (code)
Revision history for this message
Deryck Hodge (deryck) wrote :

The code itself looks fine now. But I led you wrong on where to check to display the link, I think. This didn't work for me testing locally. Did it work for you?

I didn't realize there is a mix at lp.registry.browser.structuralsubscription.StructuralSubscriptionMenuMixin where the link gets setup now. Sorry. The check is good, I believe, but needs to be done inside the mixin.

Revision history for this message
Robert Collins (lifeless) wrote :

So, to make sure I understand:
 - a distro without a bug supervisor does not permit structural subscription?

We are expecting many more distros though - derived distros are being worked on.

I have no problem with being opinionated; I'd -much- rather we warned
than stopped people - simply because if we prohibit something we
*will* get bug reports that its prohibited (the same as we get users
who were not warned about the firehose - who were *invited* into doing
something silly).

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

On Tue, Aug 03, 2010 at 10:11:04PM -0000, Robert Collins wrote:
> So, to make sure I understand:
> - a distro without a bug supervisor does not permit structural subscription?

No, this is backwards.

A distro without a bug supervisor allows structural subscription.

A distro with a bug supervisor only allows the bug supervisor, or
members of that team, to structurally subscribe.

Revision history for this message
Deryck Hodge (deryck) wrote :

On Tue, Aug 3, 2010 at 5:11 PM, Robert Collins
<email address hidden> wrote:
> So, to make sure I understand:
>  - a distro without a bug supervisor does not permit structural subscription?
>
> We are expecting many more distros though - derived distros are being worked on.
>
> I have no problem with being opinionated; I'd -much- rather we warned
> than stopped people - simply because if we prohibit something we
> *will* get bug reports that its prohibited (the same as we get users
> who were not warned about the firehose - who were *invited* into doing
> something silly).
>

Right, but we get bug reports regardless of what we do. :-) The
threat of bug reports makes me neither fear nor regret the change. ;)

The real question is "should we allow users to subscribe to all of a
distributions bugs?" I think for distributions with a large number of
users and bugs this makes no sense. Brian's choice to limit this to
distros that have defined a bug supervisor seems about the best check
we have for "is this a large distro?" Not perfect, sure, but not bad
either.

The cases of someone wanting to subscribe to every bug email for
everything in a distro are few -- people like Brian and the Debian PTS
-- and all of these cases have an option to get at the notifications
(subscribe to mailing list or join bug supervisor team), and in the
common case, we prevent people from getting themselves into trouble
and giving Launchpad a bad reputation for spamming people. Also,
we're not changing anyone's current subscription, just preventing them
in the future. We're only going to clean up deactivated accounts once
this work lands.

People don't read warnings. There is already a warning.

Finally, I really don't understand why this change is a big deal. In
the world of Launchpad, this is a very minor area of potential
complexity and confusion, and it brings great benefit in terms of
sparing less-experienced users getting spammed by the bug tracker.
It's well known and well documented that, despite our best efforts,
people end up subscribing to all of Ubuntu's bugs. Why not prevent
that and be done with it? :-)

Cheers,
deryck

--
Deryck Hodge
https://launchpad.net/~deryck
http://www.devurandom.org/

Revision history for this message
Robert Collins (lifeless) wrote :

>> Finally, I really don't understand why this change is a big deal.

Its not a big deal :- I'm still getting a handle on a couple of things.

One of them is how to be clear what hat I'm when I say something; in
this conversation its a bit unclear - sorry. I wasn't trying to swing
the TA bat at this particular mp - rather I trying to really
understand the tradeoffs we're making, and why - and expressing my
personal opinions freely as I learn.

The other thing *is* architectural, which added to the confusion i
generated with the first thing : I'm still refining my understanding /
story around what bits of our UI are for machines, and what aren't :
the email interfaces are kindof-both and as such there are tensions
around making it really excellent. (Contrast with the web UI - humans
only, and the rest UI - machines only).

-Rob

Revision history for this message
Brian Murray (brian-murray) wrote :

It should be ready for re-review now. I tested it locally by setting a bug supervisor (guadamen) for Ubuntu and then confirming that the link did not appear for someone (sample person) who is not a member of that bug supervisor team.

Revision history for this message
Deryck Hodge (deryck) wrote :

Looks good. Thanks for all your work getting this ready.

Cheers,
deryck

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/bugs/browser/bug.py'
--- lib/lp/bugs/browser/bug.py 2010-08-07 14:54:40 +0000
+++ lib/lp/bugs/browser/bug.py 2010-08-17 14:18:48 +0000
@@ -251,7 +251,7 @@
251 return Link('+addbranch', text, icon='add')251 return Link('+addbranch', text, icon='add')
252252
253 def linktocve(self):253 def linktocve(self):
254 """Return the 'Link tp CVE' Link."""254 """Return the 'Link to CVE' Link."""
255 text = structured(255 text = structured(
256 'Link to '256 'Link to '
257 '<abbr title="Common Vulnerabilities and Exposures Index">'257 '<abbr title="Common Vulnerabilities and Exposures Index">'
258258
=== modified file 'lib/lp/bugs/browser/bugtask.py'
--- lib/lp/bugs/browser/bugtask.py 2010-08-03 08:49:19 +0000
+++ lib/lp/bugs/browser/bugtask.py 2010-08-17 14:18:48 +0000
@@ -2320,7 +2320,9 @@
2320 '+securitycontact', 'Change security contact', icon='edit')2320 '+securitycontact', 'Change security contact', icon='edit')
23212321
2322 def subscribe(self):2322 def subscribe(self):
2323 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')2323 user = getUtility(ILaunchBag).user
2324 if self.context.userCanAlterBugSubscription(user):
2325 return Link('+subscribe', 'Subscribe to bug mail', icon='edit')
23242326
2325 def nominations(self):2327 def nominations(self):
2326 return Link('+nominations', 'Review nominations', icon='bug')2328 return Link('+nominations', 'Review nominations', icon='bug')
23272329
=== modified file 'lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt'
--- lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2009-12-07 15:00:59 +0000
+++ lib/lp/bugs/templates/bugtarget-portlet-bugfilters.pt 2010-08-17 14:18:48 +0000
@@ -11,7 +11,7 @@
11 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />11 tal:content="structure context/@@+bugtarget-portlet-bugfilters-info" />
12 <tbody tal:define="menu context/menu:bugs">12 <tbody tal:define="menu context/menu:bugs">
13 <tr tal:define="subscribe_link menu/subscribe|nothing"13 <tr tal:define="subscribe_link menu/subscribe|nothing"
14 tal:condition="subscribe_link">14 tal:condition="python: subscribe_link and subscribe_link.enabled">
15 <td class="bugs-count" style="padding-top: 1em">15 <td class="bugs-count" style="padding-top: 1em">
16 <a tal:attributes="href subscribe_link/url">16 <a tal:attributes="href subscribe_link/url">
17 <img tal:attributes="src subscribe_link/icon_url" />17 <img tal:attributes="src subscribe_link/icon_url" />
1818
=== modified file 'lib/lp/registry/browser/structuralsubscription.py'
--- lib/lp/registry/browser/structuralsubscription.py 2010-04-16 15:06:55 +0000
+++ lib/lp/registry/browser/structuralsubscription.py 2010-08-17 14:18:48 +0000
@@ -313,4 +313,8 @@
313 else:313 else:
314 text = 'Subscribe to bug mail'314 text = 'Subscribe to bug mail'
315 icon = 'add'315 icon = 'add'
316 return Link('+subscribe', text, icon=icon, enabled=enabled)316 if enabled == False or (
317 not sst.userCanAlterBugSubscription(self.user, self.user)):
318 return Link('+subscribe', text, icon=icon, enabled=False)
319 else:
320 return Link('+subscribe', text, icon=icon, enabled=enabled)
317321
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2010-08-06 14:49:35 +0000
+++ lib/lp/registry/configure.zcml 2010-08-17 14:18:48 +0000
@@ -150,7 +150,8 @@
150 getSubscriptions150 getSubscriptions
151 parent_subscription_target151 parent_subscription_target
152 target_type_display152 target_type_display
153 userHasBugSubscriptions"/>153 userHasBugSubscriptions
154 userCanAlterBugSubscription"/>
154 <require155 <require
155 permission="launchpad.AnyPerson"156 permission="launchpad.AnyPerson"
156 attributes="157 attributes="
@@ -294,7 +295,8 @@
294 getBugNotificationsRecipients295 getBugNotificationsRecipients
295 parent_subscription_target296 parent_subscription_target
296 target_type_display297 target_type_display
297 userHasBugSubscriptions"/>298 userHasBugSubscriptions
299 userCanAlterBugSubscription"/>
298 <require300 <require
299 permission="launchpad.AnyPerson"301 permission="launchpad.AnyPerson"
300 attributes="302 attributes="
@@ -417,7 +419,8 @@
417 official_bug_tags419 official_bug_tags
418 findRelatedArchives420 findRelatedArchives
419 findRelatedArchivePublications421 findRelatedArchivePublications
420 userHasBugSubscriptions"/>422 userHasBugSubscriptions
423 userCanAlterBugSubscription"/>
421 <require424 <require
422 permission="launchpad.AnyPerson"425 permission="launchpad.AnyPerson"
423 attributes="426 attributes="
@@ -924,7 +927,8 @@
924 getBugNotificationsRecipients927 getBugNotificationsRecipients
925 parent_subscription_target928 parent_subscription_target
926 target_type_display929 target_type_display
927 userHasBugSubscriptions"/>930 userHasBugSubscriptions
931 userCanAlterBugSubscription"/>
928 <require932 <require
929 permission="launchpad.AnyPerson"933 permission="launchpad.AnyPerson"
930 attributes="934 attributes="
@@ -1195,7 +1199,8 @@
1195 getBugNotificationsRecipients1199 getBugNotificationsRecipients
1196 parent_subscription_target1200 parent_subscription_target
1197 target_type_display1201 target_type_display
1198 userHasBugSubscriptions"/>1202 userHasBugSubscriptions
1203 userCanAlterBugSubscription"/>
1199 <require1204 <require
1200 permission="launchpad.AnyPerson"1205 permission="launchpad.AnyPerson"
1201 attributes="1206 attributes="
@@ -1344,7 +1349,8 @@
1344 getSubscriptions1349 getSubscriptions
1345 parent_subscription_target1350 parent_subscription_target
1346 target_type_display1351 target_type_display
1347 userHasBugSubscriptions"/>1352 userHasBugSubscriptions
1353 userCanAlterBugSubscription"/>
1348 <require1354 <require
1349 permission="launchpad.AnyPerson"1355 permission="launchpad.AnyPerson"
1350 attributes="1356 attributes="
@@ -1467,7 +1473,8 @@
1467 getBugNotificationsRecipients1473 getBugNotificationsRecipients
1468 parent_subscription_target1474 parent_subscription_target
1469 target_type_display1475 target_type_display
1470 userHasBugSubscriptions"/>1476 userHasBugSubscriptions
1477 userCanAlterBugSubscription"/>
1471 <require1478 <require
1472 permission="launchpad.AnyPerson"1479 permission="launchpad.AnyPerson"
1473 attributes="1480 attributes="
14741481
=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py 2010-08-12 19:35:11 +0000
+++ lib/lp/registry/interfaces/distribution.py 2010-08-17 14:18:48 +0000
@@ -183,7 +183,7 @@
183 "The person or team responsible for decisions about features "183 "The person or team responsible for decisions about features "
184 "and bugs that will be targeted for any series in this "184 "and bugs that will be targeted for any series in this "
185 "distribution. Note that you can also specify a driver "185 "distribution. Note that you can also specify a driver "
186 "on each series who's permissions will be limited to that "186 "on each series whose permissions will be limited to that "
187 "specific series."),187 "specific series."),
188 required=False, vocabulary='ValidPersonOrTeam'))188 required=False, vocabulary='ValidPersonOrTeam'))
189 drivers = Attribute(189 drivers = Attribute(
190190
=== modified file 'lib/lp/registry/interfaces/structuralsubscription.py'
--- lib/lp/registry/interfaces/structuralsubscription.py 2010-07-28 20:44:25 +0000
+++ lib/lp/registry/interfaces/structuralsubscription.py 2010-08-17 14:18:48 +0000
@@ -184,6 +184,9 @@
184 :return: The new subscription.184 :return: The new subscription.
185 """185 """
186186
187 def userCanAlterBugSubscription(subscriber, subscribed_by):
188 """Check if a user can change a bug subscription for a person."""
189
187 @operation_parameters(190 @operation_parameters(
188 subscriber=Reference(191 subscriber=Reference(
189 schema=IPerson,192 schema=IPerson,
190193
=== modified file 'lib/lp/registry/model/structuralsubscription.py'
--- lib/lp/registry/model/structuralsubscription.py 2010-07-29 20:16:23 +0000
+++ lib/lp/registry/model/structuralsubscription.py 2010-08-17 14:18:48 +0000
@@ -179,12 +179,35 @@
179 subscribed_by=subscribed_by,179 subscribed_by=subscribed_by,
180 **self._target_args)180 **self._target_args)
181181
182 def userCanAlterBugSubscription(self, subscriber, subscribed_by):
183 """See `IStructuralSubscriptionTarget`."""
184
185 admins = getUtility(ILaunchpadCelebrities).admin
186 # If the object to be structurally subscribed to for bug
187 # notifications is a distribution and that distribution has a
188 # bug supervisor then only the bug supervisor or a member of
189 # that team or, of course, admins, can subscribe someone to it.
190 if IDistribution.providedBy(self) and self.bug_supervisor is not None:
191 if subscriber is None or subscribed_by is None:
192 return False
193 elif (subscriber != self.bug_supervisor
194 and not subscriber.inTeam(self.bug_supervisor)
195 and not subscribed_by.inTeam(admins)):
196 return False
197 return True
198
182 def addBugSubscription(self, subscriber, subscribed_by):199 def addBugSubscription(self, subscriber, subscribed_by):
183 """See `IStructuralSubscriptionTarget`."""200 """See `IStructuralSubscriptionTarget`."""
184 # This is a helper method for creating a structural201 # This is a helper method for creating a structural
185 # subscription and immediately giving it a full202 # subscription and immediately giving it a full
186 # bug notification level. It is useful so long as203 # bug notification level. It is useful so long as
187 # subscriptions are mainly used to implement bug contacts.204 # subscriptions are mainly used to implement bug contacts.
205
206 if not self.userCanAlterBugSubscription(subscriber, subscribed_by):
207 raise UserCannotSubscribePerson(
208 '%s does not have permission to subscribe %s' % (
209 subscribed_by.name, subscriber.name))
210
188 sub = self.addSubscription(subscriber, subscribed_by)211 sub = self.addSubscription(subscriber, subscribed_by)
189 sub.bug_notification_level = BugNotificationLevel.COMMENTS212 sub.bug_notification_level = BugNotificationLevel.COMMENTS
190 return sub213 return sub
@@ -239,7 +262,7 @@
239 for key, value in self._target_args.items():262 for key, value in self._target_args.items():
240 if value is None:263 if value is None:
241 target_clause_parts.append(264 target_clause_parts.append(
242 "StructuralSubscription.%s IS NULL " % (key,))265 "StructuralSubscription.%s IS NULL " % (key, ))
243 else:266 else:
244 target_clause_parts.append(267 target_clause_parts.append(
245 "StructuralSubscription.%s = %s " % (key, quote(value)))268 "StructuralSubscription.%s = %s " % (key, quote(value)))
246269
=== modified file 'lib/lp/registry/tests/structural-subscription-target.txt'
--- lib/lp/registry/tests/structural-subscription-target.txt 2010-04-19 08:11:52 +0000
+++ lib/lp/registry/tests/structural-subscription-target.txt 2010-08-17 14:18:48 +0000
@@ -1,144 +1,15 @@
1= IStructuralSubscriptionTarget =1= IStructuralSubscriptionTarget =
22
3Structural subscriptions allow users to subscribe to notifications about3
4new items and changes to existing items for a Launchpad structure such as4Let's subscribe ubuntu-team.
5Product, ProjectGroup, Distribution, Milestone and series.
6
7 >>> from canonical.launchpad.interfaces import (
8 ... IStructuralSubscriptionTarget)
9 >>> from canonical.launchpad.webapp.testing import verifyObject
10 >>> verifyObject(IStructuralSubscriptionTarget, target)
11 True
12
13== Bug Subscriptions ==
14
15Bug subscriptions are structural subscriptions to notifications about
16bug activity.
17
18 >>> target.bug_subscriptions
19 []
20
21First, we try to create a new subscription.
225
23 >>> from canonical.launchpad.interfaces import IPersonSet6 >>> from canonical.launchpad.interfaces import IPersonSet
24 >>> personset = getUtility(IPersonSet)7 >>> personset = getUtility(IPersonSet)
8 >>> ubuntu_team = personset.getByName("ubuntu-team")
25 >>> no_priv = personset.getByName("no-priv")9 >>> no_priv = personset.getByName("no-priv")
2610 >>> foobar = personset.getByName("name16")
27Only authenticated users can create subscriptions.
28
29 >>> target.addBugSubscription(no_priv, no_priv)
30 Traceback (most recent call last):
31 ...
32 Unauthorized: ...
33
34Let's login then to add a subscription:
35
36 >>> from canonical.launchpad.ftests import login11 >>> from canonical.launchpad.ftests import login
37 >>> login("foo.bar@canonical.com")12 >>> login("foo.bar@canonical.com")
38
39 >>> target.addBugSubscription(no_priv, no_priv)
40 <StructuralSubscription ...>
41 >>> [sub.subscriber.name for sub in target.bug_subscriptions]
42 [u'no-priv']
43
44Trying to add a subscription to a target when that person
45or team is already subscribed to that target will return
46the existing subscription.
47
48 >>> target.addBugSubscription(no_priv, no_priv)
49 <StructuralSubscription ...>
50
51People can only be subscribed by themselves, and only the team admins may
52subscribe a team.
53
54no-priv, who has no relationship to ubuntu-team, cannot subscribe it.
55
56 >>> ubuntu_team = personset.getByName("ubuntu-team")
57 >>> target.addBugSubscription(ubuntu_team, no_priv)
58 Traceback (most recent call last):
59 ...
60 UserCannotSubscribePerson: no-priv does not have permission to subscribe ubuntu-team.
61
62But kamion, an admin of the team, can.
63
64 >>> kamion = personset.getByName("kamion")
65 >>> target.addBugSubscription(ubuntu_team, kamion)
66 <StructuralSubscription ...>
67
68 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
69 [u'no-priv', u'ubuntu-team']
70
71foobar, a Launchpad administrator, can as well.
72
73 >>> foobar = personset.getByName("name16")
74 >>> target.addBugSubscription(ubuntu_team, foobar)
75 <StructuralSubscription ...>
76
77A non-admin cannot subscribe a person other than themselves.
78
79 >>> target.addBugSubscription(kamion, no_priv)
80 Traceback (most recent call last):
81 ...
82 UserCannotSubscribePerson: no-priv does not have permission to subscribe kamion.
83 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
84 [u'no-priv', u'ubuntu-team']
85
86But again, an admin can.
87
88 >>> target.addBugSubscription(kamion, foobar)
89 <StructuralSubscription ...>
90 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
91 [u'kamion', u'no-priv', u'ubuntu-team']
92
93To remove a bug subscription, use
94IStructuralSubscriptionTarget.removeBugSubscription:
95
96 >>> target.removeBugSubscription(no_priv, no_priv)
97 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
98 [u'kamion', u'ubuntu-team']
99
100The subscription rules apply to unsubscription as well.
101
102An unprivileged user cannot unsubscribe a team.
103
104 >>> target.removeBugSubscription(ubuntu_team, no_priv)
105 Traceback (most recent call last):
106 ...
107 UserCannotSubscribePerson: no-priv does not have permission to unsubscribe ubuntu-team.
108 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
109 [u'kamion', u'ubuntu-team']
110
111But a team admin can.
112
113 >>> target.removeBugSubscription(ubuntu_team, kamion)
114 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
115 [u'kamion']
116
117An unprivileged user also cannot unsubscribe another user.
118
119 >>> target.removeBugSubscription(kamion, no_priv)
120 Traceback (most recent call last):
121 ...
122 UserCannotSubscribePerson: no-priv does not have permission to unsubscribe kamion.
123 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
124 [u'kamion']
125
126But the user themselves can.
127
128 >>> target.removeBugSubscription(kamion, kamion)
129 >>> sorted([sub.subscriber.name for sub in target.bug_subscriptions])
130 []
131
132Trying to remove a subscription that doesn't exist on a target raises a
133DeleteSubscriptionError.
134
135 >>> target.removeBugSubscription(foobar, foobar)
136 Traceback (most recent call last):
137 ...
138 DeleteSubscriptionError: ...
139
140Let's subscribe ubuntu-team again.
141
142 >>> target.addBugSubscription(ubuntu_team, foobar)13 >>> target.addBugSubscription(ubuntu_team, foobar)
143 <StructuralSubscription ...>14 <StructuralSubscription ...>
14415
@@ -191,7 +62,7 @@
19162
19263
193=== Structural subscriptions and indirect bug subscriptions ===64=== Structural subscriptions and indirect bug subscriptions ===
194 65
195 >>> bug = filebug(target, 'test bug one')66 >>> bug = filebug(target, 'test bug one')
196 >>> indirect_subscribers = set(67 >>> indirect_subscribers = set(
197 ... subscriber.name for subscriber in bug.getIndirectSubscribers())68 ... subscriber.name for subscriber in bug.getIndirectSubscribers())
19869
=== modified file 'lib/lp/registry/tests/test_structuralsubscriptiontarget.py'
--- lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2009-12-05 18:37:28 +0000
+++ lib/lp/registry/tests/test_structuralsubscriptiontarget.py 2010-08-17 14:18:48 +0000
@@ -4,12 +4,14 @@
4"""Test harness for running tests against IStructuralsubscriptionTarget4"""Test harness for running tests against IStructuralsubscriptionTarget
5implementations.5implementations.
6"""6"""
7__metaclass__ = type
78
8import unittest9import unittest
910
10from zope.component import getUtility11from zope.component import getUtility
12from zope.security.interfaces import Unauthorized
13from zope.security.proxy import ProxyFactory, removeSecurityProxy
1114
12from canonical.launchpad.webapp.interfaces import ILaunchBag
13from lp.bugs.interfaces.bug import CreateBugParams15from lp.bugs.interfaces.bug import CreateBugParams
14from lp.registry.interfaces.distribution import IDistributionSet16from lp.registry.interfaces.distribution import IDistributionSet
15from lp.registry.interfaces.product import IProductSet17from lp.registry.interfaces.product import IProductSet
@@ -18,7 +20,234 @@
18 bugtarget_filebug)20 bugtarget_filebug)
19from canonical.launchpad.testing.systemdocs import (21from canonical.launchpad.testing.systemdocs import (
20 LayeredDocFileSuite, setUp, tearDown)22 LayeredDocFileSuite, setUp, tearDown)
23from canonical.launchpad.webapp.interfaces import ILaunchBag
24from canonical.launchpad.webapp.testing import verifyObject
21from canonical.testing import LaunchpadFunctionalLayer25from canonical.testing import LaunchpadFunctionalLayer
26from lp.testing import (
27 ANONYMOUS, login, login_person, TestCaseWithFactory)
28from lp.testing._login import login_celebrity
29from lp.registry.interfaces.structuralsubscription import (
30 DeleteSubscriptionError, IStructuralSubscriptionTarget,
31 UserCannotSubscribePerson)
32from lp.registry.model.structuralsubscription import StructuralSubscription
33
34
35class StructuralSubscriptionTestBase:
36
37 def setUp(self):
38 super(StructuralSubscriptionTestBase, self).setUp()
39 self.ordinary_subscriber = self.factory.makePerson()
40 self.bug_supervisor_subscriber = self.factory.makePerson()
41 self.team_owner = self.factory.makePerson()
42 self.team = self.factory.makeTeam(owner=self.team_owner)
43
44 def test_target_implements_structural_subscription_target(self):
45 self.assertTrue(verifyObject(IStructuralSubscriptionTarget,
46 self.target))
47
48 def test_anonymous_cannot_subscribe_anyone(self):
49 # only authenticated users can create structural subscriptions
50 login(ANONYMOUS)
51 self.assertRaises(Unauthorized, getattr, self.target,
52 'addBugSubscription')
53
54 def test_person_structural_subscription_by_other_person(self):
55 # a person can not subscribe someone else willy nilly
56 login_person(self.ordinary_subscriber)
57 self.assertRaises(UserCannotSubscribePerson,
58 self.target.addBugSubscription,
59 self.team_owner, self.ordinary_subscriber)
60
61 def test_team_structural_subscription_by_non_team_member(self):
62 # a person not related to a team cannot subscribe it
63 login_person(self.ordinary_subscriber)
64 self.assertRaises(UserCannotSubscribePerson,
65 self.target.addBugSubscription,
66 self.team, self.ordinary_subscriber)
67
68 def test_admin_can_subscribe_anyone(self):
69 # a launchpad admin can create a structural subscription for
70 # anyone
71 admin = login_celebrity('admin')
72 self.assertIsInstance(
73 self.target.addBugSubscription(self.ordinary_subscriber, admin),
74 StructuralSubscription)
75
76 def test_secondary_structural_subscription(self):
77 # creating a structural subscription a 2nd time returns the
78 # first structural subscription
79 login_person(self.bug_supervisor_subscriber)
80 subscription1 = self.target.addBugSubscription(
81 self.bug_supervisor_subscriber, self.bug_supervisor_subscriber)
82 subscription2 = self.target.addBugSubscription(
83 self.bug_supervisor_subscriber, self.bug_supervisor_subscriber)
84 self.assertIs(subscription1.id, subscription2.id)
85
86 def test_remove_structural_subscription(self):
87 # an unprivileged user cannot unsubscribe a team
88 login_person(self.ordinary_subscriber)
89 self.assertRaises(UserCannotSubscribePerson,
90 self.target.removeBugSubscription,
91 self.team, self.ordinary_subscriber)
92
93 def test_remove_nonexistant_structural_subscription(self):
94 # removing a nonexistant subscription raises a
95 # DeleteSubscriptionError
96 login_person(self.ordinary_subscriber)
97 self.assertRaises(DeleteSubscriptionError,
98 self.target.removeBugSubscription,
99 self.ordinary_subscriber, self.ordinary_subscriber)
100
101
102class UnrestrictedStructuralSubscription(StructuralSubscriptionTestBase):
103
104 def test_structural_subscription_by_ordinary_user(self):
105 # ordinary users can subscribe themselves
106 login_person(self.ordinary_subscriber)
107 self.assertIsInstance(
108 self.target.addBugSubscription(
109 self.ordinary_subscriber, self.ordinary_subscriber),
110 StructuralSubscription)
111
112 def test_remove_structural_subscription_by_ordinary_user(self):
113 # ordinary users can unsubscribe themselves
114 login_person(self.ordinary_subscriber)
115 self.assertIsInstance(
116 self.target.addBugSubscription(
117 self.ordinary_subscriber, self.ordinary_subscriber),
118 StructuralSubscription)
119 self.assertEqual(
120 self.target.removeBugSubscription(
121 self.ordinary_subscriber, self.ordinary_subscriber),
122 None)
123
124 def test_team_structural_subscription_by_team_owner(self):
125 # team owners can subscribe their team
126 login_person(self.team_owner)
127 self.assertIsInstance(
128 self.target.addBugSubscription(
129 self.team, self.team_owner),
130 StructuralSubscription)
131
132 def test_remove_team_structural_subscription_by_team_owner(self):
133 # team owners can unsubscribe their team
134 login_person(self.team_owner)
135 self.assertIsInstance(
136 self.target.addBugSubscription(
137 self.team, self.team_owner),
138 StructuralSubscription)
139 self.assertEqual(
140 self.target.removeBugSubscription(
141 self.team, self.team_owner),
142 None)
143
144
145class TestStructuralSubscriptionForDistro(StructuralSubscriptionTestBase,
146 TestCaseWithFactory):
147
148 layer = LaunchpadFunctionalLayer
149
150 def setUp(self):
151 super(TestStructuralSubscriptionForDistro, self).setUp()
152 self.target = self.factory.makeDistribution()
153 naked_distro = removeSecurityProxy(self.target)
154 naked_distro.bug_supervisor = self.bug_supervisor_subscriber
155
156 def test_distribution_subscription_by_ordinary_user(self):
157 # ordinary users can not subscribe themselves to a distribution
158 login_person(self.ordinary_subscriber)
159 self.assertRaises(UserCannotSubscribePerson,
160 self.target.addBugSubscription,
161 self.ordinary_subscriber, self.ordinary_subscriber)
162
163 def test_team_distribution_structural_subscription_by_team_owner(self):
164 # team owners cannot subscribe their team to a distribution
165 login_person(self.team_owner)
166 self.assertRaises(UserCannotSubscribePerson,
167 self.target.addBugSubscription,
168 self.team, self.team_owner)
169
170 def test_distribution_subscription_by_bug_supervisor(self):
171 # bug supervisor can subscribe themselves
172 login_person(self.bug_supervisor_subscriber)
173 self.assertIsInstance(
174 self.target.addBugSubscription(
175 self.bug_supervisor_subscriber,
176 self.bug_supervisor_subscriber),
177 StructuralSubscription)
178
179 def test_distribution_subscription_by_bug_supervisor_team(self):
180 # team admins can subscribe team if team is bug supervisor
181 removeSecurityProxy(self.target).bug_supervisor = self.team
182 login_person(self.team_owner)
183 self.assertIsInstance(
184 self.target.addBugSubscription(self.team, self.team_owner),
185 StructuralSubscription)
186
187 def test_distribution_unsubscription_by_bug_supervisor_team(self):
188 # team admins can unsubscribe team if team is bug supervisor
189 removeSecurityProxy(self.target).bug_supervisor = self.team
190 login_person(self.team_owner)
191 self.assertIsInstance(
192 self.target.addBugSubscription(self.team, self.team_owner),
193 StructuralSubscription)
194 self.assertEqual(
195 self.target.removeBugSubscription(self.team, self.team_owner),
196 None)
197
198 def test_distribution_subscription_without_bug_supervisor(self):
199 # for a distribution without a bug supervisor anyone can
200 # subscribe
201 removeSecurityProxy(self.target).bug_supervisor = None
202 login_person(self.ordinary_subscriber)
203 self.assertIsInstance(
204 self.target.addBugSubscription(
205 self.ordinary_subscriber, self.ordinary_subscriber),
206 StructuralSubscription)
207
208
209class TestStructuralSubscriptionForProduct(
210 UnrestrictedStructuralSubscription, TestCaseWithFactory):
211
212 layer = LaunchpadFunctionalLayer
213
214 def setUp(self):
215 super(TestStructuralSubscriptionForProduct, self).setUp()
216 self.target = self.factory.makeProduct()
217
218
219class TestStructuralSubscriptionForDistroSourcePackage(
220 UnrestrictedStructuralSubscription, TestCaseWithFactory):
221
222 layer = LaunchpadFunctionalLayer
223
224 def setUp(self):
225 super(TestStructuralSubscriptionForDistroSourcePackage, self).setUp()
226 self.target = self.factory.makeDistributionSourcePackage()
227 self.target = ProxyFactory(self.target)
228
229
230class TestStructuralSubscriptionForMilestone(
231 UnrestrictedStructuralSubscription, TestCaseWithFactory):
232
233 layer = LaunchpadFunctionalLayer
234
235 def setUp(self):
236 super(TestStructuralSubscriptionForMilestone, self).setUp()
237 self.target = self.factory.makeMilestone()
238 self.target = ProxyFactory(self.target)
239
240
241class TestStructuralSubscriptionForDistroSeries(
242 UnrestrictedStructuralSubscription, TestCaseWithFactory):
243
244 layer = LaunchpadFunctionalLayer
245
246 def setUp(self):
247 super(TestStructuralSubscriptionForDistroSeries, self).setUp()
248 self.target = self.factory.makeDistroSeries()
249 self.target = ProxyFactory(self.target)
250
22251
23def distributionSourcePackageSetUp(test):252def distributionSourcePackageSetUp(test):
24 setUp(test)253 setUp(test)
@@ -27,27 +256,32 @@
27 test.globs['other_target'] = ubuntu.getSourcePackage('pmount')256 test.globs['other_target'] = ubuntu.getSourcePackage('pmount')
28 test.globs['filebug'] = bugtarget_filebug257 test.globs['filebug'] = bugtarget_filebug
29258
259
30def productSetUp(test):260def productSetUp(test):
31 setUp(test)261 setUp(test)
32 test.globs['target'] = getUtility(IProductSet).getByName('firefox')262 test.globs['target'] = getUtility(IProductSet).getByName('firefox')
33 test.globs['filebug'] = bugtarget_filebug263 test.globs['filebug'] = bugtarget_filebug
34264
265
35def distributionSetUp(test):266def distributionSetUp(test):
36 setUp(test)267 setUp(test)
37 test.globs['target'] = getUtility(IDistributionSet).getByName('ubuntu')268 test.globs['target'] = getUtility(IDistributionSet).getByName('ubuntu')
38 test.globs['filebug'] = bugtarget_filebug269 test.globs['filebug'] = bugtarget_filebug
39270
271
40def milestone_filebug(milestone, summary, status=None):272def milestone_filebug(milestone, summary, status=None):
41 bug = bugtarget_filebug(milestone.target, summary, status=status)273 bug = bugtarget_filebug(milestone.target, summary, status=status)
42 bug.bugtasks[0].milestone = milestone274 bug.bugtasks[0].milestone = milestone
43 return bug275 return bug
44276
277
45def milestoneSetUp(test):278def milestoneSetUp(test):
46 setUp(test)279 setUp(test)
47 firefox = getUtility(IProductSet).getByName('firefox')280 firefox = getUtility(IProductSet).getByName('firefox')
48 test.globs['target'] = firefox.getMilestone('1.0')281 test.globs['target'] = firefox.getMilestone('1.0')
49 test.globs['filebug'] = milestone_filebug282 test.globs['filebug'] = milestone_filebug
50283
284
51def distroseries_sourcepackage_filebug(distroseries, summary, status=None):285def distroseries_sourcepackage_filebug(distroseries, summary, status=None):
52 params = CreateBugParams(286 params = CreateBugParams(
53 getUtility(ILaunchBag).user, summary, comment=summary, status=status)287 getUtility(ILaunchBag).user, summary, comment=summary, status=status)
@@ -60,15 +294,18 @@
60 nomination.approve(distroseries.distribution.owner)294 nomination.approve(distroseries.distribution.owner)
61 return bug295 return bug
62296
297
63def distroSeriesSourcePackageSetUp(test):298def distroSeriesSourcePackageSetUp(test):
64 setUp(test)299 setUp(test)
65 test.globs['target'] = (300 test.globs['target'] = (
66 getUtility(IDistributionSet).getByName('ubuntu').getSeries('hoary'))301 getUtility(IDistributionSet).getByName('ubuntu').getSeries('hoary'))
67 test.globs['filebug'] = distroseries_sourcepackage_filebug302 test.globs['filebug'] = distroseries_sourcepackage_filebug
68303
304
69def test_suite():305def test_suite():
70 """Return the `IStructuralSubscriptionTarget` TestSuite."""306 """Return the `IStructuralSubscriptionTarget` TestSuite."""
71 suite = unittest.TestSuite()307 suite = unittest.TestSuite()
308 suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
72309
73 setUpMethods = [310 setUpMethods = [
74 distributionSourcePackageSetUp,311 distributionSourcePackageSetUp,