Merge lp:~gary/launchpad/bug723999-2d into lp:launchpad

Proposed by Gary Poster
Status: Merged
Approved by: Gary Poster
Approved revision: no longer in the source branch.
Merged at revision: 12548
Proposed branch: lp:~gary/launchpad/bug723999-2d
Merge into: lp:launchpad
Prerequisite: lp:~gary/launchpad/bug723999-2c
Diff against target: 765 lines (+271/-317)
10 files modified
lib/lp/bugs/browser/bugsubscription.py (+1/-4)
lib/lp/bugs/configure.zcml (+1/-0)
lib/lp/bugs/interfaces/bug.py (+6/-0)
lib/lp/bugs/interfaces/bugtask.py (+0/-12)
lib/lp/bugs/model/bug.py (+4/-0)
lib/lp/bugs/model/bugtask.py (+1/-23)
lib/lp/bugs/model/structuralsubscription.py (+0/-28)
lib/lp/bugs/model/tests/test_bug.py (+34/-0)
lib/lp/bugs/model/tests/test_bugtask.py (+2/-248)
lib/lp/bugs/tests/test_structuralsubscription.py (+222/-2)
To merge this branch: bzr merge lp:~gary/launchpad/bug723999-2d
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+52478@code.launchpad.net

Commit message

[r=benji][no-qa] Clean up structural subcription code to eliminate duplicated code and remove code middle-men that emerged after my work on bug 723999.

Description of the change

This is the final clean-up branch from my work on bug723999, building most recently on ~gary/launchpad/bug723999-2c.

This branch is all about removing three methods on the bugtaskset utility (lib/lp/bugs/model/bugtask.py) that were simply operating as middlemen to functions in the structuralsubscription module.

- _getStructuralSubscriptionTargets was just a helper function that had stayed around because it was tested. Those tests are now repointed to look at the underlying get_structural_subscription_targets function, and moved from test_bugtask to test_structuralsubscription.

- At the end of the 2c branch, getStructuralSubscribers was no longer called by anything in LP. It was only around because useful tests looked at it. Those tests are now repointed and moved, in the same way as described for _getStructuralSubscriptionTargets. This also meant that I was able to remove get_structural_subscribers_for_bugtasks from structuralsubscription.py, because that function was really only a shim against get_structural_subscribers for the method to use. I wrote some new tests for the method, which also prove that the function does what we want (TestGetStructuralSubscriptionsForPerson).

- getAllStructuralSubscriptions was now only used by a browser view. It essentially wanted the structural subscriptions for a bug, for a person. I eliminated this bugtaskset method and added a getStructuralSubscriptionsForPerson to IBug. The implementation again is just a middleman, but because this is called by browser code, we want to be able to control who calls what from a security perspective, so I thought it made sense to make the browser code call a method off of a class rather than a function. This is arguable, but it made sense to me at the time.

That's pretty much it.

Thank you!

Gary

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

Looks good. This branch, like the others in this series, was cohesive
so that helped in reviewing it.

One small note: The import of StructuralSubscription in
lib/lp/bugs/model/bugtask.py could be on one line if you wanted.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/browser/bugsubscription.py'
2--- lib/lp/bugs/browser/bugsubscription.py 2011-03-04 11:00:04 +0000
3+++ lib/lp/bugs/browser/bugsubscription.py 2011-03-07 22:10:38 +0000
4@@ -19,7 +19,6 @@
5 from zope import formlib
6 from zope.app.form import CustomWidgetFactory
7 from zope.app.form.browser.itemswidgets import RadioWidget
8-from zope.component import getUtility
9 from zope.schema import Choice
10 from zope.schema.vocabulary import (
11 SimpleTerm,
12@@ -41,7 +40,6 @@
13 from lp.bugs.browser.bug import BugViewMixin
14 from lp.bugs.enum import BugNotificationLevel, HIDDEN_BUG_NOTIFICATION_LEVELS
15 from lp.bugs.interfaces.bugsubscription import IBugSubscription
16-from lp.bugs.interfaces.bugtask import IBugTaskSet
17 from lp.services import features
18 from lp.services.propertycache import cachedproperty
19
20@@ -559,5 +557,4 @@
21
22 @property
23 def structural_subscriptions(self):
24- return getUtility(IBugTaskSet).getAllStructuralSubscriptions(
25- self.context.bug.bugtasks, self.user)
26+ return self.context.bug.getStructuralSubscriptionsForPerson(self.user)
27
28=== modified file 'lib/lp/bugs/configure.zcml'
29--- lib/lp/bugs/configure.zcml 2011-03-07 17:54:44 +0000
30+++ lib/lp/bugs/configure.zcml 2011-03-07 22:10:38 +0000
31@@ -747,6 +747,7 @@
32 getSubscriptionsFromDuplicates
33 getSubscribersForPerson
34 getSubscriptionForPerson
35+ getStructuralSubscriptionsForPerson
36 getSubscriptionInfo
37 indexed_messages
38 _indexed_messages
39
40=== modified file 'lib/lp/bugs/interfaces/bug.py'
41--- lib/lp/bugs/interfaces/bug.py 2011-03-07 22:10:32 +0000
42+++ lib/lp/bugs/interfaces/bug.py 2011-03-07 22:10:38 +0000
43@@ -533,6 +533,12 @@
44 If no such `BugSubscription` exists, return None.
45 """
46
47+ def getStructuralSubscriptionsForPerson(person):
48+ """Return the `StructuralSubscription`s for a `Person` to this `Bug`.
49+
50+ This disregards filters.
51+ """
52+
53 def getSubscriptionInfo(level):
54 """Return a `BugSubscriptionInfo` at the given `level`.
55
56
57=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
58--- lib/lp/bugs/interfaces/bugtask.py 2011-03-03 18:58:49 +0000
59+++ lib/lp/bugs/interfaces/bugtask.py 2011-03-07 22:10:38 +0000
60@@ -1597,18 +1597,6 @@
61 def getOpenBugTasksPerProduct(user, products):
62 """Return open bugtask count for multiple products."""
63
64- def getAllStructuralSubscriptions(bugtasks):
65- """Return all potential structural subscriptions for the bugtasks.
66-
67- This method ignores subscription filters.
68- """
69-
70- def getStructuralSubscribers(bugtasks, recipients=None, level=None):
71- """Return `IPerson`s subscribed to the given bug tasks.
72-
73- This takes into account bug subscription filters.
74- """
75-
76 def getPrecachedNonConjoinedBugTasks(user, milestone):
77 """List of non-conjoined bugtasks targeted to the milestone.
78
79
80=== modified file 'lib/lp/bugs/model/bug.py'
81--- lib/lp/bugs/model/bug.py 2011-03-07 22:10:32 +0000
82+++ lib/lp/bugs/model/bug.py 2011-03-07 22:10:38 +0000
83@@ -982,6 +982,10 @@
84 BugSubscription.person == person,
85 BugSubscription.bug == self).one()
86
87+ def getStructuralSubscriptionsForPerson(self, person):
88+ """See `IBug`."""
89+ return get_all_structural_subscriptions(self.bugtasks, person)
90+
91 def getAlsoNotifiedSubscribers(self, recipients=None, level=None):
92 """See `IBug`.
93
94
95=== modified file 'lib/lp/bugs/model/bugtask.py'
96--- lib/lp/bugs/model/bugtask.py 2011-03-07 07:27:56 +0000
97+++ lib/lp/bugs/model/bugtask.py 2011-03-07 22:10:38 +0000
98@@ -131,12 +131,7 @@
99 )
100 from lp.bugs.model.bugnomination import BugNomination
101 from lp.bugs.model.bugsubscription import BugSubscription
102-from lp.bugs.model.structuralsubscription import (
103- get_all_structural_subscriptions,
104- get_structural_subscribers_for_bugtasks,
105- get_structural_subscription_targets,
106- StructuralSubscription,
107- )
108+from lp.bugs.model.structuralsubscription import StructuralSubscription
109 from lp.registry.interfaces.distribution import (
110 IDistribution,
111 IDistributionSet,
112@@ -3107,20 +3102,3 @@
113 counts.append(package_counts)
114
115 return counts
116-
117- def _getStructuralSubscriptionTargets(self, bugtasks):
118- """Return (bugtask, target) pairs for each target of the bugtasks.
119-
120- Each bugtask may be responsible theoretically for 0 or more targets.
121- In practice, each generates one, two or three.
122- """
123- return get_structural_subscription_targets(bugtasks)
124-
125- def getAllStructuralSubscriptions(self, bugtasks, recipient=None):
126- """See `IBugTaskSet`."""
127- return get_all_structural_subscriptions(bugtasks, recipient)
128-
129- def getStructuralSubscribers(self, bugtasks, recipients=None, level=None):
130- """See `IBugTaskSet`."""
131- return get_structural_subscribers_for_bugtasks(
132- bugtasks, recipients, level)
133
134=== modified file 'lib/lp/bugs/model/structuralsubscription.py'
135--- lib/lp/bugs/model/structuralsubscription.py 2011-03-07 22:10:32 +0000
136+++ lib/lp/bugs/model/structuralsubscription.py 2011-03-07 22:10:38 +0000
137@@ -5,7 +5,6 @@
138 __all__ = [
139 'get_all_structural_subscriptions',
140 'get_structural_subscribers',
141- 'get_structural_subscribers_for_bugtasks',
142 'get_structural_subscription_targets',
143 'StructuralSubscription',
144 'StructuralSubscriptionTargetMixin',
145@@ -608,33 +607,6 @@
146 return subscribers
147
148
149-def get_structural_subscribers_for_bugtasks(bugtasks,
150- recipients=None,
151- level=None):
152- """Return subscribers for structural filters for the bugtasks at "level".
153-
154- :param bugtasks: an iterable of bugtasks. Should be a single bugtask, or
155- all of the bugtasks from one bug.
156- :param recipients: a BugNotificationRecipients object or None.
157- Populates if given.
158- :param level: a level from lp.bugs.enum.BugNotificationLevel.
159-
160- Excludes structural subscriptions for people who are directly subscribed
161- to the bug."""
162- bugtasks = list(bugtasks)
163- if not bugtasks:
164- return EmptyResultSet()
165- if len(bugtasks) == 1:
166- target = bugtasks[0]
167- else:
168- target = bugtasks[0].bug
169- if target.bugtasks != bugtasks:
170- raise NotImplementedError(
171- 'bugtasks must be one, or the full set of bugtasks from one '
172- 'bug')
173- return get_structural_subscribers(target, recipients, level)
174-
175-
176 class ArrayAgg(NamedFunc):
177 "Aggregate values (within a GROUP BY) into an array."
178 __slots__ = ()
179
180=== modified file 'lib/lp/bugs/model/tests/test_bug.py'
181--- lib/lp/bugs/model/tests/test_bug.py 2011-02-18 12:51:40 +0000
182+++ lib/lp/bugs/model/tests/test_bug.py 2011-03-07 22:10:38 +0000
183@@ -12,6 +12,7 @@
184 person_logged_in,
185 TestCaseWithFactory,
186 )
187+from lp.testing.factory import is_security_proxied_or_harmless
188
189
190 class TestBug(TestCaseWithFactory):
191@@ -247,3 +248,36 @@
192 with person_logged_in(bug.owner):
193 info = bug.getSubscriptionInfo(BugNotificationLevel.METADATA)
194 self.assertEqual(BugNotificationLevel.METADATA, info.level)
195+
196+
197+class TestGetStructuralSubscriptionsForPerson(TestCaseWithFactory):
198+
199+ layer = DatabaseFunctionalLayer
200+
201+ def getStructuralSubscriptionsForPerson(self, bug, recipient):
202+ # Call bug.getStructuralSubscriptionsForPerson() and check that the
203+ # result is security proxied.
204+ result = bug.getStructuralSubscriptionsForPerson(recipient)
205+ self.assertTrue(is_security_proxied_or_harmless(result))
206+ return result
207+
208+ def setUp(self):
209+ super(TestGetStructuralSubscriptionsForPerson, self).setUp()
210+ self.subscriber = self.factory.makePerson()
211+ login_person(self.subscriber)
212+ self.product = self.factory.makeProduct()
213+ self.milestone = self.factory.makeMilestone(product=self.product)
214+ self.bug = self.factory.makeBug(
215+ product=self.product, milestone=self.milestone)
216+
217+ def test_no_subscriptions(self):
218+ subscriptions = self.getStructuralSubscriptionsForPerson(
219+ self.bug, self.subscriber)
220+ self.assertEqual([], list(subscriptions))
221+
222+ def test_one_subscription(self):
223+ sub = self.product.addBugSubscription(
224+ self.subscriber, self.subscriber)
225+ subscriptions = self.getStructuralSubscriptionsForPerson(
226+ self.bug, self.subscriber)
227+ self.assertEqual([sub], list(subscriptions))
228
229=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
230--- lib/lp/bugs/model/tests/test_bugtask.py 2011-03-03 02:14:14 +0000
231+++ lib/lp/bugs/model/tests/test_bugtask.py 2011-03-07 22:10:38 +0000
232@@ -8,17 +8,9 @@
233 import unittest
234
235 from lazr.lifecycle.snapshot import Snapshot
236-from storm.store import (
237- EmptyResultSet,
238- ResultSet,
239- )
240-from testtools.matchers import (
241- Equals,
242- StartsWith,
243- )
244+from testtools.matchers import Equals
245 from zope.component import getUtility
246 from zope.interface import providedBy
247-from zope.security.proxy import removeSecurityProxy
248
249 from canonical.database.sqlbase import flush_database_updates
250 from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
251@@ -32,7 +24,6 @@
252 LaunchpadZopelessLayer,
253 )
254 from lp.app.enums import ServiceUsage
255-from lp.bugs.enum import BugNotificationLevel
256 from lp.bugs.interfaces.bug import IBugSet
257 from lp.bugs.interfaces.bugtarget import IBugTarget
258 from lp.bugs.interfaces.bugtask import (
259@@ -45,7 +36,6 @@
260 UNRESOLVED_BUGTASK_STATUSES,
261 )
262 from lp.bugs.interfaces.bugwatch import IBugWatchSet
263-from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
264 from lp.bugs.model.bugtask import build_tag_search_clause
265 from lp.bugs.tests.bug import (
266 create_old_bug,
267@@ -72,10 +62,7 @@
268 TestCase,
269 TestCaseWithFactory,
270 )
271-from lp.testing.factory import (
272- is_security_proxied_or_harmless,
273- LaunchpadObjectFactory,
274- )
275+from lp.testing.factory import LaunchpadObjectFactory
276 from lp.testing.matchers import HasQueryCount
277
278
279@@ -1346,239 +1333,6 @@
280 self.assertNotIn(BugTaskStatus.UNKNOWN, UNRESOLVED_BUGTASK_STATUSES)
281
282
283-class TestGetStructuralSubscriptionTargets(TestCaseWithFactory):
284- # This tests a private method because it has some subtleties that are
285- # nice to test in isolation.
286-
287- layer = DatabaseFunctionalLayer
288-
289- def getStructuralSubscriptionTargets(self, bugtasks):
290- unwrapped = removeSecurityProxy(getUtility(IBugTaskSet))
291- return unwrapped._getStructuralSubscriptionTargets(bugtasks)
292-
293- def test_product_target(self):
294- product = self.factory.makeProduct()
295- bug = self.factory.makeBug(product=product)
296- bugtask = bug.bugtasks[0]
297- result = self.getStructuralSubscriptionTargets(bug.bugtasks)
298- self.assertEqual(list(result), [(bugtask, product)])
299-
300- def test_milestone_target(self):
301- actor = self.factory.makePerson()
302- login_person(actor)
303- product = self.factory.makeProduct()
304- milestone = self.factory.makeMilestone(product=product)
305- bug = self.factory.makeBug(product=product, milestone=milestone)
306- bugtask = bug.bugtasks[0]
307- result = self.getStructuralSubscriptionTargets(bug.bugtasks)
308- self.assertEqual(set(result), set(
309- ((bugtask, product), (bugtask, milestone))))
310-
311- def test_sourcepackage_target(self):
312- actor = self.factory.makePerson()
313- login_person(actor)
314- distroseries = self.factory.makeDistroSeries()
315- sourcepackage = self.factory.makeSourcePackage(
316- distroseries=distroseries)
317- product = self.factory.makeProduct()
318- bug = self.factory.makeBug(product=product)
319- bug.addTask(actor, sourcepackage)
320- product_bugtask = bug.bugtasks[0]
321- sourcepackage_bugtask = bug.bugtasks[1]
322- result = self.getStructuralSubscriptionTargets(bug.bugtasks)
323- self.assertEqual(set(result), set(
324- ((product_bugtask, product),
325- (sourcepackage_bugtask, distroseries))))
326-
327- def test_distribution_source_package_target(self):
328- actor = self.factory.makePerson()
329- login_person(actor)
330- distribution = self.factory.makeDistribution()
331- dist_sourcepackage = self.factory.makeDistributionSourcePackage(
332- distribution=distribution)
333- product = self.factory.makeProduct()
334- bug = self.factory.makeBug(product=product)
335- bug.addTask(actor, dist_sourcepackage)
336- product_bugtask = bug.bugtasks[0]
337- dist_sourcepackage_bugtask = bug.bugtasks[1]
338- result = self.getStructuralSubscriptionTargets(bug.bugtasks)
339- self.assertEqual(set(result), set(
340- ((product_bugtask, product),
341- (dist_sourcepackage_bugtask, dist_sourcepackage),
342- (dist_sourcepackage_bugtask, distribution))))
343-
344-
345-class TestGetAllStructuralSubscriptions(TestCaseWithFactory):
346-
347- layer = DatabaseFunctionalLayer
348-
349- def getAllStructuralSubscriptions(self, bugtasks, recipient):
350- # Call IBugTaskSet.getAllStructuralSubscriptions() and check that the
351- # result is security proxied.
352- result = getUtility(IBugTaskSet).getAllStructuralSubscriptions(
353- bugtasks, recipient)
354- self.assertTrue(is_security_proxied_or_harmless(result))
355- return result
356-
357- def setUp(self):
358- super(TestGetAllStructuralSubscriptions, self).setUp()
359- self.subscriber = self.factory.makePerson()
360- login_person(self.subscriber)
361- self.product = self.factory.makeProduct()
362- self.milestone = self.factory.makeMilestone(product=self.product)
363- self.bug = self.factory.makeBug(
364- product=self.product, milestone=self.milestone)
365-
366- def test_no_subscriptions(self):
367- subscriptions = self.getAllStructuralSubscriptions(
368- self.bug.bugtasks, self.subscriber)
369- self.assertEqual([], list(subscriptions))
370-
371- def test_one_subscription(self):
372- sub = self.product.addBugSubscription(
373- self.subscriber, self.subscriber)
374- subscriptions = self.getAllStructuralSubscriptions(
375- self.bug.bugtasks, self.subscriber)
376- self.assertEqual([sub], list(subscriptions))
377-
378- def test_two_subscriptions(self):
379- sub1 = self.product.addBugSubscription(
380- self.subscriber, self.subscriber)
381- sub2 = self.milestone.addBugSubscription(
382- self.subscriber, self.subscriber)
383- subscriptions = self.getAllStructuralSubscriptions(
384- self.bug.bugtasks, self.subscriber)
385- self.assertEqual(set([sub1, sub2]), set(subscriptions))
386-
387- def test_two_bugtasks_one_subscription(self):
388- sub = self.product.addBugSubscription(
389- self.subscriber, self.subscriber)
390- product2 = self.factory.makeProduct()
391- self.bug.addTask(self.subscriber, product2)
392- subscriptions = self.getAllStructuralSubscriptions(
393- self.bug.bugtasks, self.subscriber)
394- self.assertEqual([sub], list(subscriptions))
395-
396- def test_two_bugtasks_two_subscriptions(self):
397- sub1 = self.product.addBugSubscription(
398- self.subscriber, self.subscriber)
399- product2 = self.factory.makeProduct()
400- self.bug.addTask(self.subscriber, product2)
401- sub2 = product2.addBugSubscription(
402- self.subscriber, self.subscriber)
403- subscriptions = self.getAllStructuralSubscriptions(
404- self.bug.bugtasks, self.subscriber)
405- self.assertEqual(set([sub1, sub2]), set(subscriptions))
406-
407- def test_ignore_other_subscriptions(self):
408- sub1 = self.product.addBugSubscription(
409- self.subscriber, self.subscriber)
410- another_subscriber = self.factory.makePerson()
411- login_person(another_subscriber)
412- sub2 = self.product.addBugSubscription(
413- another_subscriber, another_subscriber)
414- subscriptions = self.getAllStructuralSubscriptions(
415- self.bug.bugtasks, self.subscriber)
416- self.assertEqual([sub1], list(subscriptions))
417- subscriptions = self.getAllStructuralSubscriptions(
418- self.bug.bugtasks, another_subscriber)
419- self.assertEqual([sub2], list(subscriptions))
420-
421-
422-class TestGetStructuralSubscribers(TestCaseWithFactory):
423-
424- layer = DatabaseFunctionalLayer
425-
426- def make_product_with_bug(self):
427- product = self.factory.makeProduct()
428- bug = self.factory.makeBug(product=product)
429- return product, bug
430-
431- def getStructuralSubscribers(self, bugtasks, *args, **kwargs):
432- # Call IBugTaskSet.getStructuralSubscribers() and check that the
433- # result is security proxied.
434- result = getUtility(IBugTaskSet).getStructuralSubscribers(
435- bugtasks, *args, **kwargs)
436- self.assertTrue(is_security_proxied_or_harmless(result))
437- return result
438-
439- def test_getStructuralSubscribers_no_subscribers(self):
440- # If there are no subscribers for any of the bug's targets then no
441- # subscribers will be returned by getStructuralSubscribers().
442- product, bug = self.make_product_with_bug()
443- subscribers = self.getStructuralSubscribers(bug.bugtasks)
444- self.assertIsInstance(subscribers, (ResultSet, EmptyResultSet))
445- self.assertEqual([], list(subscribers))
446-
447- def test_getStructuralSubscribers_single_target(self):
448- # Subscribers for any of the bug's targets are returned.
449- subscriber = self.factory.makePerson()
450- login_person(subscriber)
451- product, bug = self.make_product_with_bug()
452- product.addBugSubscription(subscriber, subscriber)
453- self.assertEqual(
454- [subscriber], list(
455- self.getStructuralSubscribers(bug.bugtasks)))
456-
457- def test_getStructuralSubscribers_multiple_targets(self):
458- # Subscribers for any of the bug's targets are returned.
459- actor = self.factory.makePerson()
460- login_person(actor)
461-
462- subscriber1 = self.factory.makePerson()
463- subscriber2 = self.factory.makePerson()
464-
465- product1 = self.factory.makeProduct(owner=actor)
466- product1.addBugSubscription(subscriber1, subscriber1)
467- product2 = self.factory.makeProduct(owner=actor)
468- product2.addBugSubscription(subscriber2, subscriber2)
469-
470- bug = self.factory.makeBug(product=product1)
471- bug.addTask(actor, product2)
472-
473- subscribers = self.getStructuralSubscribers(bug.bugtasks)
474- self.assertIsInstance(subscribers, ResultSet)
475- self.assertEqual(set([subscriber1, subscriber2]), set(subscribers))
476-
477- def test_getStructuralSubscribers_recipients(self):
478- # If provided, getStructuralSubscribers() calls the appropriate
479- # methods on a BugNotificationRecipients object.
480- subscriber = self.factory.makePerson()
481- login_person(subscriber)
482- product, bug = self.make_product_with_bug()
483- product.addBugSubscription(subscriber, subscriber)
484- recipients = BugNotificationRecipients()
485- subscribers = self.getStructuralSubscribers(
486- bug.bugtasks, recipients=recipients)
487- # The return value is a list only when populating recipients.
488- self.assertIsInstance(subscribers, list)
489- self.assertEqual([subscriber], recipients.getRecipients())
490- reason, header = recipients.getReason(subscriber)
491- self.assertThat(
492- reason, StartsWith(
493- u"You received this bug notification because "
494- u"you are subscribed to "))
495- self.assertThat(header, StartsWith(u"Subscriber "))
496-
497- def test_getStructuralSubscribers_level(self):
498- # getStructuralSubscribers() respects the given level.
499- subscriber = self.factory.makePerson()
500- login_person(subscriber)
501- product, bug = self.make_product_with_bug()
502- subscription = product.addBugSubscription(subscriber, subscriber)
503- filter = subscription.bug_filters.one()
504- filter.bug_notification_level = BugNotificationLevel.METADATA
505- self.assertEqual(
506- [subscriber], list(
507- self.getStructuralSubscribers(
508- bug.bugtasks, level=BugNotificationLevel.METADATA)))
509- filter.bug_notification_level = BugNotificationLevel.METADATA
510- self.assertEqual(
511- [], list(
512- self.getStructuralSubscribers(
513- bug.bugtasks, level=BugNotificationLevel.COMMENTS)))
514-
515-
516 def test_suite():
517 suite = unittest.TestSuite()
518 suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
519
520=== modified file 'lib/lp/bugs/tests/test_structuralsubscription.py'
521--- lib/lp/bugs/tests/test_structuralsubscription.py 2011-03-07 22:10:32 +0000
522+++ lib/lp/bugs/tests/test_structuralsubscription.py 2011-03-07 22:10:38 +0000
523@@ -5,7 +5,12 @@
524
525 __metaclass__ = type
526
527-from storm.store import Store
528+from storm.store import (
529+ EmptyResultSet,
530+ ResultSet,
531+ Store,
532+ )
533+from testtools.matchers import StartsWith
534 from zope.security.interfaces import Unauthorized
535
536 from canonical.testing.layers import (
537@@ -17,8 +22,13 @@
538 BugTaskImportance,
539 BugTaskStatus,
540 )
541+from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
542 from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilter
543-from lp.bugs.model.structuralsubscription import get_structural_subscribers
544+from lp.bugs.model.structuralsubscription import (
545+ get_all_structural_subscriptions,
546+ get_structural_subscribers,
547+ get_structural_subscription_targets,
548+ )
549 from lp.testing import (
550 anonymous_logged_in,
551 login_person,
552@@ -444,3 +454,213 @@
553
554 def makeTarget(self):
555 return self.factory.makeProductSeries()
556+
557+
558+class TestGetStructuralSubscriptionTargets(TestCaseWithFactory):
559+
560+ layer = DatabaseFunctionalLayer
561+
562+ def test_product_target(self):
563+ product = self.factory.makeProduct()
564+ bug = self.factory.makeBug(product=product)
565+ bugtask = bug.bugtasks[0]
566+ result = get_structural_subscription_targets(bug.bugtasks)
567+ self.assertEqual(list(result), [(bugtask, product)])
568+
569+ def test_milestone_target(self):
570+ actor = self.factory.makePerson()
571+ login_person(actor)
572+ product = self.factory.makeProduct()
573+ milestone = self.factory.makeMilestone(product=product)
574+ bug = self.factory.makeBug(product=product, milestone=milestone)
575+ bugtask = bug.bugtasks[0]
576+ result = get_structural_subscription_targets(bug.bugtasks)
577+ self.assertEqual(set(result), set(
578+ ((bugtask, product), (bugtask, milestone))))
579+
580+ def test_sourcepackage_target(self):
581+ actor = self.factory.makePerson()
582+ login_person(actor)
583+ distroseries = self.factory.makeDistroSeries()
584+ sourcepackage = self.factory.makeSourcePackage(
585+ distroseries=distroseries)
586+ product = self.factory.makeProduct()
587+ bug = self.factory.makeBug(product=product)
588+ bug.addTask(actor, sourcepackage)
589+ product_bugtask = bug.bugtasks[0]
590+ sourcepackage_bugtask = bug.bugtasks[1]
591+ result = get_structural_subscription_targets(bug.bugtasks)
592+ self.assertEqual(set(result), set(
593+ ((product_bugtask, product),
594+ (sourcepackage_bugtask, distroseries))))
595+
596+ def test_distribution_source_package_target(self):
597+ actor = self.factory.makePerson()
598+ login_person(actor)
599+ distribution = self.factory.makeDistribution()
600+ dist_sourcepackage = self.factory.makeDistributionSourcePackage(
601+ distribution=distribution)
602+ product = self.factory.makeProduct()
603+ bug = self.factory.makeBug(product=product)
604+ bug.addTask(actor, dist_sourcepackage)
605+ product_bugtask = bug.bugtasks[0]
606+ dist_sourcepackage_bugtask = bug.bugtasks[1]
607+ result = get_structural_subscription_targets(bug.bugtasks)
608+ self.assertEqual(set(result), set(
609+ ((product_bugtask, product),
610+ (dist_sourcepackage_bugtask, dist_sourcepackage),
611+ (dist_sourcepackage_bugtask, distribution))))
612+
613+
614+class TestGetAllStructuralSubscriptions(TestCaseWithFactory):
615+
616+ layer = DatabaseFunctionalLayer
617+
618+ def setUp(self):
619+ super(TestGetAllStructuralSubscriptions, self).setUp()
620+ self.subscriber = self.factory.makePerson()
621+ login_person(self.subscriber)
622+ self.product = self.factory.makeProduct()
623+ self.milestone = self.factory.makeMilestone(product=self.product)
624+ self.bug = self.factory.makeBug(
625+ product=self.product, milestone=self.milestone)
626+
627+ def test_no_subscriptions(self):
628+ subscriptions = get_all_structural_subscriptions(
629+ self.bug.bugtasks, self.subscriber)
630+ self.assertEqual([], list(subscriptions))
631+
632+ def test_one_subscription(self):
633+ sub = self.product.addBugSubscription(
634+ self.subscriber, self.subscriber)
635+ subscriptions = get_all_structural_subscriptions(
636+ self.bug.bugtasks, self.subscriber)
637+ self.assertEqual([sub], list(subscriptions))
638+
639+ def test_two_subscriptions(self):
640+ sub1 = self.product.addBugSubscription(
641+ self.subscriber, self.subscriber)
642+ sub2 = self.milestone.addBugSubscription(
643+ self.subscriber, self.subscriber)
644+ subscriptions = get_all_structural_subscriptions(
645+ self.bug.bugtasks, self.subscriber)
646+ self.assertEqual(set([sub1, sub2]), set(subscriptions))
647+
648+ def test_two_bugtasks_one_subscription(self):
649+ sub = self.product.addBugSubscription(
650+ self.subscriber, self.subscriber)
651+ product2 = self.factory.makeProduct()
652+ self.bug.addTask(self.subscriber, product2)
653+ subscriptions = get_all_structural_subscriptions(
654+ self.bug.bugtasks, self.subscriber)
655+ self.assertEqual([sub], list(subscriptions))
656+
657+ def test_two_bugtasks_two_subscriptions(self):
658+ sub1 = self.product.addBugSubscription(
659+ self.subscriber, self.subscriber)
660+ product2 = self.factory.makeProduct()
661+ self.bug.addTask(self.subscriber, product2)
662+ sub2 = product2.addBugSubscription(
663+ self.subscriber, self.subscriber)
664+ subscriptions = get_all_structural_subscriptions(
665+ self.bug.bugtasks, self.subscriber)
666+ self.assertEqual(set([sub1, sub2]), set(subscriptions))
667+
668+ def test_ignore_other_subscriptions(self):
669+ sub1 = self.product.addBugSubscription(
670+ self.subscriber, self.subscriber)
671+ another_subscriber = self.factory.makePerson()
672+ login_person(another_subscriber)
673+ sub2 = self.product.addBugSubscription(
674+ another_subscriber, another_subscriber)
675+ subscriptions = get_all_structural_subscriptions(
676+ self.bug.bugtasks, self.subscriber)
677+ self.assertEqual([sub1], list(subscriptions))
678+ subscriptions = get_all_structural_subscriptions(
679+ self.bug.bugtasks, another_subscriber)
680+ self.assertEqual([sub2], list(subscriptions))
681+
682+
683+class TestGetStructuralSubscribers(TestCaseWithFactory):
684+
685+ layer = DatabaseFunctionalLayer
686+
687+ def make_product_with_bug(self):
688+ product = self.factory.makeProduct()
689+ bug = self.factory.makeBug(product=product)
690+ return product, bug
691+
692+ def test_getStructuralSubscribers_no_subscribers(self):
693+ # If there are no subscribers for any of the bug's targets then no
694+ # subscribers will be returned by get_structural_subscribers().
695+ product, bug = self.make_product_with_bug()
696+ subscribers = get_structural_subscribers(bug, None, None, None)
697+ self.assertIsInstance(subscribers, (ResultSet, EmptyResultSet))
698+ self.assertEqual([], list(subscribers))
699+
700+ def test_getStructuralSubscribers_single_target(self):
701+ # Subscribers for any of the bug's targets are returned.
702+ subscriber = self.factory.makePerson()
703+ login_person(subscriber)
704+ product, bug = self.make_product_with_bug()
705+ product.addBugSubscription(subscriber, subscriber)
706+ self.assertEqual(
707+ [subscriber], list(
708+ get_structural_subscribers(bug, None, None, None)))
709+
710+ def test_getStructuralSubscribers_multiple_targets(self):
711+ # Subscribers for any of the bug's targets are returned.
712+ actor = self.factory.makePerson()
713+ login_person(actor)
714+
715+ subscriber1 = self.factory.makePerson()
716+ subscriber2 = self.factory.makePerson()
717+
718+ product1 = self.factory.makeProduct(owner=actor)
719+ product1.addBugSubscription(subscriber1, subscriber1)
720+ product2 = self.factory.makeProduct(owner=actor)
721+ product2.addBugSubscription(subscriber2, subscriber2)
722+
723+ bug = self.factory.makeBug(product=product1)
724+ bug.addTask(actor, product2)
725+
726+ subscribers = get_structural_subscribers(bug, None, None, None)
727+ self.assertIsInstance(subscribers, ResultSet)
728+ self.assertEqual(set([subscriber1, subscriber2]), set(subscribers))
729+
730+ def test_getStructuralSubscribers_recipients(self):
731+ # If provided, get_structural_subscribers() calls the appropriate
732+ # methods on a BugNotificationRecipients object.
733+ subscriber = self.factory.makePerson()
734+ login_person(subscriber)
735+ product, bug = self.make_product_with_bug()
736+ product.addBugSubscription(subscriber, subscriber)
737+ recipients = BugNotificationRecipients()
738+ subscribers = get_structural_subscribers(bug, recipients, None, None)
739+ # The return value is a list only when populating recipients.
740+ self.assertIsInstance(subscribers, list)
741+ self.assertEqual([subscriber], recipients.getRecipients())
742+ reason, header = recipients.getReason(subscriber)
743+ self.assertThat(
744+ reason, StartsWith(
745+ u"You received this bug notification because "
746+ u"you are subscribed to "))
747+ self.assertThat(header, StartsWith(u"Subscriber "))
748+
749+ def test_getStructuralSubscribers_level(self):
750+ # get_structural_subscribers() respects the given level.
751+ subscriber = self.factory.makePerson()
752+ login_person(subscriber)
753+ product, bug = self.make_product_with_bug()
754+ subscription = product.addBugSubscription(subscriber, subscriber)
755+ filter = subscription.bug_filters.one()
756+ filter.bug_notification_level = BugNotificationLevel.METADATA
757+ self.assertEqual(
758+ [subscriber], list(
759+ get_structural_subscribers(
760+ bug, None, BugNotificationLevel.METADATA, None)))
761+ filter.bug_notification_level = BugNotificationLevel.METADATA
762+ self.assertEqual(
763+ [], list(
764+ get_structural_subscribers(
765+ bug, None, BugNotificationLevel.COMMENTS, None)))