Merge lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 into lp:launchpad/db-devel
- ditch-get-bug-notifications-recipients-bug-659085
- Merge into db-devel
Status: | Rejected |
---|---|
Rejected by: | Gavin Panella |
Proposed branch: | lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 |
Merge into: | lp:launchpad/db-devel |
Prerequisite: | lp:~allenap/launchpad/wire-up-filter-subs-bug-655567 |
Diff against target: |
2497 lines (+1153/-1028) 19 files modified
lib/lp/bugs/configure.zcml (+0/-1) lib/lp/bugs/doc/bugtask-search.txt (+1/-1) lib/lp/bugs/interfaces/bug.py (+0/-6) lib/lp/bugs/interfaces/bugtask.py (+6/-0) lib/lp/bugs/model/bug.py (+5/-59) lib/lp/bugs/model/bugtask.py (+61/-1) lib/lp/bugs/model/tests/test_bug.py (+0/-85) lib/lp/bugs/model/tests/test_bugtask.py (+442/-2) lib/lp/bugs/model/tests/test_bugtask_status.py (+356/-0) lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt (+5/-5) lib/lp/bugs/subscribers/bug.py (+7/-12) lib/lp/bugs/tests/test_bugtask_0.py (+0/-36) lib/lp/bugs/tests/test_bugtask_1.py (+0/-345) lib/lp/bugs/tests/test_bugtask_status.py (+0/-356) lib/lp/code/interfaces/branchmergequeue.py (+115/-0) lib/lp/code/model/tests/test_branchmergequeue.py (+155/-0) lib/lp/registry/doc/structural-subscriptions.txt (+0/-87) lib/lp/registry/interfaces/structuralsubscription.py (+0/-14) lib/lp/registry/model/structuralsubscription.py (+0/-18) |
To merge this branch: | bzr merge lp:~allenap/launchpad/ditch-get-bug-notifications-recipients-bug-659085 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Graham Binns (community) | code | Approve | |
Review via email: mp+38325@code.launchpad.net |
Commit message
Remove getBugNotificat
Description of the change
This is a big branch, but fear not, most of it is moving code and
deletions. I shall guide you though the dark forest...
- tests/test_
tests/
tests/
automated tree migration.
- test_bugtask_0.py contained one doctest-
rewritten as a unittest (it's *very* short). It's still called
test_
- tests/test_
into model/tests.
- Bug.getStructur
accepts a list of bugtasks instead of getting them from
self.bugtasks, and gets the store to use with IStore(Person) rather
than Store.of(self).
- TestBugStructur
to model/tests/
TestGetStruct
- A couple of functions have been changed to use
BugTaskSet.
- getBugNotificat
associated tests.
I'm sorry it got a bit long. The dependencies of each change cascaded
somewhat.
Graham Binns (gmb) : | # |
Preview Diff
1 | === modified file 'lib/lp/bugs/configure.zcml' |
2 | --- lib/lp/bugs/configure.zcml 2010-10-22 18:48:58 +0000 |
3 | +++ lib/lp/bugs/configure.zcml 2010-10-25 11:13:20 +0000 |
4 | @@ -708,7 +708,6 @@ |
5 | getSubscribersForPerson |
6 | indexed_messages |
7 | getAlsoNotifiedSubscribers |
8 | - getStructuralSubscribers |
9 | getBugWatch |
10 | canBeNominatedFor |
11 | getNominationFor |
12 | |
13 | === modified file 'lib/lp/bugs/doc/bugtask-search.txt' |
14 | --- lib/lp/bugs/doc/bugtask-search.txt 2010-10-19 18:44:31 +0000 |
15 | +++ lib/lp/bugs/doc/bugtask-search.txt 2010-10-25 11:13:20 +0000 |
16 | @@ -299,7 +299,7 @@ |
17 | ... BugTaskImportance, |
18 | ... BugTaskStatus, |
19 | ... ) |
20 | - >>> from lp.bugs.tests.test_bugtask_1 import ( |
21 | + >>> from lp.bugs.tests.test_bugtask import ( |
22 | ... BugTaskSearchBugsElsewhereTest) |
23 | >>> def bugTaskInfo(bugtask): |
24 | ... return '%i %i %s %s' % ( |
25 | |
26 | === modified file 'lib/lp/bugs/interfaces/bug.py' |
27 | --- lib/lp/bugs/interfaces/bug.py 2010-10-16 14:20:57 +0000 |
28 | +++ lib/lp/bugs/interfaces/bug.py 2010-10-25 11:13:20 +0000 |
29 | @@ -493,12 +493,6 @@ |
30 | from duplicates. |
31 | """ |
32 | |
33 | - def getStructuralSubscribers(recipients=None, level=None): |
34 | - """Return `IPerson`s subscribed to this bug's targets. |
35 | - |
36 | - This takes into account bug subscription filters. |
37 | - """ |
38 | - |
39 | def getSubscriptionsFromDuplicates(): |
40 | """Return IBugSubscriptions subscribed from dupes of this bug.""" |
41 | |
42 | |
43 | === modified file 'lib/lp/bugs/interfaces/bugtask.py' |
44 | --- lib/lp/bugs/interfaces/bugtask.py 2010-10-22 21:38:42 +0000 |
45 | +++ lib/lp/bugs/interfaces/bugtask.py 2010-10-25 11:13:20 +0000 |
46 | @@ -1524,6 +1524,12 @@ |
47 | def getOpenBugTasksPerProduct(user, products): |
48 | """Return open bugtask count for multiple products.""" |
49 | |
50 | + def getStructuralSubscribers(bugtasks, recipients=None, level=None): |
51 | + """Return `IPerson`s subscribed to the given bug tasks. |
52 | + |
53 | + This takes into account bug subscription filters. |
54 | + """ |
55 | + |
56 | |
57 | def valid_remote_bug_url(value): |
58 | """Verify that the URL is to a bug to a known bug tracker.""" |
59 | |
60 | === modified file 'lib/lp/bugs/model/bug.py' |
61 | --- lib/lp/bugs/model/bug.py 2010-10-22 13:43:50 +0000 |
62 | +++ lib/lp/bugs/model/bug.py 2010-10-25 11:13:20 +0000 |
63 | @@ -176,9 +176,6 @@ |
64 | from lp.registry.interfaces.productseries import IProductSeries |
65 | from lp.registry.interfaces.series import SeriesStatus |
66 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
67 | -from lp.registry.interfaces.structuralsubscription import ( |
68 | - IStructuralSubscriptionTarget, |
69 | - ) |
70 | from lp.registry.model.mentoringoffer import MentoringOffer |
71 | from lp.registry.model.person import ( |
72 | Person, |
73 | @@ -934,8 +931,9 @@ |
74 | # XXX: RobertCollins 2010-09-22 bug=374777: This SQL(...) is a |
75 | # hack; it does not seem to be possible to express DISTINCT ON |
76 | # with Storm. |
77 | - (SQL("DISTINCT ON (Person.name, BugSubscription.person) 0 AS ignore"), |
78 | - # return people and subscribptions |
79 | + (SQL("DISTINCT ON (Person.name, BugSubscription.person) " |
80 | + "0 AS ignore"), |
81 | + # Return people and subscriptions |
82 | Person, BugSubscription), |
83 | # For this bug or its duplicates |
84 | Or( |
85 | @@ -986,8 +984,8 @@ |
86 | |
87 | # Structural subscribers. |
88 | also_notified_subscribers.update( |
89 | - self.getStructuralSubscribers( |
90 | - recipients=recipients, level=level)) |
91 | + getUtility(IBugTaskSet).getStructuralSubscribers( |
92 | + self.bugtasks, recipients=recipients, level=level)) |
93 | |
94 | # Direct subscriptions always take precedence over indirect |
95 | # subscriptions. |
96 | @@ -999,58 +997,6 @@ |
97 | (also_notified_subscribers - direct_subscribers), |
98 | key=lambda x: removeSecurityProxy(x).displayname) |
99 | |
100 | - def getStructuralSubscribers(self, recipients=None, level=None): |
101 | - """See `IBug`. """ |
102 | - query_arguments = [] |
103 | - for bugtask in self.bugtasks: |
104 | - if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
105 | - query_arguments.append((bugtask.target, bugtask)) |
106 | - if bugtask.target.parent_subscription_target is not None: |
107 | - query_arguments.append( |
108 | - (bugtask.target.parent_subscription_target, bugtask)) |
109 | - if ISourcePackage.providedBy(bugtask.target): |
110 | - # Distribution series bug tasks with a package have the source |
111 | - # package set as their target, so we add the distroseries |
112 | - # explicitly to the set of subscription targets. |
113 | - query_arguments.append((bugtask.distroseries, bugtask)) |
114 | - if bugtask.milestone is not None: |
115 | - query_arguments.append((bugtask.milestone, bugtask)) |
116 | - |
117 | - if len(query_arguments) == 0: |
118 | - return EmptyResultSet() |
119 | - |
120 | - if level is None: |
121 | - # If level is not specified, default to NOTHING so that all |
122 | - # subscriptions are found. XXX: Perhaps this should go in |
123 | - # getSubscriptionsForBugTask()? |
124 | - level = BugNotificationLevel.NOTHING |
125 | - |
126 | - # Build the query. |
127 | - union = lambda left, right: left.union(right) |
128 | - queries = ( |
129 | - target.getSubscriptionsForBugTask(bugtask, level) |
130 | - for target, bugtask in query_arguments) |
131 | - subscriptions = reduce(union, queries) |
132 | - |
133 | - # Pull all the subscriptions in. |
134 | - subscriptions = list(subscriptions) |
135 | - |
136 | - # Prepare a query for the subscribers. |
137 | - subscribers = Store.of(self).find( |
138 | - Person, Person.id.is_in( |
139 | - subscription.subscriberID |
140 | - for subscription in subscriptions)) |
141 | - |
142 | - if recipients is not None: |
143 | - # We need to process subscriptions, so pull all the subscribes |
144 | - # into the cache, then update recipients with the subscriptions. |
145 | - subscribers = list(subscribers) |
146 | - for subscription in subscriptions: |
147 | - recipients.addStructuralSubscriber( |
148 | - subscription.subscriber, subscription.target) |
149 | - |
150 | - return subscribers |
151 | - |
152 | def getBugNotificationRecipients(self, duplicateof=None, old_bug=None, |
153 | level=None, |
154 | include_master_dupe_subscribers=False): |
155 | |
156 | === modified file 'lib/lp/bugs/model/bugtask.py' |
157 | --- lib/lp/bugs/model/bugtask.py 2010-10-22 19:56:26 +0000 |
158 | +++ lib/lp/bugs/model/bugtask.py 2010-10-25 11:13:20 +0000 |
159 | @@ -127,6 +127,7 @@ |
160 | ) |
161 | from lp.bugs.model.bugnomination import BugNomination |
162 | from lp.bugs.model.bugsubscription import BugSubscription |
163 | +from lp.registry.enum import BugNotificationLevel |
164 | from lp.registry.interfaces.distribution import ( |
165 | IDistribution, |
166 | IDistributionSet, |
167 | @@ -155,6 +156,9 @@ |
168 | from lp.registry.interfaces.projectgroup import IProjectGroup |
169 | from lp.registry.interfaces.sourcepackage import ISourcePackage |
170 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
171 | +from lp.registry.interfaces.structuralsubscription import ( |
172 | + IStructuralSubscriptionTarget, |
173 | + ) |
174 | from lp.registry.model.pillar import pillar_sort_key |
175 | from lp.registry.model.sourcepackagename import SourcePackageName |
176 | from lp.services.propertycache import IPropertyCache |
177 | @@ -1953,7 +1957,7 @@ |
178 | query = " AND ".join(extra_clauses) |
179 | |
180 | if not decorators: |
181 | - decorator = lambda x:x |
182 | + decorator = lambda x: x |
183 | else: |
184 | def decorator(obj): |
185 | for decor in decorators: |
186 | @@ -2835,3 +2839,59 @@ |
187 | counts.append(package_counts) |
188 | |
189 | return counts |
190 | + |
191 | + def getStructuralSubscribers(self, bugtasks, recipients=None, level=None): |
192 | + """See `IBugTaskSet`.""" |
193 | + # getStructuralSubscribers() is called from various sites, but needs |
194 | + # bugtasks without security proxies. |
195 | + bugtasks = [removeSecurityProxy(bugtask) for bugtask in bugtasks] |
196 | + |
197 | + query_arguments = [] |
198 | + for bugtask in bugtasks: |
199 | + if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
200 | + query_arguments.append((bugtask.target, bugtask)) |
201 | + if bugtask.target.parent_subscription_target is not None: |
202 | + query_arguments.append( |
203 | + (bugtask.target.parent_subscription_target, bugtask)) |
204 | + if ISourcePackage.providedBy(bugtask.target): |
205 | + # Distribution series bug tasks with a package have the source |
206 | + # package set as their target, so we add the distroseries |
207 | + # explicitly to the set of subscription targets. |
208 | + query_arguments.append((bugtask.distroseries, bugtask)) |
209 | + if bugtask.milestone is not None: |
210 | + query_arguments.append((bugtask.milestone, bugtask)) |
211 | + |
212 | + if len(query_arguments) == 0: |
213 | + return EmptyResultSet() |
214 | + |
215 | + if level is None: |
216 | + # If level is not specified, default to NOTHING so that all |
217 | + # subscriptions are found. |
218 | + level = BugNotificationLevel.NOTHING |
219 | + |
220 | + # Build the query. |
221 | + union = lambda left, right: left.union(right) |
222 | + queries = ( |
223 | + target.getSubscriptionsForBugTask(bugtask, level) |
224 | + for target, bugtask in query_arguments) |
225 | + subscriptions = reduce(union, queries) |
226 | + |
227 | + # Pull all the subscriptions in. |
228 | + subscriptions = list(subscriptions) |
229 | + |
230 | + # Prepare a query for the subscribers. |
231 | + from lp.registry.model.person import Person |
232 | + subscribers = IStore(Person).find( |
233 | + Person, Person.id.is_in( |
234 | + subscription.subscriberID |
235 | + for subscription in subscriptions)) |
236 | + |
237 | + if recipients is not None: |
238 | + # We need to process subscriptions, so pull all the subscribes into |
239 | + # the cache, then update recipients with the subscriptions. |
240 | + subscribers = list(subscribers) |
241 | + for subscription in subscriptions: |
242 | + recipients.addStructuralSubscriber( |
243 | + subscription.subscriber, subscription.target) |
244 | + |
245 | + return subscribers |
246 | |
247 | === modified file 'lib/lp/bugs/model/tests/test_bug.py' |
248 | --- lib/lp/bugs/model/tests/test_bug.py 2010-10-16 14:20:57 +0000 |
249 | +++ lib/lp/bugs/model/tests/test_bug.py 2010-10-25 11:13:20 +0000 |
250 | @@ -5,10 +5,7 @@ |
251 | |
252 | __metaclass__ = type |
253 | |
254 | -from storm.store import ResultSet |
255 | - |
256 | from canonical.testing.layers import DatabaseFunctionalLayer |
257 | -from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
258 | from lp.registry.enum import BugNotificationLevel |
259 | from lp.registry.interfaces.person import PersonVisibility |
260 | from lp.registry.model.structuralsubscription import StructuralSubscription |
261 | @@ -17,7 +14,6 @@ |
262 | person_logged_in, |
263 | TestCaseWithFactory, |
264 | ) |
265 | -from lp.testing.matchers import StartsWith |
266 | |
267 | |
268 | class TestBug(TestCaseWithFactory): |
269 | @@ -246,84 +242,3 @@ |
270 | self.assertTrue( |
271 | subscriber not in duplicate_subscribers, |
272 | "Subscriber should not be in duplicate_subscribers.") |
273 | - |
274 | - |
275 | -class TestBugStructuralSubscribers(TestCaseWithFactory): |
276 | - |
277 | - layer = DatabaseFunctionalLayer |
278 | - |
279 | - def test_getStructuralSubscribers_no_subscribers(self): |
280 | - # If there are no subscribers for any of the bug's targets then no |
281 | - # subscribers will be returned by getStructuralSubscribers(). |
282 | - product = self.factory.makeProduct() |
283 | - bug = self.factory.makeBug(product=product) |
284 | - subscribers = bug.getStructuralSubscribers() |
285 | - self.assertIsInstance(subscribers, ResultSet) |
286 | - self.assertEqual([], list(subscribers)) |
287 | - |
288 | - def test_getStructuralSubscribers_single_target(self): |
289 | - # Subscribers for any of the bug's targets are returned. |
290 | - subscriber = self.factory.makePerson() |
291 | - login_person(subscriber) |
292 | - product = self.factory.makeProduct() |
293 | - product.addBugSubscription(subscriber, subscriber) |
294 | - bug = self.factory.makeBug(product=product) |
295 | - self.assertEqual([subscriber], list(bug.getStructuralSubscribers())) |
296 | - |
297 | - def test_getStructuralSubscribers_multiple_targets(self): |
298 | - # Subscribers for any of the bug's targets are returned. |
299 | - actor = self.factory.makePerson() |
300 | - login_person(actor) |
301 | - |
302 | - subscriber1 = self.factory.makePerson() |
303 | - subscriber2 = self.factory.makePerson() |
304 | - |
305 | - product1 = self.factory.makeProduct(owner=actor) |
306 | - product1.addBugSubscription(subscriber1, subscriber1) |
307 | - product2 = self.factory.makeProduct(owner=actor) |
308 | - product2.addBugSubscription(subscriber2, subscriber2) |
309 | - |
310 | - bug = self.factory.makeBug(product=product1) |
311 | - bug.addTask(actor, product2) |
312 | - |
313 | - subscribers = bug.getStructuralSubscribers() |
314 | - self.assertIsInstance(subscribers, ResultSet) |
315 | - self.assertEqual(set([subscriber1, subscriber2]), set(subscribers)) |
316 | - |
317 | - def test_getStructuralSubscribers_recipients(self): |
318 | - # If provided, getStructuralSubscribers() calls the appropriate |
319 | - # methods on a BugNotificationRecipients object. |
320 | - subscriber = self.factory.makePerson() |
321 | - login_person(subscriber) |
322 | - product = self.factory.makeProduct() |
323 | - product.addBugSubscription(subscriber, subscriber) |
324 | - bug = self.factory.makeBug(product=product) |
325 | - recipients = BugNotificationRecipients() |
326 | - subscribers = bug.getStructuralSubscribers(recipients=recipients) |
327 | - # The return value is a list only when populating recipients. |
328 | - self.assertIsInstance(subscribers, list) |
329 | - self.assertEqual([subscriber], recipients.getRecipients()) |
330 | - reason, header = recipients.getReason(subscriber) |
331 | - self.assertThat( |
332 | - reason, StartsWith( |
333 | - u"You received this bug notification because " |
334 | - u"you are subscribed to ")) |
335 | - self.assertThat(header, StartsWith(u"Subscriber ")) |
336 | - |
337 | - def test_getStructuralSubscribers_level(self): |
338 | - # getStructuralSubscribers() respects the given level. |
339 | - subscriber = self.factory.makePerson() |
340 | - login_person(subscriber) |
341 | - product = self.factory.makeProduct() |
342 | - subscription = product.addBugSubscription(subscriber, subscriber) |
343 | - subscription.bug_notification_level = BugNotificationLevel.METADATA |
344 | - bug = self.factory.makeBug(product=product) |
345 | - self.assertEqual( |
346 | - [subscriber], list( |
347 | - bug.getStructuralSubscribers( |
348 | - level=BugNotificationLevel.METADATA))) |
349 | - subscription.bug_notification_level = BugNotificationLevel.METADATA |
350 | - self.assertEqual( |
351 | - [], list( |
352 | - bug.getStructuralSubscribers( |
353 | - level=BugNotificationLevel.COMMENTS))) |
354 | |
355 | === renamed file 'lib/lp/bugs/tests/test_bugtask.py' => 'lib/lp/bugs/model/tests/test_bugtask.py' |
356 | --- lib/lp/bugs/tests/test_bugtask.py 2010-10-21 16:40:10 +0000 |
357 | +++ lib/lp/bugs/model/tests/test_bugtask.py 2010-10-25 11:13:20 +0000 |
358 | @@ -8,31 +8,52 @@ |
359 | import unittest |
360 | |
361 | from lazr.lifecycle.snapshot import Snapshot |
362 | +from storm.store import ResultSet |
363 | from zope.component import getUtility |
364 | from zope.interface import providedBy |
365 | |
366 | +from canonical.database.sqlbase import flush_database_updates |
367 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
368 | from canonical.launchpad.searchbuilder import ( |
369 | all, |
370 | any, |
371 | ) |
372 | +from canonical.launchpad.webapp.interfaces import ILaunchBag |
373 | from canonical.testing.layers import ( |
374 | DatabaseFunctionalLayer, |
375 | LaunchpadZopelessLayer, |
376 | ) |
377 | +from lp.app.enums import ServiceUsage |
378 | +from lp.bugs.interfaces.bug import IBugSet |
379 | from lp.bugs.interfaces.bugtarget import IBugTarget |
380 | from lp.bugs.interfaces.bugtask import ( |
381 | BugTaskImportance, |
382 | BugTaskSearchParams, |
383 | BugTaskStatus, |
384 | + IBugTaskSet, |
385 | + IUpstreamBugTask, |
386 | + RESOLVED_BUGTASK_STATUSES, |
387 | + UNRESOLVED_BUGTASK_STATUSES, |
388 | ) |
389 | +from lp.bugs.interfaces.bugwatch import IBugWatchSet |
390 | +from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
391 | from lp.bugs.model.bugtask import build_tag_search_clause |
392 | +from lp.bugs.tests.bug import ( |
393 | + create_old_bug, |
394 | + sync_bugtasks, |
395 | + ) |
396 | from lp.hardwaredb.interfaces.hwdb import ( |
397 | HWBus, |
398 | IHWDeviceSet, |
399 | ) |
400 | +from lp.registry.enum import BugNotificationLevel |
401 | from lp.registry.interfaces.distribution import IDistributionSet |
402 | -from lp.registry.interfaces.person import IPerson, IPersonSet |
403 | +from lp.registry.interfaces.person import ( |
404 | + IPerson, |
405 | + IPersonSet, |
406 | + ) |
407 | +from lp.registry.interfaces.product import IProductSet |
408 | +from lp.registry.interfaces.projectgroup import IProjectGroupSet |
409 | from lp.testing import ( |
410 | ANONYMOUS, |
411 | login, |
412 | @@ -42,6 +63,11 @@ |
413 | TestCase, |
414 | TestCaseWithFactory, |
415 | ) |
416 | +from lp.testing.factory import ( |
417 | + is_security_proxied_or_harmless, |
418 | + LaunchpadObjectFactory, |
419 | + ) |
420 | +from lp.testing.matchers import StartsWith |
421 | |
422 | |
423 | class TestBugTaskDelta(TestCaseWithFactory): |
424 | @@ -892,7 +918,7 @@ |
425 | self.assertEqual(2, tasks.count()) |
426 | # Cache in the storm cache the account->person lookup so its not |
427 | # distorting what we're testing. |
428 | - _ = IPerson(person.account, None) |
429 | + IPerson(person.account, None) |
430 | # One query and only one should be issued to get the tasks, bugs and |
431 | # allow access to getConjoinedMaster attribute - an attribute that |
432 | # triggers a permission check (nb: id does not trigger such a check) |
433 | @@ -945,6 +971,420 @@ |
434 | self.assertEqual([task2], list(result)) |
435 | |
436 | |
437 | +class BugTaskSearchBugsElsewhereTest(unittest.TestCase): |
438 | + """Tests for searching bugs filtering on related bug tasks. |
439 | + |
440 | + It also acts as a helper class, which makes related doctests more |
441 | + readable, since they can use methods from this class. |
442 | + """ |
443 | + layer = DatabaseFunctionalLayer |
444 | + |
445 | + def __init__(self, methodName='runTest', helper_only=False): |
446 | + """If helper_only is True, set up it only as a helper class.""" |
447 | + if not helper_only: |
448 | + unittest.TestCase.__init__(self, methodName=methodName) |
449 | + |
450 | + def setUp(self): |
451 | + login(ANONYMOUS) |
452 | + |
453 | + def tearDown(self): |
454 | + logout() |
455 | + |
456 | + def _getBugTaskByTarget(self, bug, target): |
457 | + """Return a bug's bugtask for the given target.""" |
458 | + for bugtask in bug.bugtasks: |
459 | + if bugtask.target == target: |
460 | + return bugtask |
461 | + else: |
462 | + raise AssertionError( |
463 | + "Didn't find a %s task on bug %s." % ( |
464 | + target.bugtargetname, bug.id)) |
465 | + |
466 | + def setUpBugsResolvedUpstreamTests(self): |
467 | + """Modify some bugtasks to match the resolved upstream filter.""" |
468 | + bugset = getUtility(IBugSet) |
469 | + productset = getUtility(IProductSet) |
470 | + firefox = productset.getByName("firefox") |
471 | + thunderbird = productset.getByName("thunderbird") |
472 | + |
473 | + # Mark an upstream task on bug #1 "Fix Released" |
474 | + bug_one = bugset.get(1) |
475 | + firefox_upstream = self._getBugTaskByTarget(bug_one, firefox) |
476 | + self.assertEqual( |
477 | + ServiceUsage.LAUNCHPAD, |
478 | + firefox_upstream.product.bug_tracking_usage) |
479 | + self.old_firefox_status = firefox_upstream.status |
480 | + firefox_upstream.transitionToStatus( |
481 | + BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
482 | + self.firefox_upstream = firefox_upstream |
483 | + |
484 | + # Mark an upstream task on bug #9 "Fix Committed" |
485 | + bug_nine = bugset.get(9) |
486 | + thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird) |
487 | + self.old_thunderbird_status = thunderbird_upstream.status |
488 | + thunderbird_upstream.transitionToStatus( |
489 | + BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user) |
490 | + self.thunderbird_upstream = thunderbird_upstream |
491 | + |
492 | + # Add a watch to a Debian bug for bug #2, and mark the task Fix |
493 | + # Released. |
494 | + bug_two = bugset.get(2) |
495 | + bugwatchset = getUtility(IBugWatchSet) |
496 | + |
497 | + # Get a debbugs watch. |
498 | + watch_debbugs_327452 = bugwatchset.get(9) |
499 | + self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs") |
500 | + self.assertEquals(watch_debbugs_327452.remotebug, "327452") |
501 | + |
502 | + # Associate the watch to a Fix Released task. |
503 | + debian = getUtility(IDistributionSet).getByName("debian") |
504 | + debian_firefox = debian.getSourcePackage("mozilla-firefox") |
505 | + bug_two_in_debian_firefox = self._getBugTaskByTarget( |
506 | + bug_two, debian_firefox) |
507 | + bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452 |
508 | + bug_two_in_debian_firefox.transitionToStatus( |
509 | + BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
510 | + |
511 | + flush_database_updates() |
512 | + |
513 | + def tearDownBugsElsewhereTests(self): |
514 | + """Resets the modified bugtasks to their original statuses.""" |
515 | + self.firefox_upstream.transitionToStatus( |
516 | + self.old_firefox_status, |
517 | + self.firefox_upstream.target.bug_supervisor) |
518 | + self.thunderbird_upstream.transitionToStatus( |
519 | + self.old_thunderbird_status, |
520 | + self.firefox_upstream.target.bug_supervisor) |
521 | + flush_database_updates() |
522 | + |
523 | + def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask): |
524 | + """Assert the bugtask is pending a bug watch elsewhere. |
525 | + |
526 | + Pending a bugwatch elsewhere means that at least one of the bugtask's |
527 | + related task's target isn't using Malone, and that |
528 | + related_bugtask.bugwatch is None. |
529 | + """ |
530 | + non_malone_using_bugtasks = [ |
531 | + related_task for related_task in bugtask.related_tasks |
532 | + if not related_task.target_uses_malone] |
533 | + pending_bugwatch_bugtasks = [ |
534 | + related_bugtask for related_bugtask in non_malone_using_bugtasks |
535 | + if related_bugtask.bugwatch is None] |
536 | + self.assert_( |
537 | + len(pending_bugwatch_bugtasks) > 0, |
538 | + 'Bugtask %s on %s has no related bug watches elsewhere.' % ( |
539 | + bugtask.id, bugtask.target.displayname)) |
540 | + |
541 | + def assertBugTaskIsResolvedUpstream(self, bugtask): |
542 | + """Make sure at least one of the related upstream tasks is resolved. |
543 | + |
544 | + "Resolved", for our purposes, means either that one of the related |
545 | + tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or |
546 | + it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or |
547 | + INVALID state. |
548 | + """ |
549 | + resolved_upstream_states = [ |
550 | + BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED] |
551 | + resolved_bugwatch_states = [ |
552 | + BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED, |
553 | + BugTaskStatus.INVALID] |
554 | + |
555 | + # Helper functions for the list comprehension below. |
556 | + def _is_resolved_upstream_task(bugtask): |
557 | + return ( |
558 | + IUpstreamBugTask.providedBy(bugtask) and |
559 | + bugtask.status in resolved_upstream_states) |
560 | + |
561 | + def _is_resolved_bugwatch_task(bugtask): |
562 | + return ( |
563 | + bugtask.bugwatch and bugtask.status in |
564 | + resolved_bugwatch_states) |
565 | + |
566 | + resolved_related_tasks = [ |
567 | + related_task for related_task in bugtask.related_tasks |
568 | + if (_is_resolved_upstream_task(related_task) or |
569 | + _is_resolved_bugwatch_task(related_task))] |
570 | + |
571 | + self.assert_(len(resolved_related_tasks) > 0) |
572 | + self.assert_( |
573 | + len(resolved_related_tasks) > 0, |
574 | + 'Bugtask %s on %s has no resolved related tasks.' % ( |
575 | + bugtask.id, bugtask.target.displayname)) |
576 | + |
577 | + def assertBugTaskIsOpenUpstream(self, bugtask): |
578 | + """Make sure at least one of the related upstream tasks is open. |
579 | + |
580 | + "Open", for our purposes, means either that one of the related |
581 | + tasks is an upstream task or a task with a bugwatch which has |
582 | + one of the states listed in open_states. |
583 | + """ |
584 | + open_states = [ |
585 | + BugTaskStatus.NEW, |
586 | + BugTaskStatus.INCOMPLETE, |
587 | + BugTaskStatus.CONFIRMED, |
588 | + BugTaskStatus.INPROGRESS, |
589 | + BugTaskStatus.UNKNOWN] |
590 | + |
591 | + # Helper functions for the list comprehension below. |
592 | + def _is_open_upstream_task(bugtask): |
593 | + return ( |
594 | + IUpstreamBugTask.providedBy(bugtask) and |
595 | + bugtask.status in open_states) |
596 | + |
597 | + def _is_open_bugwatch_task(bugtask): |
598 | + return ( |
599 | + bugtask.bugwatch and bugtask.status in |
600 | + open_states) |
601 | + |
602 | + open_related_tasks = [ |
603 | + related_task for related_task in bugtask.related_tasks |
604 | + if (_is_open_upstream_task(related_task) or |
605 | + _is_open_bugwatch_task(related_task))] |
606 | + |
607 | + self.assert_( |
608 | + len(open_related_tasks) > 0, |
609 | + 'Bugtask %s on %s has no open related tasks.' % ( |
610 | + bugtask.id, bugtask.target.displayname)) |
611 | + |
612 | + def _hasUpstreamTask(self, bug): |
613 | + """Does this bug have an upstream task associated with it? |
614 | + |
615 | + Returns True if yes, otherwise False. |
616 | + """ |
617 | + for bugtask in bug.bugtasks: |
618 | + if IUpstreamBugTask.providedBy(bugtask): |
619 | + return True |
620 | + return False |
621 | + |
622 | + def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask): |
623 | + """Should the bugtask be shown in the search no upstream task search? |
624 | + |
625 | + Returns True if yes, otherwise False. |
626 | + """ |
627 | + self.assert_( |
628 | + not self._hasUpstreamTask(bugtask.bug), |
629 | + 'Bugtask %s on %s has upstream tasks.' % ( |
630 | + bugtask.id, bugtask.target.displayname)) |
631 | + |
632 | + |
633 | +class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase): |
634 | + """Test `BugTaskSet.findExpirableBugTasks()` behaviour.""" |
635 | + layer = DatabaseFunctionalLayer |
636 | + |
637 | + def setUp(self): |
638 | + """Setup the zope interaction and create expirable bugtasks.""" |
639 | + login('test@canonical.com') |
640 | + self.user = getUtility(ILaunchBag).user |
641 | + self.distribution = getUtility(IDistributionSet).getByName('ubuntu') |
642 | + self.distroseries = self.distribution.getSeries('hoary') |
643 | + self.product = getUtility(IProductSet).getByName('jokosher') |
644 | + self.productseries = self.product.getSeries('trunk') |
645 | + self.bugtaskset = getUtility(IBugTaskSet) |
646 | + bugtasks = [] |
647 | + bugtasks.append( |
648 | + create_old_bug("90 days old", 90, self.distribution)) |
649 | + bugtasks.append( |
650 | + self.bugtaskset.createTask( |
651 | + bug=bugtasks[-1].bug, owner=self.user, |
652 | + distroseries=self.distroseries)) |
653 | + bugtasks.append( |
654 | + create_old_bug("90 days old", 90, self.product)) |
655 | + bugtasks.append( |
656 | + self.bugtaskset.createTask( |
657 | + bug=bugtasks[-1].bug, owner=self.user, |
658 | + productseries=self.productseries)) |
659 | + sync_bugtasks(bugtasks) |
660 | + |
661 | + def tearDown(self): |
662 | + logout() |
663 | + |
664 | + def testSupportedTargetParam(self): |
665 | + """The target param supports a limited set of BugTargets. |
666 | + |
667 | + Four BugTarget types may passed as the target argument: |
668 | + Distribution, DistroSeries, Product, ProductSeries. |
669 | + """ |
670 | + supported_targets = [self.distribution, self.distroseries, |
671 | + self.product, self.productseries] |
672 | + for target in supported_targets: |
673 | + expirable_bugtasks = self.bugtaskset.findExpirableBugTasks( |
674 | + 0, self.user, target=target) |
675 | + self.assertNotEqual(expirable_bugtasks.count(), 0, |
676 | + "%s has %d expirable bugtasks." % |
677 | + (self.distroseries, expirable_bugtasks.count())) |
678 | + |
679 | + def testUnsupportedBugTargetParam(self): |
680 | + """Test that unsupported targets raise errors. |
681 | + |
682 | + Three BugTarget types are not supported because the UI does not |
683 | + provide bug-index to link to the 'bugs that can expire' page. |
684 | + ProjectGroup, SourcePackage, and DistributionSourcePackage will |
685 | + raise an NotImplementedError. |
686 | + |
687 | + Passing an unknown bugtarget type will raise an AssertionError. |
688 | + """ |
689 | + project = getUtility(IProjectGroupSet).getByName('mozilla') |
690 | + distributionsourcepackage = self.distribution.getSourcePackage( |
691 | + 'mozilla-firefox') |
692 | + sourcepackage = self.distroseries.getSourcePackage( |
693 | + 'mozilla-firefox') |
694 | + unsupported_targets = [project, distributionsourcepackage, |
695 | + sourcepackage] |
696 | + for target in unsupported_targets: |
697 | + self.assertRaises( |
698 | + NotImplementedError, self.bugtaskset.findExpirableBugTasks, |
699 | + 0, self.user, target=target) |
700 | + |
701 | + # Objects that are not a known BugTarget type raise an AssertionError. |
702 | + self.assertRaises( |
703 | + AssertionError, self.bugtaskset.findExpirableBugTasks, |
704 | + 0, self.user, target=[]) |
705 | + |
706 | + |
707 | +class BugTaskSetTest(unittest.TestCase): |
708 | + """Test `BugTaskSet` methods.""" |
709 | + layer = DatabaseFunctionalLayer |
710 | + |
711 | + def setUp(self): |
712 | + login(ANONYMOUS) |
713 | + |
714 | + def test_getBugTasks(self): |
715 | + """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given |
716 | + bugs to their bugtasks. It does that in a single query, to avoid |
717 | + hitting the DB again when getting the bugs' tasks. |
718 | + """ |
719 | + login('no-priv@canonical.com') |
720 | + factory = LaunchpadObjectFactory() |
721 | + bug1 = factory.makeBug() |
722 | + factory.makeBugTask(bug1) |
723 | + bug2 = factory.makeBug() |
724 | + factory.makeBugTask(bug2) |
725 | + factory.makeBugTask(bug2) |
726 | + |
727 | + bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks( |
728 | + [bug1.id, bug2.id]) |
729 | + # The bugtasks returned by getBugTasks() are exactly the same as the |
730 | + # ones returned by bug.bugtasks, obviously. |
731 | + self.failUnlessEqual( |
732 | + set(bugs_and_tasks[bug1]).difference(bug1.bugtasks), |
733 | + set([])) |
734 | + self.failUnlessEqual( |
735 | + set(bugs_and_tasks[bug2]).difference(bug2.bugtasks), |
736 | + set([])) |
737 | + |
738 | + def test_getBugTasks_with_empty_list(self): |
739 | + # When given an empty list of bug IDs, getBugTasks() will return an |
740 | + # empty dictionary. |
741 | + bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([]) |
742 | + self.failUnlessEqual(bugs_and_tasks, {}) |
743 | + |
744 | + |
745 | +class TestBugTaskStatuses(TestCase): |
746 | + |
747 | + def test_open_and_resolved_statuses(self): |
748 | + """ |
749 | + There are constants that are used to define which statuses are for |
750 | + resolved bugs (`RESOLVED_BUGTASK_STATUSES`), and which are for |
751 | + unresolved bugs (`UNRESOLVED_BUGTASK_STATUSES`). The two constants |
752 | + include all statuses defined in BugTaskStatus, except for Unknown. |
753 | + """ |
754 | + self.assertNotIn(BugTaskStatus.UNKNOWN, RESOLVED_BUGTASK_STATUSES) |
755 | + self.assertNotIn(BugTaskStatus.UNKNOWN, UNRESOLVED_BUGTASK_STATUSES) |
756 | + |
757 | + |
758 | +class TestGetStructuralSubscribers(TestCaseWithFactory): |
759 | + |
760 | + layer = DatabaseFunctionalLayer |
761 | + |
762 | + def make_product_with_bug(self): |
763 | + product = self.factory.makeProduct() |
764 | + bug = self.factory.makeBug(product=product) |
765 | + return product, bug |
766 | + |
767 | + def getStructuralSubscribers(self, bugtasks, *args, **kwargs): |
768 | + # Call IBugTaskSet.getStructuralSubscribers() and check that the |
769 | + # result is security proxied. |
770 | + result = getUtility(IBugTaskSet).getStructuralSubscribers( |
771 | + bugtasks, *args, **kwargs) |
772 | + self.assertTrue(is_security_proxied_or_harmless(result)) |
773 | + return result |
774 | + |
775 | + def test_getStructuralSubscribers_no_subscribers(self): |
776 | + # If there are no subscribers for any of the bug's targets then no |
777 | + # subscribers will be returned by getStructuralSubscribers(). |
778 | + product, bug = self.make_product_with_bug() |
779 | + subscribers = self.getStructuralSubscribers(bug.bugtasks) |
780 | + self.assertIsInstance(subscribers, ResultSet) |
781 | + self.assertEqual([], list(subscribers)) |
782 | + |
783 | + def test_getStructuralSubscribers_single_target(self): |
784 | + # Subscribers for any of the bug's targets are returned. |
785 | + subscriber = self.factory.makePerson() |
786 | + login_person(subscriber) |
787 | + product, bug = self.make_product_with_bug() |
788 | + product.addBugSubscription(subscriber, subscriber) |
789 | + self.assertEqual( |
790 | + [subscriber], list( |
791 | + self.getStructuralSubscribers(bug.bugtasks))) |
792 | + |
793 | + def test_getStructuralSubscribers_multiple_targets(self): |
794 | + # Subscribers for any of the bug's targets are returned. |
795 | + actor = self.factory.makePerson() |
796 | + login_person(actor) |
797 | + |
798 | + subscriber1 = self.factory.makePerson() |
799 | + subscriber2 = self.factory.makePerson() |
800 | + |
801 | + product1 = self.factory.makeProduct(owner=actor) |
802 | + product1.addBugSubscription(subscriber1, subscriber1) |
803 | + product2 = self.factory.makeProduct(owner=actor) |
804 | + product2.addBugSubscription(subscriber2, subscriber2) |
805 | + |
806 | + bug = self.factory.makeBug(product=product1) |
807 | + bug.addTask(actor, product2) |
808 | + |
809 | + subscribers = self.getStructuralSubscribers(bug.bugtasks) |
810 | + self.assertIsInstance(subscribers, ResultSet) |
811 | + self.assertEqual(set([subscriber1, subscriber2]), set(subscribers)) |
812 | + |
813 | + def test_getStructuralSubscribers_recipients(self): |
814 | + # If provided, getStructuralSubscribers() calls the appropriate |
815 | + # methods on a BugNotificationRecipients object. |
816 | + subscriber = self.factory.makePerson() |
817 | + login_person(subscriber) |
818 | + product, bug = self.make_product_with_bug() |
819 | + product.addBugSubscription(subscriber, subscriber) |
820 | + recipients = BugNotificationRecipients() |
821 | + subscribers = self.getStructuralSubscribers( |
822 | + bug.bugtasks, recipients=recipients) |
823 | + # The return value is a list only when populating recipients. |
824 | + self.assertIsInstance(subscribers, list) |
825 | + self.assertEqual([subscriber], recipients.getRecipients()) |
826 | + reason, header = recipients.getReason(subscriber) |
827 | + self.assertThat( |
828 | + reason, StartsWith( |
829 | + u"You received this bug notification because " |
830 | + u"you are subscribed to ")) |
831 | + self.assertThat(header, StartsWith(u"Subscriber ")) |
832 | + |
833 | + def test_getStructuralSubscribers_level(self): |
834 | + # getStructuralSubscribers() respects the given level. |
835 | + subscriber = self.factory.makePerson() |
836 | + login_person(subscriber) |
837 | + product, bug = self.make_product_with_bug() |
838 | + subscription = product.addBugSubscription(subscriber, subscriber) |
839 | + subscription.bug_notification_level = BugNotificationLevel.METADATA |
840 | + self.assertEqual( |
841 | + [subscriber], list( |
842 | + self.getStructuralSubscribers( |
843 | + bug.bugtasks, level=BugNotificationLevel.METADATA))) |
844 | + subscription.bug_notification_level = BugNotificationLevel.METADATA |
845 | + self.assertEqual( |
846 | + [], list( |
847 | + self.getStructuralSubscribers( |
848 | + bug.bugtasks, level=BugNotificationLevel.COMMENTS))) |
849 | + |
850 | + |
851 | def test_suite(): |
852 | suite = unittest.TestSuite() |
853 | suite.addTest(unittest.TestLoader().loadTestsFromName(__name__)) |
854 | |
855 | === added file 'lib/lp/bugs/model/tests/test_bugtask_status.py' |
856 | --- lib/lp/bugs/model/tests/test_bugtask_status.py 1970-01-01 00:00:00 +0000 |
857 | +++ lib/lp/bugs/model/tests/test_bugtask_status.py 2010-10-25 11:13:20 +0000 |
858 | @@ -0,0 +1,356 @@ |
859 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
860 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
861 | + |
862 | +"""Tests for bug task status transitions.""" |
863 | + |
864 | +__metaclass__ = type |
865 | + |
866 | +from zope.component import getUtility |
867 | +from zope.security.proxy import removeSecurityProxy |
868 | + |
869 | +from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
870 | +from canonical.testing.layers import LaunchpadFunctionalLayer |
871 | +from lp.bugs.interfaces.bugtask import UserCannotEditBugTaskStatus |
872 | +from lp.bugs.model.bugtask import BugTaskStatus |
873 | +from lp.testing import ( |
874 | + person_logged_in, |
875 | + TestCaseWithFactory, |
876 | + ) |
877 | + |
878 | + |
879 | +class TestBugTaskStatusTransitionForUser(TestCaseWithFactory): |
880 | + """Test bugtask status transitions for a regular logged in user.""" |
881 | + |
882 | + layer = LaunchpadFunctionalLayer |
883 | + |
884 | + def setUp(self): |
885 | + super(TestBugTaskStatusTransitionForUser, self).setUp() |
886 | + self.user = self.factory.makePerson() |
887 | + self.task = self.factory.makeBugTask() |
888 | + |
889 | + def test_user_transition_all_statuses(self): |
890 | + # A regular user should not be able to set statuses in |
891 | + # BUG_SUPERVISOR_BUGTASK_STATUSES, but can set any |
892 | + # other status. |
893 | + self.assertEqual(self.task.status, BugTaskStatus.NEW) |
894 | + with person_logged_in(self.user): |
895 | + self.assertRaises( |
896 | + UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
897 | + BugTaskStatus.WONTFIX, self.user) |
898 | + self.assertRaises( |
899 | + UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
900 | + BugTaskStatus.EXPIRED, self.user) |
901 | + self.assertRaises( |
902 | + UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
903 | + BugTaskStatus.TRIAGED, self.user) |
904 | + self.task.transitionToStatus(BugTaskStatus.NEW, self.user) |
905 | + self.assertEqual(self.task.status, BugTaskStatus.NEW) |
906 | + self.task.transitionToStatus( |
907 | + BugTaskStatus.INCOMPLETE, self.user) |
908 | + self.assertEqual(self.task.status, BugTaskStatus.INCOMPLETE) |
909 | + self.task.transitionToStatus(BugTaskStatus.OPINION, self.user) |
910 | + self.assertEqual(self.task.status, BugTaskStatus.OPINION) |
911 | + self.task.transitionToStatus(BugTaskStatus.INVALID, self.user) |
912 | + self.assertEqual(self.task.status, BugTaskStatus.INVALID) |
913 | + self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.user) |
914 | + self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
915 | + self.task.transitionToStatus( |
916 | + BugTaskStatus.INPROGRESS, self.user) |
917 | + self.assertEqual(self.task.status, BugTaskStatus.INPROGRESS) |
918 | + self.task.transitionToStatus( |
919 | + BugTaskStatus.FIXCOMMITTED, self.user) |
920 | + self.assertEqual(self.task.status, BugTaskStatus.FIXCOMMITTED) |
921 | + self.task.transitionToStatus( |
922 | + BugTaskStatus.FIXRELEASED, self.user) |
923 | + self.assertEqual(self.task.status, BugTaskStatus.FIXRELEASED) |
924 | + |
925 | + def test_user_cannot_unset_wont_fix_status(self): |
926 | + # A regular user should not be able to transition a bug away |
927 | + # from Won't Fix. |
928 | + removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
929 | + with person_logged_in(self.user): |
930 | + self.assertRaises( |
931 | + UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
932 | + BugTaskStatus.CONFIRMED, self.user) |
933 | + |
934 | + def test_user_cannot_unset_fix_released_status(self): |
935 | + # A regular user should not be able to transition a bug away |
936 | + # from Fix Released. |
937 | + removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
938 | + with person_logged_in(self.user): |
939 | + self.assertRaises( |
940 | + UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
941 | + BugTaskStatus.FIXRELEASED, self.user) |
942 | + |
943 | + def test_user_canTransitionToStatus(self): |
944 | + # Regular user cannot transition to BUG_SUPERVISOR_BUGTASK_STATUSES, |
945 | + # but can transition to any other status. |
946 | + self.assertEqual( |
947 | + self.task.canTransitionToStatus( |
948 | + BugTaskStatus.WONTFIX, self.user), |
949 | + False) |
950 | + self.assertEqual( |
951 | + self.task.canTransitionToStatus( |
952 | + BugTaskStatus.EXPIRED, self.user), |
953 | + False) |
954 | + self.assertEqual( |
955 | + self.task.canTransitionToStatus( |
956 | + BugTaskStatus.TRIAGED, self.user), |
957 | + False) |
958 | + self.assertEqual( |
959 | + self.task.canTransitionToStatus( |
960 | + BugTaskStatus.NEW, self.user), |
961 | + True) |
962 | + self.assertEqual( |
963 | + self.task.canTransitionToStatus( |
964 | + BugTaskStatus.INCOMPLETE, self.user), True) |
965 | + self.assertEqual( |
966 | + self.task.canTransitionToStatus( |
967 | + BugTaskStatus.OPINION, self.user), |
968 | + True) |
969 | + self.assertEqual( |
970 | + self.task.canTransitionToStatus( |
971 | + BugTaskStatus.INVALID, self.user), |
972 | + True) |
973 | + self.assertEqual( |
974 | + self.task.canTransitionToStatus( |
975 | + BugTaskStatus.CONFIRMED, self.user), |
976 | + True) |
977 | + self.assertEqual( |
978 | + self.task.canTransitionToStatus( |
979 | + BugTaskStatus.INPROGRESS, self.user), |
980 | + True) |
981 | + self.assertEqual( |
982 | + self.task.canTransitionToStatus( |
983 | + BugTaskStatus.FIXCOMMITTED, self.user), |
984 | + True) |
985 | + self.assertEqual( |
986 | + self.task.canTransitionToStatus( |
987 | + BugTaskStatus.FIXRELEASED, self.user), |
988 | + True) |
989 | + |
990 | + def test_user_canTransitionToStatus_from_wontfix(self): |
991 | + # A regular user cannot transition away from Won't Fix, |
992 | + # so canTransitionToStatus should return False. |
993 | + removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
994 | + self.assertEqual( |
995 | + self.task.canTransitionToStatus( |
996 | + BugTaskStatus.NEW, self.user), |
997 | + False) |
998 | + |
999 | + def test_user_canTransitionToStatus_from_fixreleased(self): |
1000 | + # A regular user cannot transition away from Fix Released, |
1001 | + # so canTransitionToStatus should return False. |
1002 | + removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1003 | + self.assertEqual( |
1004 | + self.task.canTransitionToStatus( |
1005 | + BugTaskStatus.NEW, self.user), |
1006 | + False) |
1007 | + |
1008 | + |
1009 | +class TestBugTaskStatusTransitionForPrivilegedUserBase: |
1010 | + """Base class used to test privileged users and status transitions.""" |
1011 | + |
1012 | + layer = LaunchpadFunctionalLayer |
1013 | + |
1014 | + def setUp(self): |
1015 | + super(TestBugTaskStatusTransitionForPrivilegedUserBase, self).setUp() |
1016 | + # Creation of task and target are deferred to subclasses. |
1017 | + self.task = None |
1018 | + self.person = None |
1019 | + self.makePersonAndTask() |
1020 | + |
1021 | + def makePersonAndTask(self): |
1022 | + """Create a bug task and privileged person for this task. |
1023 | + |
1024 | + This method is implemented by subclasses to correctly setup |
1025 | + each test. |
1026 | + """ |
1027 | + raise NotImplementedError(self.makePersonAndTask) |
1028 | + |
1029 | + def test_privileged_user_transition_any_status(self): |
1030 | + # Privileged users (like owner or bug supervisor) should |
1031 | + # be able to set any status. |
1032 | + with person_logged_in(self.person): |
1033 | + self.task.transitionToStatus(BugTaskStatus.WONTFIX, self.person) |
1034 | + self.assertEqual(self.task.status, BugTaskStatus.WONTFIX) |
1035 | + self.task.transitionToStatus(BugTaskStatus.EXPIRED, self.person) |
1036 | + self.assertEqual(self.task.status, BugTaskStatus.EXPIRED) |
1037 | + self.task.transitionToStatus(BugTaskStatus.TRIAGED, self.person) |
1038 | + self.assertEqual(self.task.status, BugTaskStatus.TRIAGED) |
1039 | + self.task.transitionToStatus(BugTaskStatus.NEW, self.person) |
1040 | + self.assertEqual(self.task.status, BugTaskStatus.NEW) |
1041 | + self.task.transitionToStatus( |
1042 | + BugTaskStatus.INCOMPLETE, self.person) |
1043 | + self.assertEqual(self.task.status, BugTaskStatus.INCOMPLETE) |
1044 | + self.task.transitionToStatus(BugTaskStatus.OPINION, self.person) |
1045 | + self.assertEqual(self.task.status, BugTaskStatus.OPINION) |
1046 | + self.task.transitionToStatus(BugTaskStatus.INVALID, self.person) |
1047 | + self.assertEqual(self.task.status, BugTaskStatus.INVALID) |
1048 | + self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1049 | + self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1050 | + self.task.transitionToStatus( |
1051 | + BugTaskStatus.INPROGRESS, self.person) |
1052 | + self.assertEqual(self.task.status, BugTaskStatus.INPROGRESS) |
1053 | + self.task.transitionToStatus( |
1054 | + BugTaskStatus.FIXCOMMITTED, self.person) |
1055 | + self.assertEqual(self.task.status, BugTaskStatus.FIXCOMMITTED) |
1056 | + self.task.transitionToStatus( |
1057 | + BugTaskStatus.FIXRELEASED, self.person) |
1058 | + self.assertEqual(self.task.status, BugTaskStatus.FIXRELEASED) |
1059 | + |
1060 | + def test_privileged_user_can_unset_wont_fix_status(self): |
1061 | + # Privileged users can transition away from Won't Fix. |
1062 | + removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1063 | + with person_logged_in(self.person): |
1064 | + self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1065 | + self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1066 | + |
1067 | + def test_privileged_user_can_unset_wont_fix_released(self): |
1068 | + # Privileged users can transition away from Fix Released. |
1069 | + removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1070 | + with person_logged_in(self.person): |
1071 | + self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1072 | + self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1073 | + |
1074 | + def test_privileged_user_canTransitionToStatus(self): |
1075 | + # Privileged users (like owner or bug supervisor) should |
1076 | + # be able to set any status, so canTransitionToStatus should |
1077 | + # always return True. |
1078 | + self.assertEqual( |
1079 | + self.task.canTransitionToStatus( |
1080 | + BugTaskStatus.WONTFIX, self.person), |
1081 | + True) |
1082 | + self.assertEqual( |
1083 | + self.task.canTransitionToStatus( |
1084 | + BugTaskStatus.EXPIRED, self.person), |
1085 | + True) |
1086 | + self.assertEqual( |
1087 | + self.task.canTransitionToStatus( |
1088 | + BugTaskStatus.TRIAGED, self.person), |
1089 | + True) |
1090 | + self.assertEqual( |
1091 | + self.task.canTransitionToStatus( |
1092 | + BugTaskStatus.NEW, self.person), |
1093 | + True) |
1094 | + self.assertEqual( |
1095 | + self.task.canTransitionToStatus( |
1096 | + BugTaskStatus.INCOMPLETE, self.person), |
1097 | + True) |
1098 | + self.assertEqual( |
1099 | + self.task.canTransitionToStatus( |
1100 | + BugTaskStatus.OPINION, self.person), |
1101 | + True) |
1102 | + self.assertEqual( |
1103 | + self.task.canTransitionToStatus( |
1104 | + BugTaskStatus.INVALID, self.person), |
1105 | + True) |
1106 | + self.assertEqual( |
1107 | + self.task.canTransitionToStatus( |
1108 | + BugTaskStatus.CONFIRMED, self.person), |
1109 | + True) |
1110 | + self.assertEqual( |
1111 | + self.task.canTransitionToStatus( |
1112 | + BugTaskStatus.INPROGRESS, self.person), |
1113 | + True) |
1114 | + self.assertEqual( |
1115 | + self.task.canTransitionToStatus( |
1116 | + BugTaskStatus.FIXCOMMITTED, self.person), |
1117 | + True) |
1118 | + self.assertEqual( |
1119 | + self.task.canTransitionToStatus( |
1120 | + BugTaskStatus.FIXRELEASED, self.person), |
1121 | + True) |
1122 | + |
1123 | + def test_privileged_user_canTransitionToStatus_from_wontfix(self): |
1124 | + # A privileged user can transition away from Won't Fix, so |
1125 | + # canTransitionToStatus should return True. |
1126 | + removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1127 | + self.assertEqual( |
1128 | + self.task.canTransitionToStatus( |
1129 | + BugTaskStatus.NEW, self.person), |
1130 | + True) |
1131 | + |
1132 | + def test_privileged_user_canTransitionToStatus_from_fixreleased(self): |
1133 | + # A privileged user can transition away from Fix Released, so |
1134 | + # canTransitionToStatus should return True. |
1135 | + removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1136 | + self.assertEqual( |
1137 | + self.task.canTransitionToStatus( |
1138 | + BugTaskStatus.NEW, self.person), |
1139 | + True) |
1140 | + |
1141 | + |
1142 | +class TestBugTaskStatusTransitionOwnerPerson( |
1143 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1144 | + """Tests to ensure owner person can transition to any status..""" |
1145 | + |
1146 | + def makePersonAndTask(self): |
1147 | + self.person = self.factory.makePerson() |
1148 | + self.product = self.factory.makeProduct(owner=self.person) |
1149 | + self.task = self.factory.makeBugTask(target=self.product) |
1150 | + |
1151 | + |
1152 | +class TestBugTaskStatusTransitionOwnerTeam( |
1153 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1154 | + """Tests to ensure owner team can transition to any status..""" |
1155 | + |
1156 | + def makePersonAndTask(self): |
1157 | + self.person = self.factory.makePerson() |
1158 | + self.team = self.factory.makeTeam(members=[self.person]) |
1159 | + self.product = self.factory.makeProduct(owner=self.team) |
1160 | + self.task = self.factory.makeBugTask(target=self.product) |
1161 | + |
1162 | + |
1163 | +class TestBugTaskStatusTransitionBugSupervisorPerson( |
1164 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1165 | + """Tests to ensure bug supervisor person can transition to any status.""" |
1166 | + |
1167 | + def makePersonAndTask(self): |
1168 | + self.owner = self.factory.makePerson() |
1169 | + self.person = self.factory.makePerson() |
1170 | + self.product = self.factory.makeProduct(owner=self.owner) |
1171 | + self.task = self.factory.makeBugTask(target=self.product) |
1172 | + with person_logged_in(self.owner): |
1173 | + self.product.setBugSupervisor(self.person, self.person) |
1174 | + |
1175 | + |
1176 | +class TestBugTaskStatusTransitionBugSupervisorTeamMember( |
1177 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1178 | + """Tests to ensure bug supervisor team can transition to any status.""" |
1179 | + |
1180 | + def makePersonAndTask(self): |
1181 | + self.owner = self.factory.makePerson() |
1182 | + self.person = self.factory.makePerson() |
1183 | + self.team = self.factory.makeTeam(members=[self.person]) |
1184 | + self.product = self.factory.makeProduct(owner=self.owner) |
1185 | + self.task = self.factory.makeBugTask(target=self.product) |
1186 | + with person_logged_in(self.owner): |
1187 | + self.product.setBugSupervisor(self.team, self.team) |
1188 | + |
1189 | + |
1190 | +class TestBugTaskStatusTransitionBugWatchUpdater( |
1191 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1192 | + """Tests to ensure bug_watch_updater can transition to any status.""" |
1193 | + |
1194 | + def makePersonAndTask(self): |
1195 | + self.person = getUtility(ILaunchpadCelebrities).bug_watch_updater |
1196 | + self.task = self.factory.makeBugTask() |
1197 | + |
1198 | + |
1199 | +class TestBugTaskStatusTransitionBugImporter( |
1200 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1201 | + """Tests to ensure bug_importer can transition to any status.""" |
1202 | + |
1203 | + def makePersonAndTask(self): |
1204 | + self.person = getUtility(ILaunchpadCelebrities).bug_importer |
1205 | + self.task = self.factory.makeBugTask() |
1206 | + |
1207 | + |
1208 | +class TestBugTaskStatusTransitionJanitor( |
1209 | + TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1210 | + """Tests to ensure lp janitor can transition to any status.""" |
1211 | + |
1212 | + def makePersonAndTask(self): |
1213 | + self.person = getUtility(ILaunchpadCelebrities).janitor |
1214 | + self.task = self.factory.makeBugTask() |
1215 | |
1216 | === modified file 'lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt' |
1217 | --- lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt 2010-10-18 22:24:59 +0000 |
1218 | +++ lib/lp/bugs/stories/bugs/xx-bugs-advanced-search-upstream-status.txt 2010-10-25 11:13:20 +0000 |
1219 | @@ -17,7 +17,7 @@ |
1220 | |
1221 | Now if we go to the advanced search and choose to list only the bugs |
1222 | needing a bug watch, only the bugs with tasks in other contexts that |
1223 | -don't use Launchpad Bugs are shown, if at least one of those contexts |
1224 | +don't use Launchpad Bugs are shown, if at least one of those contexts |
1225 | doesn't have a bug watch. |
1226 | |
1227 | # XXX: Bjorn Tillenius 2006-07-04 bug=51853: |
1228 | @@ -97,7 +97,7 @@ |
1229 | demonstrate. |
1230 | |
1231 | >>> from canonical.launchpad.ftests import login, logout |
1232 | - >>> from lp.bugs.tests.test_bugtask_1 import ( |
1233 | + >>> from lp.bugs.model.tests.test_bugtask import ( |
1234 | ... BugTaskSearchBugsElsewhereTest) |
1235 | >>> test_helper = BugTaskSearchBugsElsewhereTest(helper_only=True) |
1236 | >>> login('test@canonical.com') |
1237 | @@ -181,8 +181,8 @@ |
1238 | linux-source-2.6.15 Medium New |
1239 | 2 Blackhole Trash folder |
1240 | — Medium New |
1241 | - |
1242 | -The user opens a bookmark for "upstream status: Show only bugs that need |
1243 | + |
1244 | +The user opens a bookmark for "upstream status: Show only bugs that need |
1245 | to be forwarded to an upstream bug tracker". |
1246 | |
1247 | >>> bookmark_params['field.status_upstream'] = 'pending_bugwatch' |
1248 | @@ -225,7 +225,7 @@ |
1249 | ... bookmark_params, True)) |
1250 | Traceback (most recent call last): |
1251 | ... |
1252 | - UnexpectedFormData: Unexpected value for field 'status_upstream'. |
1253 | + UnexpectedFormData: Unexpected value for field 'status_upstream'. |
1254 | Perhaps your bookmarks are out of date or you changed the URL by hand? |
1255 | |
1256 | |
1257 | |
1258 | === modified file 'lib/lp/bugs/subscribers/bug.py' |
1259 | --- lib/lp/bugs/subscribers/bug.py 2010-08-23 09:25:17 +0000 |
1260 | +++ lib/lp/bugs/subscribers/bug.py 2010-10-25 11:13:20 +0000 |
1261 | @@ -19,6 +19,8 @@ |
1262 | import datetime |
1263 | from operator import attrgetter |
1264 | |
1265 | +from zope.component import getUtility |
1266 | + |
1267 | from canonical.config import config |
1268 | from canonical.database.sqlbase import block_implicit_flushes |
1269 | from canonical.launchpad.helpers import get_contact_email_addresses |
1270 | @@ -34,14 +36,12 @@ |
1271 | ) |
1272 | from lp.bugs.adapters.bugdelta import BugDelta |
1273 | from lp.bugs.interfaces.bugchange import IBugChange |
1274 | +from lp.bugs.interfaces.bugtask import IBugTaskSet |
1275 | from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder |
1276 | from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
1277 | from lp.bugs.mail.newbug import generate_bug_add_email |
1278 | from lp.registry.enum import BugNotificationLevel |
1279 | from lp.registry.interfaces.person import IPerson |
1280 | -from lp.registry.interfaces.structuralsubscription import ( |
1281 | - IStructuralSubscriptionTarget, |
1282 | - ) |
1283 | |
1284 | |
1285 | @block_implicit_flushes |
1286 | @@ -179,15 +179,10 @@ |
1287 | if recipients is not None: |
1288 | recipients.addAssignee(bugtask.assignee) |
1289 | |
1290 | - if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
1291 | - also_notified_subscribers.update( |
1292 | - bugtask.target.getBugNotificationsRecipients( |
1293 | - recipients, level=level)) |
1294 | - |
1295 | - if bugtask.milestone is not None: |
1296 | - also_notified_subscribers.update( |
1297 | - bugtask.milestone.getBugNotificationsRecipients( |
1298 | - recipients, level=level)) |
1299 | + # Get structural subscribers. |
1300 | + also_notified_subscribers.update( |
1301 | + getUtility(IBugTaskSet).getStructuralSubscribers( |
1302 | + [bugtask], recipients, level)) |
1303 | |
1304 | # If the target's bug supervisor isn't set, |
1305 | # we add the owner as a subscriber. |
1306 | |
1307 | === removed file 'lib/lp/bugs/tests/test_bugtask_0.py' |
1308 | --- lib/lp/bugs/tests/test_bugtask_0.py 2010-10-21 04:19:36 +0000 |
1309 | +++ lib/lp/bugs/tests/test_bugtask_0.py 1970-01-01 00:00:00 +0000 |
1310 | @@ -1,36 +0,0 @@ |
1311 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
1312 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1313 | - |
1314 | -"""Tests for bugtask.py.""" |
1315 | - |
1316 | -__metaclass__ = type |
1317 | - |
1318 | -from doctest import ( |
1319 | - DocTestSuite, |
1320 | - ELLIPSIS, |
1321 | - NORMALIZE_WHITESPACE, |
1322 | - REPORT_NDIFF, |
1323 | - ) |
1324 | - |
1325 | - |
1326 | -def test_open_and_resolved_statuses(self): |
1327 | - """ |
1328 | - There are constants that are used to define which statuses are for |
1329 | - resolved bugs (RESOLVED_BUGTASK_STATUSES), and which are for |
1330 | - unresolved bugs (UNRESOLVED_BUGTASK_STATUSES). The two constants |
1331 | - include all statuses defined in BugTaskStatus, except for Unknown. |
1332 | - |
1333 | - >>> from lp.bugs.interfaces.bugtask import ( |
1334 | - ... BugTaskStatus, RESOLVED_BUGTASK_STATUSES, |
1335 | - ... UNRESOLVED_BUGTASK_STATUSES) |
1336 | - >>> not_included_status = set(BugTaskStatus.items).difference( |
1337 | - ... RESOLVED_BUGTASK_STATUSES + UNRESOLVED_BUGTASK_STATUSES) |
1338 | - >>> [status.name for status in not_included_status] |
1339 | - ['UNKNOWN'] |
1340 | - """ |
1341 | - |
1342 | - |
1343 | -def test_suite(): |
1344 | - suite = DocTestSuite( |
1345 | - optionflags=REPORT_NDIFF|NORMALIZE_WHITESPACE|ELLIPSIS) |
1346 | - return suite |
1347 | |
1348 | === removed file 'lib/lp/bugs/tests/test_bugtask_1.py' |
1349 | --- lib/lp/bugs/tests/test_bugtask_1.py 2010-10-21 12:43:32 +0000 |
1350 | +++ lib/lp/bugs/tests/test_bugtask_1.py 1970-01-01 00:00:00 +0000 |
1351 | @@ -1,345 +0,0 @@ |
1352 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
1353 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1354 | - |
1355 | -"""Bugtask related tests that are too complex to be readable as doctests.""" |
1356 | - |
1357 | -__metaclass__ = type |
1358 | - |
1359 | -import unittest |
1360 | - |
1361 | -from zope.component import getUtility |
1362 | - |
1363 | -from canonical.database.sqlbase import flush_database_updates |
1364 | -from canonical.launchpad.ftests import ( |
1365 | - ANONYMOUS, |
1366 | - login, |
1367 | - logout, |
1368 | - ) |
1369 | -from canonical.launchpad.webapp.interfaces import ILaunchBag |
1370 | -from canonical.testing.layers import DatabaseFunctionalLayer |
1371 | -from lp.app.enums import ServiceUsage |
1372 | -from lp.bugs.interfaces.bug import IBugSet |
1373 | -from lp.bugs.interfaces.bugtask import ( |
1374 | - BugTaskStatus, |
1375 | - IBugTaskSet, |
1376 | - IUpstreamBugTask, |
1377 | - ) |
1378 | -from lp.bugs.interfaces.bugwatch import IBugWatchSet |
1379 | -from lp.bugs.tests.bug import ( |
1380 | - create_old_bug, |
1381 | - sync_bugtasks, |
1382 | - ) |
1383 | -from lp.registry.interfaces.distribution import IDistributionSet |
1384 | -from lp.registry.interfaces.product import IProductSet |
1385 | -from lp.registry.interfaces.projectgroup import IProjectGroupSet |
1386 | -from lp.testing.factory import LaunchpadObjectFactory |
1387 | - |
1388 | - |
1389 | -class BugTaskSearchBugsElsewhereTest(unittest.TestCase): |
1390 | - """Tests for searching bugs filtering on related bug tasks. |
1391 | - |
1392 | - It also acts as a helper class, which makes related doctests more |
1393 | - readable, since they can use methods from this class. |
1394 | - """ |
1395 | - layer = DatabaseFunctionalLayer |
1396 | - |
1397 | - def __init__(self, methodName='runTest', helper_only=False): |
1398 | - """If helper_only is True, set up it only as a helper class.""" |
1399 | - if not helper_only: |
1400 | - unittest.TestCase.__init__(self, methodName=methodName) |
1401 | - |
1402 | - def setUp(self): |
1403 | - login(ANONYMOUS) |
1404 | - |
1405 | - def tearDown(self): |
1406 | - logout() |
1407 | - |
1408 | - def _getBugTaskByTarget(self, bug, target): |
1409 | - """Return a bug's bugtask for the given target.""" |
1410 | - for bugtask in bug.bugtasks: |
1411 | - if bugtask.target == target: |
1412 | - return bugtask |
1413 | - else: |
1414 | - raise AssertionError( |
1415 | - "Didn't find a %s task on bug %s." % ( |
1416 | - target.bugtargetname, bug.id)) |
1417 | - |
1418 | - def setUpBugsResolvedUpstreamTests(self): |
1419 | - """Modify some bugtasks to match the resolved upstream filter.""" |
1420 | - bugset = getUtility(IBugSet) |
1421 | - productset = getUtility(IProductSet) |
1422 | - firefox = productset.getByName("firefox") |
1423 | - thunderbird = productset.getByName("thunderbird") |
1424 | - |
1425 | - # Mark an upstream task on bug #1 "Fix Released" |
1426 | - bug_one = bugset.get(1) |
1427 | - firefox_upstream = self._getBugTaskByTarget(bug_one, firefox) |
1428 | - self.assertEqual( |
1429 | - ServiceUsage.LAUNCHPAD, |
1430 | - firefox_upstream.product.bug_tracking_usage) |
1431 | - self.old_firefox_status = firefox_upstream.status |
1432 | - firefox_upstream.transitionToStatus( |
1433 | - BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
1434 | - self.firefox_upstream = firefox_upstream |
1435 | - |
1436 | - # Mark an upstream task on bug #9 "Fix Committed" |
1437 | - bug_nine = bugset.get(9) |
1438 | - thunderbird_upstream = self._getBugTaskByTarget(bug_nine, thunderbird) |
1439 | - self.old_thunderbird_status = thunderbird_upstream.status |
1440 | - thunderbird_upstream.transitionToStatus( |
1441 | - BugTaskStatus.FIXCOMMITTED, getUtility(ILaunchBag).user) |
1442 | - self.thunderbird_upstream = thunderbird_upstream |
1443 | - |
1444 | - # Add a watch to a Debian bug for bug #2, and mark the task Fix |
1445 | - # Released. |
1446 | - bug_two = bugset.get(2) |
1447 | - current_user = getUtility(ILaunchBag).user |
1448 | - bugtaskset = getUtility(IBugTaskSet) |
1449 | - bugwatchset = getUtility(IBugWatchSet) |
1450 | - |
1451 | - # Get a debbugs watch. |
1452 | - watch_debbugs_327452 = bugwatchset.get(9) |
1453 | - self.assertEquals(watch_debbugs_327452.bugtracker.name, "debbugs") |
1454 | - self.assertEquals(watch_debbugs_327452.remotebug, "327452") |
1455 | - |
1456 | - # Associate the watch to a Fix Released task. |
1457 | - debian = getUtility(IDistributionSet).getByName("debian") |
1458 | - debian_firefox = debian.getSourcePackage("mozilla-firefox") |
1459 | - bug_two_in_debian_firefox = self._getBugTaskByTarget( |
1460 | - bug_two, debian_firefox) |
1461 | - bug_two_in_debian_firefox.bugwatch = watch_debbugs_327452 |
1462 | - bug_two_in_debian_firefox.transitionToStatus( |
1463 | - BugTaskStatus.FIXRELEASED, getUtility(ILaunchBag).user) |
1464 | - |
1465 | - flush_database_updates() |
1466 | - |
1467 | - def tearDownBugsElsewhereTests(self): |
1468 | - """Resets the modified bugtasks to their original statuses.""" |
1469 | - self.firefox_upstream.transitionToStatus( |
1470 | - self.old_firefox_status, |
1471 | - self.firefox_upstream.target.bug_supervisor) |
1472 | - self.thunderbird_upstream.transitionToStatus( |
1473 | - self.old_thunderbird_status, |
1474 | - self.firefox_upstream.target.bug_supervisor) |
1475 | - flush_database_updates() |
1476 | - |
1477 | - def assertBugTaskIsPendingBugWatchElsewhere(self, bugtask): |
1478 | - """Assert the bugtask is pending a bug watch elsewhere. |
1479 | - |
1480 | - Pending a bugwatch elsewhere means that at least one of the bugtask's |
1481 | - related task's target isn't using Malone, and that |
1482 | - related_bugtask.bugwatch is None. |
1483 | - """ |
1484 | - non_malone_using_bugtasks = [ |
1485 | - related_task for related_task in bugtask.related_tasks |
1486 | - if not related_task.target_uses_malone] |
1487 | - pending_bugwatch_bugtasks = [ |
1488 | - related_bugtask for related_bugtask in non_malone_using_bugtasks |
1489 | - if related_bugtask.bugwatch is None] |
1490 | - self.assert_( |
1491 | - len(pending_bugwatch_bugtasks) > 0, |
1492 | - 'Bugtask %s on %s has no related bug watches elsewhere.' % ( |
1493 | - bugtask.id, bugtask.target.displayname)) |
1494 | - |
1495 | - def assertBugTaskIsResolvedUpstream(self, bugtask): |
1496 | - """Make sure at least one of the related upstream tasks is resolved. |
1497 | - |
1498 | - "Resolved", for our purposes, means either that one of the related |
1499 | - tasks is an upstream task in FIXCOMMITTED or FIXRELEASED state, or |
1500 | - it is a task with a bugwatch, and in FIXCOMMITTED, FIXRELEASED, or |
1501 | - INVALID state. |
1502 | - """ |
1503 | - resolved_upstream_states = [ |
1504 | - BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED] |
1505 | - resolved_bugwatch_states = [ |
1506 | - BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED, |
1507 | - BugTaskStatus.INVALID] |
1508 | - |
1509 | - # Helper functions for the list comprehension below. |
1510 | - def _is_resolved_upstream_task(bugtask): |
1511 | - return ( |
1512 | - IUpstreamBugTask.providedBy(bugtask) and |
1513 | - bugtask.status in resolved_upstream_states) |
1514 | - |
1515 | - def _is_resolved_bugwatch_task(bugtask): |
1516 | - return ( |
1517 | - bugtask.bugwatch and bugtask.status in |
1518 | - resolved_bugwatch_states) |
1519 | - |
1520 | - resolved_related_tasks = [ |
1521 | - related_task for related_task in bugtask.related_tasks |
1522 | - if (_is_resolved_upstream_task(related_task) or |
1523 | - _is_resolved_bugwatch_task(related_task))] |
1524 | - |
1525 | - self.assert_(len(resolved_related_tasks) > 0) |
1526 | - self.assert_( |
1527 | - len(resolved_related_tasks) > 0, |
1528 | - 'Bugtask %s on %s has no resolved related tasks.' % ( |
1529 | - bugtask.id, bugtask.target.displayname)) |
1530 | - |
1531 | - def assertBugTaskIsOpenUpstream(self, bugtask): |
1532 | - """Make sure at least one of the related upstream tasks is open. |
1533 | - |
1534 | - "Open", for our purposes, means either that one of the related |
1535 | - tasks is an upstream task or a task with a bugwatch which has |
1536 | - one of the states listed in open_states. |
1537 | - """ |
1538 | - open_states = [ |
1539 | - BugTaskStatus.NEW, |
1540 | - BugTaskStatus.INCOMPLETE, |
1541 | - BugTaskStatus.CONFIRMED, |
1542 | - BugTaskStatus.INPROGRESS, |
1543 | - BugTaskStatus.UNKNOWN] |
1544 | - |
1545 | - # Helper functions for the list comprehension below. |
1546 | - def _is_open_upstream_task(bugtask): |
1547 | - return ( |
1548 | - IUpstreamBugTask.providedBy(bugtask) and |
1549 | - bugtask.status in open_states) |
1550 | - |
1551 | - def _is_open_bugwatch_task(bugtask): |
1552 | - return ( |
1553 | - bugtask.bugwatch and bugtask.status in |
1554 | - open_states) |
1555 | - |
1556 | - open_related_tasks = [ |
1557 | - related_task for related_task in bugtask.related_tasks |
1558 | - if (_is_open_upstream_task(related_task) or |
1559 | - _is_open_bugwatch_task(related_task))] |
1560 | - |
1561 | - self.assert_( |
1562 | - len(open_related_tasks) > 0, |
1563 | - 'Bugtask %s on %s has no open related tasks.' % ( |
1564 | - bugtask.id, bugtask.target.displayname)) |
1565 | - |
1566 | - def _hasUpstreamTask(self, bug): |
1567 | - """Does this bug have an upstream task associated with it? |
1568 | - |
1569 | - Returns True if yes, otherwise False. |
1570 | - """ |
1571 | - for bugtask in bug.bugtasks: |
1572 | - if IUpstreamBugTask.providedBy(bugtask): |
1573 | - return True |
1574 | - return False |
1575 | - |
1576 | - def assertShouldBeShownOnNoUpstreamTaskSearch(self, bugtask): |
1577 | - """Should the bugtask be shown in the search no upstream task search? |
1578 | - |
1579 | - Returns True if yes, otherwise False. |
1580 | - """ |
1581 | - self.assert_( |
1582 | - not self._hasUpstreamTask(bugtask.bug), |
1583 | - 'Bugtask %s on %s has upstream tasks.' % ( |
1584 | - bugtask.id, bugtask.target.displayname)) |
1585 | - |
1586 | - |
1587 | -class BugTaskSetFindExpirableBugTasksTest(unittest.TestCase): |
1588 | - """Test `BugTaskSet.findExpirableBugTasks()` behaviour.""" |
1589 | - layer = DatabaseFunctionalLayer |
1590 | - |
1591 | - def setUp(self): |
1592 | - """Setup the zope interaction and create expirable bugtasks.""" |
1593 | - login('test@canonical.com') |
1594 | - self.user = getUtility(ILaunchBag).user |
1595 | - self.distribution = getUtility(IDistributionSet).getByName('ubuntu') |
1596 | - self.distroseries = self.distribution.getSeries('hoary') |
1597 | - self.product = getUtility(IProductSet).getByName('jokosher') |
1598 | - self.productseries = self.product.getSeries('trunk') |
1599 | - self.bugtaskset = getUtility(IBugTaskSet) |
1600 | - bugtasks = [] |
1601 | - bugtasks.append( |
1602 | - create_old_bug("90 days old", 90, self.distribution)) |
1603 | - bugtasks.append( |
1604 | - self.bugtaskset.createTask( |
1605 | - bug=bugtasks[-1].bug, owner=self.user, |
1606 | - distroseries=self.distroseries)) |
1607 | - bugtasks.append( |
1608 | - create_old_bug("90 days old", 90, self.product)) |
1609 | - bugtasks.append( |
1610 | - self.bugtaskset.createTask( |
1611 | - bug=bugtasks[-1].bug, owner=self.user, |
1612 | - productseries=self.productseries)) |
1613 | - sync_bugtasks(bugtasks) |
1614 | - |
1615 | - def tearDown(self): |
1616 | - logout() |
1617 | - |
1618 | - def testSupportedTargetParam(self): |
1619 | - """The target param supports a limited set of BugTargets. |
1620 | - |
1621 | - Four BugTarget types may passed as the target argument: |
1622 | - Distribution, DistroSeries, Product, ProductSeries. |
1623 | - """ |
1624 | - supported_targets = [self.distribution, self.distroseries, |
1625 | - self.product, self.productseries] |
1626 | - for target in supported_targets: |
1627 | - expirable_bugtasks = self.bugtaskset.findExpirableBugTasks( |
1628 | - 0, self.user, target=target) |
1629 | - self.assertNotEqual(expirable_bugtasks.count(), 0, |
1630 | - "%s has %d expirable bugtasks." % |
1631 | - (self.distroseries, expirable_bugtasks.count())) |
1632 | - |
1633 | - def testUnsupportedBugTargetParam(self): |
1634 | - """Test that unsupported targets raise errors. |
1635 | - |
1636 | - Three BugTarget types are not supported because the UI does not |
1637 | - provide bug-index to link to the 'bugs that can expire' page. |
1638 | - ProjectGroup, SourcePackage, and DistributionSourcePackage will |
1639 | - raise an NotImplementedError. |
1640 | - |
1641 | - Passing an unknown bugtarget type will raise an AssertionError. |
1642 | - """ |
1643 | - project = getUtility(IProjectGroupSet).getByName('mozilla') |
1644 | - distributionsourcepackage = self.distribution.getSourcePackage( |
1645 | - 'mozilla-firefox') |
1646 | - sourcepackage = self.distroseries.getSourcePackage( |
1647 | - 'mozilla-firefox') |
1648 | - unsupported_targets = [project, distributionsourcepackage, |
1649 | - sourcepackage] |
1650 | - for target in unsupported_targets: |
1651 | - self.assertRaises( |
1652 | - NotImplementedError, self.bugtaskset.findExpirableBugTasks, |
1653 | - 0, self.user, target=target) |
1654 | - |
1655 | - # Objects that are not a known BugTarget type raise an AssertionError. |
1656 | - self.assertRaises( |
1657 | - AssertionError, self.bugtaskset.findExpirableBugTasks, |
1658 | - 0, self.user, target=[]) |
1659 | - |
1660 | - |
1661 | -class BugTaskSetTest(unittest.TestCase): |
1662 | - """Test `BugTaskSet` methods.""" |
1663 | - layer = DatabaseFunctionalLayer |
1664 | - |
1665 | - def setUp(self): |
1666 | - login(ANONYMOUS) |
1667 | - |
1668 | - def test_getBugTasks(self): |
1669 | - """ IBugTaskSet.getBugTasks() returns a dictionary mapping the given |
1670 | - bugs to their bugtasks. It does that in a single query, to avoid |
1671 | - hitting the DB again when getting the bugs' tasks. |
1672 | - """ |
1673 | - login('no-priv@canonical.com') |
1674 | - factory = LaunchpadObjectFactory() |
1675 | - bug1 = factory.makeBug() |
1676 | - factory.makeBugTask(bug1) |
1677 | - bug2 = factory.makeBug() |
1678 | - factory.makeBugTask(bug2) |
1679 | - factory.makeBugTask(bug2) |
1680 | - |
1681 | - bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks( |
1682 | - [bug1.id, bug2.id]) |
1683 | - # The bugtasks returned by getBugTasks() are exactly the same as the |
1684 | - # ones returned by bug.bugtasks, obviously. |
1685 | - self.failUnlessEqual( |
1686 | - set(bugs_and_tasks[bug1]).difference(bug1.bugtasks), |
1687 | - set([])) |
1688 | - self.failUnlessEqual( |
1689 | - set(bugs_and_tasks[bug2]).difference(bug2.bugtasks), |
1690 | - set([])) |
1691 | - |
1692 | - def test_getBugTasks_with_empty_list(self): |
1693 | - # When given an empty list of bug IDs, getBugTasks() will return an |
1694 | - # empty dictionary. |
1695 | - bugs_and_tasks = getUtility(IBugTaskSet).getBugTasks([]) |
1696 | - self.failUnlessEqual(bugs_and_tasks, {}) |
1697 | |
1698 | === removed file 'lib/lp/bugs/tests/test_bugtask_status.py' |
1699 | --- lib/lp/bugs/tests/test_bugtask_status.py 2010-10-19 19:31:07 +0000 |
1700 | +++ lib/lp/bugs/tests/test_bugtask_status.py 1970-01-01 00:00:00 +0000 |
1701 | @@ -1,356 +0,0 @@ |
1702 | -# Copyright 2009 Canonical Ltd. This software is licensed under the |
1703 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
1704 | - |
1705 | -"""Tests for bug task status transitions.""" |
1706 | - |
1707 | -__metaclass__ = type |
1708 | - |
1709 | -from zope.component import getUtility |
1710 | -from zope.security.proxy import removeSecurityProxy |
1711 | - |
1712 | -from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
1713 | -from canonical.testing.layers import LaunchpadFunctionalLayer |
1714 | -from lp.bugs.interfaces.bugtask import UserCannotEditBugTaskStatus |
1715 | -from lp.bugs.model.bugtask import BugTaskStatus |
1716 | -from lp.testing import ( |
1717 | - person_logged_in, |
1718 | - TestCaseWithFactory, |
1719 | - ) |
1720 | - |
1721 | - |
1722 | -class TestBugTaskStatusTransitionForUser(TestCaseWithFactory): |
1723 | - """Test bugtask status transitions for a regular logged in user.""" |
1724 | - |
1725 | - layer = LaunchpadFunctionalLayer |
1726 | - |
1727 | - def setUp(self): |
1728 | - super(TestBugTaskStatusTransitionForUser, self).setUp() |
1729 | - self.user = self.factory.makePerson() |
1730 | - self.task = self.factory.makeBugTask() |
1731 | - |
1732 | - def test_user_transition_all_statuses(self): |
1733 | - # A regular user should not be able to set statuses in |
1734 | - # BUG_SUPERVISOR_BUGTASK_STATUSES, but can set any |
1735 | - # other status. |
1736 | - self.assertEqual(self.task.status, BugTaskStatus.NEW) |
1737 | - with person_logged_in(self.user): |
1738 | - self.assertRaises( |
1739 | - UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
1740 | - BugTaskStatus.WONTFIX, self.user) |
1741 | - self.assertRaises( |
1742 | - UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
1743 | - BugTaskStatus.EXPIRED, self.user) |
1744 | - self.assertRaises( |
1745 | - UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
1746 | - BugTaskStatus.TRIAGED, self.user) |
1747 | - self.task.transitionToStatus(BugTaskStatus.NEW, self.user) |
1748 | - self.assertEqual(self.task.status, BugTaskStatus.NEW) |
1749 | - self.task.transitionToStatus( |
1750 | - BugTaskStatus.INCOMPLETE, self.user) |
1751 | - self.assertEqual(self.task.status, BugTaskStatus.INCOMPLETE) |
1752 | - self.task.transitionToStatus(BugTaskStatus.OPINION, self.user) |
1753 | - self.assertEqual(self.task.status, BugTaskStatus.OPINION) |
1754 | - self.task.transitionToStatus(BugTaskStatus.INVALID, self.user) |
1755 | - self.assertEqual(self.task.status, BugTaskStatus.INVALID) |
1756 | - self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.user) |
1757 | - self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1758 | - self.task.transitionToStatus( |
1759 | - BugTaskStatus.INPROGRESS, self.user) |
1760 | - self.assertEqual(self.task.status, BugTaskStatus.INPROGRESS) |
1761 | - self.task.transitionToStatus( |
1762 | - BugTaskStatus.FIXCOMMITTED, self.user) |
1763 | - self.assertEqual(self.task.status, BugTaskStatus.FIXCOMMITTED) |
1764 | - self.task.transitionToStatus( |
1765 | - BugTaskStatus.FIXRELEASED, self.user) |
1766 | - self.assertEqual(self.task.status, BugTaskStatus.FIXRELEASED) |
1767 | - |
1768 | - def test_user_cannot_unset_wont_fix_status(self): |
1769 | - # A regular user should not be able to transition a bug away |
1770 | - # from Won't Fix. |
1771 | - removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1772 | - with person_logged_in(self.user): |
1773 | - self.assertRaises( |
1774 | - UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
1775 | - BugTaskStatus.CONFIRMED, self.user) |
1776 | - |
1777 | - def test_user_cannot_unset_fix_released_status(self): |
1778 | - # A regular user should not be able to transition a bug away |
1779 | - # from Fix Released. |
1780 | - removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1781 | - with person_logged_in(self.user): |
1782 | - self.assertRaises( |
1783 | - UserCannotEditBugTaskStatus, self.task.transitionToStatus, |
1784 | - BugTaskStatus.FIXRELEASED, self.user) |
1785 | - |
1786 | - def test_user_canTransitionToStatus(self): |
1787 | - # Regular user cannot transition to BUG_SUPERVISOR_BUGTASK_STATUSES, |
1788 | - # but can transition to any other status. |
1789 | - self.assertEqual( |
1790 | - self.task.canTransitionToStatus( |
1791 | - BugTaskStatus.WONTFIX, self.user), |
1792 | - False) |
1793 | - self.assertEqual( |
1794 | - self.task.canTransitionToStatus( |
1795 | - BugTaskStatus.EXPIRED, self.user), |
1796 | - False) |
1797 | - self.assertEqual( |
1798 | - self.task.canTransitionToStatus( |
1799 | - BugTaskStatus.TRIAGED, self.user), |
1800 | - False) |
1801 | - self.assertEqual( |
1802 | - self.task.canTransitionToStatus( |
1803 | - BugTaskStatus.NEW, self.user), |
1804 | - True) |
1805 | - self.assertEqual( |
1806 | - self.task.canTransitionToStatus( |
1807 | - BugTaskStatus.INCOMPLETE, self.user), True) |
1808 | - self.assertEqual( |
1809 | - self.task.canTransitionToStatus( |
1810 | - BugTaskStatus.OPINION, self.user), |
1811 | - True) |
1812 | - self.assertEqual( |
1813 | - self.task.canTransitionToStatus( |
1814 | - BugTaskStatus.INVALID, self.user), |
1815 | - True) |
1816 | - self.assertEqual( |
1817 | - self.task.canTransitionToStatus( |
1818 | - BugTaskStatus.CONFIRMED, self.user), |
1819 | - True) |
1820 | - self.assertEqual( |
1821 | - self.task.canTransitionToStatus( |
1822 | - BugTaskStatus.INPROGRESS, self.user), |
1823 | - True) |
1824 | - self.assertEqual( |
1825 | - self.task.canTransitionToStatus( |
1826 | - BugTaskStatus.FIXCOMMITTED, self.user), |
1827 | - True) |
1828 | - self.assertEqual( |
1829 | - self.task.canTransitionToStatus( |
1830 | - BugTaskStatus.FIXRELEASED, self.user), |
1831 | - True) |
1832 | - |
1833 | - def test_user_canTransitionToStatus_from_wontfix(self): |
1834 | - # A regular user cannot transition away from Won't Fix, |
1835 | - # so canTransitionToStatus should return False. |
1836 | - removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1837 | - self.assertEqual( |
1838 | - self.task.canTransitionToStatus( |
1839 | - BugTaskStatus.NEW, self.user), |
1840 | - False) |
1841 | - |
1842 | - def test_user_canTransitionToStatus_from_fixreleased(self): |
1843 | - # A regular user cannot transition away from Fix Released, |
1844 | - # so canTransitionToStatus should return False. |
1845 | - removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1846 | - self.assertEqual( |
1847 | - self.task.canTransitionToStatus( |
1848 | - BugTaskStatus.NEW, self.user), |
1849 | - False) |
1850 | - |
1851 | - |
1852 | -class TestBugTaskStatusTransitionForPrivilegedUserBase: |
1853 | - """Base class used to test privileged users and status transitions.""" |
1854 | - |
1855 | - layer = LaunchpadFunctionalLayer |
1856 | - |
1857 | - def setUp(self): |
1858 | - super(TestBugTaskStatusTransitionForPrivilegedUserBase, self).setUp() |
1859 | - # Creation of task and target are deferred to subclasses. |
1860 | - self.task = None |
1861 | - self.person = None |
1862 | - self.makePersonAndTask() |
1863 | - |
1864 | - def makePersonAndTask(self): |
1865 | - """Create a bug task and privileged person for this task. |
1866 | - |
1867 | - This method is implemented by subclasses to correctly setup |
1868 | - each test. |
1869 | - """ |
1870 | - raise NotImplementedError(self.makePersonAndTask) |
1871 | - |
1872 | - def test_privileged_user_transition_any_status(self): |
1873 | - # Privileged users (like owner or bug supervisor) should |
1874 | - # be able to set any status. |
1875 | - with person_logged_in(self.person): |
1876 | - self.task.transitionToStatus(BugTaskStatus.WONTFIX, self.person) |
1877 | - self.assertEqual(self.task.status, BugTaskStatus.WONTFIX) |
1878 | - self.task.transitionToStatus(BugTaskStatus.EXPIRED, self.person) |
1879 | - self.assertEqual(self.task.status, BugTaskStatus.EXPIRED) |
1880 | - self.task.transitionToStatus(BugTaskStatus.TRIAGED, self.person) |
1881 | - self.assertEqual(self.task.status, BugTaskStatus.TRIAGED) |
1882 | - self.task.transitionToStatus(BugTaskStatus.NEW, self.person) |
1883 | - self.assertEqual(self.task.status, BugTaskStatus.NEW) |
1884 | - self.task.transitionToStatus( |
1885 | - BugTaskStatus.INCOMPLETE, self.person) |
1886 | - self.assertEqual(self.task.status, BugTaskStatus.INCOMPLETE) |
1887 | - self.task.transitionToStatus(BugTaskStatus.OPINION, self.person) |
1888 | - self.assertEqual(self.task.status, BugTaskStatus.OPINION) |
1889 | - self.task.transitionToStatus(BugTaskStatus.INVALID, self.person) |
1890 | - self.assertEqual(self.task.status, BugTaskStatus.INVALID) |
1891 | - self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1892 | - self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1893 | - self.task.transitionToStatus( |
1894 | - BugTaskStatus.INPROGRESS, self.person) |
1895 | - self.assertEqual(self.task.status, BugTaskStatus.INPROGRESS) |
1896 | - self.task.transitionToStatus( |
1897 | - BugTaskStatus.FIXCOMMITTED, self.person) |
1898 | - self.assertEqual(self.task.status, BugTaskStatus.FIXCOMMITTED) |
1899 | - self.task.transitionToStatus( |
1900 | - BugTaskStatus.FIXRELEASED, self.person) |
1901 | - self.assertEqual(self.task.status, BugTaskStatus.FIXRELEASED) |
1902 | - |
1903 | - def test_privileged_user_can_unset_wont_fix_status(self): |
1904 | - # Privileged users can transition away from Won't Fix. |
1905 | - removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1906 | - with person_logged_in(self.person): |
1907 | - self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1908 | - self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1909 | - |
1910 | - def test_privileged_user_can_unset_wont_fix_released(self): |
1911 | - # Privileged users can transition away from Fix Released. |
1912 | - removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1913 | - with person_logged_in(self.person): |
1914 | - self.task.transitionToStatus(BugTaskStatus.CONFIRMED, self.person) |
1915 | - self.assertEqual(self.task.status, BugTaskStatus.CONFIRMED) |
1916 | - |
1917 | - def test_privileged_user_canTransitionToStatus(self): |
1918 | - # Privileged users (like owner or bug supervisor) should |
1919 | - # be able to set any status, so canTransitionToStatus should |
1920 | - # always return True. |
1921 | - self.assertEqual( |
1922 | - self.task.canTransitionToStatus( |
1923 | - BugTaskStatus.WONTFIX, self.person), |
1924 | - True) |
1925 | - self.assertEqual( |
1926 | - self.task.canTransitionToStatus( |
1927 | - BugTaskStatus.EXPIRED, self.person), |
1928 | - True) |
1929 | - self.assertEqual( |
1930 | - self.task.canTransitionToStatus( |
1931 | - BugTaskStatus.TRIAGED, self.person), |
1932 | - True) |
1933 | - self.assertEqual( |
1934 | - self.task.canTransitionToStatus( |
1935 | - BugTaskStatus.NEW, self.person), |
1936 | - True) |
1937 | - self.assertEqual( |
1938 | - self.task.canTransitionToStatus( |
1939 | - BugTaskStatus.INCOMPLETE, self.person), |
1940 | - True) |
1941 | - self.assertEqual( |
1942 | - self.task.canTransitionToStatus( |
1943 | - BugTaskStatus.OPINION, self.person), |
1944 | - True) |
1945 | - self.assertEqual( |
1946 | - self.task.canTransitionToStatus( |
1947 | - BugTaskStatus.INVALID, self.person), |
1948 | - True) |
1949 | - self.assertEqual( |
1950 | - self.task.canTransitionToStatus( |
1951 | - BugTaskStatus.CONFIRMED, self.person), |
1952 | - True) |
1953 | - self.assertEqual( |
1954 | - self.task.canTransitionToStatus( |
1955 | - BugTaskStatus.INPROGRESS, self.person), |
1956 | - True) |
1957 | - self.assertEqual( |
1958 | - self.task.canTransitionToStatus( |
1959 | - BugTaskStatus.FIXCOMMITTED, self.person), |
1960 | - True) |
1961 | - self.assertEqual( |
1962 | - self.task.canTransitionToStatus( |
1963 | - BugTaskStatus.FIXRELEASED, self.person), |
1964 | - True) |
1965 | - |
1966 | - def test_privileged_user_canTransitionToStatus_from_wontfix(self): |
1967 | - # A privileged user can transition away from Won't Fix, so |
1968 | - # canTransitionToStatus should return True. |
1969 | - removeSecurityProxy(self.task).status = BugTaskStatus.WONTFIX |
1970 | - self.assertEqual( |
1971 | - self.task.canTransitionToStatus( |
1972 | - BugTaskStatus.NEW, self.person), |
1973 | - True) |
1974 | - |
1975 | - def test_privileged_user_canTransitionToStatus_from_fixreleased(self): |
1976 | - # A privileged user can transition away from Fix Released, so |
1977 | - # canTransitionToStatus should return True. |
1978 | - removeSecurityProxy(self.task).status = BugTaskStatus.FIXRELEASED |
1979 | - self.assertEqual( |
1980 | - self.task.canTransitionToStatus( |
1981 | - BugTaskStatus.NEW, self.person), |
1982 | - True) |
1983 | - |
1984 | - |
1985 | -class TestBugTaskStatusTransitionOwnerPerson( |
1986 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1987 | - """Tests to ensure owner person can transition to any status..""" |
1988 | - |
1989 | - def makePersonAndTask(self): |
1990 | - self.person = self.factory.makePerson() |
1991 | - self.product = self.factory.makeProduct(owner=self.person) |
1992 | - self.task = self.factory.makeBugTask(target=self.product) |
1993 | - |
1994 | - |
1995 | -class TestBugTaskStatusTransitionOwnerTeam( |
1996 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
1997 | - """Tests to ensure owner team can transition to any status..""" |
1998 | - |
1999 | - def makePersonAndTask(self): |
2000 | - self.person = self.factory.makePerson() |
2001 | - self.team = self.factory.makeTeam(members=[self.person]) |
2002 | - self.product = self.factory.makeProduct(owner=self.team) |
2003 | - self.task = self.factory.makeBugTask(target=self.product) |
2004 | - |
2005 | - |
2006 | -class TestBugTaskStatusTransitionBugSupervisorPerson( |
2007 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
2008 | - """Tests to ensure bug supervisor person can transition to any status.""" |
2009 | - |
2010 | - def makePersonAndTask(self): |
2011 | - self.owner = self.factory.makePerson() |
2012 | - self.person = self.factory.makePerson() |
2013 | - self.product = self.factory.makeProduct(owner=self.owner) |
2014 | - self.task = self.factory.makeBugTask(target=self.product) |
2015 | - with person_logged_in(self.owner): |
2016 | - self.product.setBugSupervisor(self.person, self.person) |
2017 | - |
2018 | - |
2019 | -class TestBugTaskStatusTransitionBugSupervisorTeamMember( |
2020 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
2021 | - """Tests to ensure bug supervisor team can transition to any status.""" |
2022 | - |
2023 | - def makePersonAndTask(self): |
2024 | - self.owner = self.factory.makePerson() |
2025 | - self.person = self.factory.makePerson() |
2026 | - self.team = self.factory.makeTeam(members=[self.person]) |
2027 | - self.product = self.factory.makeProduct(owner=self.owner) |
2028 | - self.task = self.factory.makeBugTask(target=self.product) |
2029 | - with person_logged_in(self.owner): |
2030 | - self.product.setBugSupervisor(self.team, self.team) |
2031 | - |
2032 | - |
2033 | -class TestBugTaskStatusTransitionBugWatchUpdater( |
2034 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
2035 | - """Tests to ensure bug_watch_updater can transition to any status.""" |
2036 | - |
2037 | - def makePersonAndTask(self): |
2038 | - self.person = getUtility(ILaunchpadCelebrities).bug_watch_updater |
2039 | - self.task = self.factory.makeBugTask() |
2040 | - |
2041 | - |
2042 | -class TestBugTaskStatusTransitionBugImporter( |
2043 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
2044 | - """Tests to ensure bug_importer can transition to any status.""" |
2045 | - |
2046 | - def makePersonAndTask(self): |
2047 | - self.person = getUtility(ILaunchpadCelebrities).bug_importer |
2048 | - self.task = self.factory.makeBugTask() |
2049 | - |
2050 | - |
2051 | -class TestBugTaskStatusTransitionJanitor( |
2052 | - TestBugTaskStatusTransitionForPrivilegedUserBase, TestCaseWithFactory): |
2053 | - """Tests to ensure lp janitor can transition to any status.""" |
2054 | - |
2055 | - def makePersonAndTask(self): |
2056 | - self.person = getUtility(ILaunchpadCelebrities).janitor |
2057 | - self.task = self.factory.makeBugTask() |
2058 | |
2059 | === added file 'lib/lp/code/interfaces/branchmergequeue.py' |
2060 | --- lib/lp/code/interfaces/branchmergequeue.py 1970-01-01 00:00:00 +0000 |
2061 | +++ lib/lp/code/interfaces/branchmergequeue.py 2010-10-20 15:32:38 +0000 |
2062 | @@ -0,0 +1,115 @@ |
2063 | +# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
2064 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2065 | + |
2066 | +"""Branch merge queue interfaces.""" |
2067 | + |
2068 | +__metaclass__ = type |
2069 | + |
2070 | +__all__ = [ |
2071 | + 'IBranchMergeQueue', |
2072 | + 'IBranchMergeQueueSource', |
2073 | + ] |
2074 | + |
2075 | +from lazr.restful.declarations import ( |
2076 | + export_as_webservice_entry, |
2077 | + export_write_operation, |
2078 | + exported, |
2079 | + mutator_for, |
2080 | + operation_parameters, |
2081 | + ) |
2082 | +from lazr.restful.fields import ( |
2083 | + CollectionField, |
2084 | + Reference, |
2085 | + ) |
2086 | +from zope.interface import Interface |
2087 | +from zope.schema import ( |
2088 | + Datetime, |
2089 | + Int, |
2090 | + Text, |
2091 | + TextLine, |
2092 | + ) |
2093 | + |
2094 | +from canonical.launchpad import _ |
2095 | +from lp.services.fields import ( |
2096 | + PersonChoice, |
2097 | + PublicPersonChoice, |
2098 | + ) |
2099 | + |
2100 | + |
2101 | +class IBranchMergeQueue(Interface): |
2102 | + """An interface for managing branch merges.""" |
2103 | + |
2104 | + export_as_webservice_entry() |
2105 | + |
2106 | + id = Int(title=_('ID'), readonly=True, required=True) |
2107 | + |
2108 | + registrant = exported( |
2109 | + PublicPersonChoice( |
2110 | + title=_("The user that registered the branch."), |
2111 | + required=True, readonly=True, |
2112 | + vocabulary='ValidPersonOrTeam')) |
2113 | + |
2114 | + owner = exported( |
2115 | + PersonChoice( |
2116 | + title=_('Owner'), |
2117 | + required=True, readonly=True, |
2118 | + vocabulary='UserTeamsParticipationPlusSelf', |
2119 | + description=_("The owner of the merge queue."))) |
2120 | + |
2121 | + name = exported( |
2122 | + TextLine( |
2123 | + title=_('Name'), required=True, |
2124 | + description=_( |
2125 | + "Keep very short, unique, and descriptive, because it will " |
2126 | + "be used in URLs. " |
2127 | + "Examples: main, devel, release-1.0, gnome-vfs."))) |
2128 | + |
2129 | + description = exported( |
2130 | + Text( |
2131 | + title=_('Description'), required=False, |
2132 | + description=_( |
2133 | + 'A short description of the purpose of this merge queue.'))) |
2134 | + |
2135 | + configuration = exported( |
2136 | + TextLine( |
2137 | + title=_('Configuration'), required=False, readonly=True, |
2138 | + description=_( |
2139 | + "A JSON string of configuration values."))) |
2140 | + |
2141 | + date_created = exported( |
2142 | + Datetime( |
2143 | + title=_('Date Created'), |
2144 | + required=True, |
2145 | + readonly=True)) |
2146 | + |
2147 | + branches = exported( |
2148 | + CollectionField( |
2149 | + title=_('Dependent Branches'), |
2150 | + description=_( |
2151 | + 'A collection of branches that this queue manages.'), |
2152 | + readonly=True, |
2153 | + value_type=Reference(Interface))) |
2154 | + |
2155 | + @mutator_for(configuration) |
2156 | + @operation_parameters( |
2157 | + config=TextLine(title=_("A JSON string of configuration values."))) |
2158 | + @export_write_operation() |
2159 | + def setMergeQueueConfig(config): |
2160 | + """Set the JSON string configuration of the merge queue. |
2161 | + |
2162 | + :param config: A JSON string of configuration values. |
2163 | + """ |
2164 | + |
2165 | + |
2166 | +class IBranchMergeQueueSource(Interface): |
2167 | + |
2168 | + def new(name, owner, registrant, description, configuration, branches): |
2169 | + """Create a new IBranchMergeQueue object. |
2170 | + |
2171 | + :param name: The name of the branch merge queue. |
2172 | + :param description: A description of queue. |
2173 | + :param configuration: A JSON string of configuration values. |
2174 | + :param owner: The owner of the queue. |
2175 | + :param registrant: The registrant of the queue. |
2176 | + :param branches: A list of branches to add to the queue. |
2177 | + """ |
2178 | |
2179 | === added file 'lib/lp/code/model/tests/test_branchmergequeue.py' |
2180 | --- lib/lp/code/model/tests/test_branchmergequeue.py 1970-01-01 00:00:00 +0000 |
2181 | +++ lib/lp/code/model/tests/test_branchmergequeue.py 2010-10-20 17:48:51 +0000 |
2182 | @@ -0,0 +1,155 @@ |
2183 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
2184 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
2185 | + |
2186 | +"""Unit tests for methods of BranchMergeQueue.""" |
2187 | + |
2188 | +from __future__ import with_statement |
2189 | + |
2190 | +import simplejson |
2191 | + |
2192 | +import transaction |
2193 | + |
2194 | +from canonical.launchpad.interfaces.lpstorm import IStore |
2195 | +from canonical.launchpad.webapp.testing import verifyObject |
2196 | +from canonical.testing.layers import ( |
2197 | + AppServerLayer, |
2198 | + DatabaseFunctionalLayer, |
2199 | + ) |
2200 | +from lp.code.errors import InvalidMergeQueueConfig |
2201 | +from lp.code.interfaces.branchmergequeue import IBranchMergeQueue |
2202 | +from lp.code.model.branchmergequeue import BranchMergeQueue |
2203 | +from lp.testing import ( |
2204 | + ANONYMOUS, |
2205 | + person_logged_in, |
2206 | + launchpadlib_for, |
2207 | + TestCaseWithFactory, |
2208 | + ws_object, |
2209 | + ) |
2210 | + |
2211 | + |
2212 | +class TestBranchMergeQueueInterface(TestCaseWithFactory): |
2213 | + """Test IBranchMergeQueue interface.""" |
2214 | + |
2215 | + layer = DatabaseFunctionalLayer |
2216 | + |
2217 | + def test_implements_interface(self): |
2218 | + queue = self.factory.makeBranchMergeQueue() |
2219 | + IStore(BranchMergeQueue).add(queue) |
2220 | + verifyObject(IBranchMergeQueue, queue) |
2221 | + |
2222 | + |
2223 | +class TestBranchMergeQueueSource(TestCaseWithFactory): |
2224 | + """Test the methods of IBranchMergeQueueSource.""" |
2225 | + |
2226 | + layer = DatabaseFunctionalLayer |
2227 | + |
2228 | + def test_new(self): |
2229 | + owner = self.factory.makePerson() |
2230 | + name = u'SooperQueue' |
2231 | + description = u'This is Sooper Queue' |
2232 | + config = unicode(simplejson.dumps({'test': 'make check'})) |
2233 | + |
2234 | + queue = BranchMergeQueue.new( |
2235 | + name, owner, owner, description, config) |
2236 | + |
2237 | + self.assertEqual(queue.name, name) |
2238 | + self.assertEqual(queue.owner, owner) |
2239 | + self.assertEqual(queue.registrant, owner) |
2240 | + self.assertEqual(queue.description, description) |
2241 | + self.assertEqual(queue.configuration, config) |
2242 | + |
2243 | + |
2244 | +class TestBranchMergeQueue(TestCaseWithFactory): |
2245 | + """Test the functions of the BranchMergeQueue.""" |
2246 | + |
2247 | + layer = DatabaseFunctionalLayer |
2248 | + |
2249 | + def test_branches(self): |
2250 | + """Test that a merge queue can get all its managed branches.""" |
2251 | + store = IStore(BranchMergeQueue) |
2252 | + |
2253 | + queue = self.factory.makeBranchMergeQueue() |
2254 | + store.add(queue) |
2255 | + |
2256 | + branch = self.factory.makeBranch() |
2257 | + store.add(branch) |
2258 | + with person_logged_in(branch.owner): |
2259 | + branch.addToQueue(queue) |
2260 | + |
2261 | + self.assertEqual( |
2262 | + list(queue.branches), |
2263 | + [branch]) |
2264 | + |
2265 | + def test_setMergeQueueConfig(self): |
2266 | + """Test that the configuration is set properly.""" |
2267 | + queue = self.factory.makeBranchMergeQueue() |
2268 | + config = unicode(simplejson.dumps({ |
2269 | + 'test': 'make test'})) |
2270 | + |
2271 | + queue.setMergeQueueConfig(config) |
2272 | + |
2273 | + self.assertEqual(queue.configuration, config) |
2274 | + |
2275 | + def test_setMergeQueueConfig_invalid_json(self): |
2276 | + """Test that invalid json can't be set as the config.""" |
2277 | + queue = self.factory.makeBranchMergeQueue() |
2278 | + self.assertRaises( |
2279 | + InvalidMergeQueueConfig, |
2280 | + queue.setMergeQueueConfig, |
2281 | + 'abc') |
2282 | + |
2283 | + |
2284 | +class TestWebservice(TestCaseWithFactory): |
2285 | + |
2286 | + layer = AppServerLayer |
2287 | + |
2288 | + def test_properties(self): |
2289 | + """Test that the correct properties are exposed.""" |
2290 | + with person_logged_in(ANONYMOUS): |
2291 | + name = u'teh-queue' |
2292 | + description = u'Oh hai! I are a queues' |
2293 | + configuration = unicode(simplejson.dumps({'test': 'make check'})) |
2294 | + |
2295 | + queuer = self.factory.makePerson() |
2296 | + db_queue = self.factory.makeBranchMergeQueue( |
2297 | + registrant=queuer, owner=queuer, name=name, |
2298 | + description=description, |
2299 | + configuration=configuration) |
2300 | + branch1 = self.factory.makeBranch() |
2301 | + with person_logged_in(branch1.owner): |
2302 | + branch1.addToQueue(db_queue) |
2303 | + branch2 = self.factory.makeBranch() |
2304 | + with person_logged_in(branch2.owner): |
2305 | + branch2.addToQueue(db_queue) |
2306 | + launchpad = launchpadlib_for('test', db_queue.owner, |
2307 | + service_root="http://api.launchpad.dev:8085") |
2308 | + transaction.commit() |
2309 | + |
2310 | + queuer = ws_object(launchpad, queuer) |
2311 | + queue = ws_object(launchpad, db_queue) |
2312 | + branch1 = ws_object(launchpad, branch1) |
2313 | + branch2 = ws_object(launchpad, branch2) |
2314 | + |
2315 | + self.assertEqual(queue.registrant, queuer) |
2316 | + self.assertEqual(queue.owner, queuer) |
2317 | + self.assertEqual(queue.name, name) |
2318 | + self.assertEqual(queue.description, description) |
2319 | + self.assertEqual(queue.configuration, configuration) |
2320 | + self.assertEqual(queue.date_created, db_queue.date_created) |
2321 | + self.assertEqual(len(queue.branches), 2) |
2322 | + |
2323 | + def test_set_configuration(self): |
2324 | + """Test the mutator for setting configuration.""" |
2325 | + with person_logged_in(ANONYMOUS): |
2326 | + db_queue = self.factory.makeBranchMergeQueue() |
2327 | + launchpad = launchpadlib_for('test', db_queue.owner, |
2328 | + service_root="http://api.launchpad.dev:8085") |
2329 | + |
2330 | + configuration = simplejson.dumps({'test': 'make check'}) |
2331 | + |
2332 | + queue = ws_object(launchpad, db_queue) |
2333 | + queue.configuration = configuration |
2334 | + queue.lp_save() |
2335 | + |
2336 | + queue2 = ws_object(launchpad, db_queue) |
2337 | + self.assertEqual(queue2.configuration, configuration) |
2338 | |
2339 | === modified file 'lib/lp/registry/doc/structural-subscriptions.txt' |
2340 | --- lib/lp/registry/doc/structural-subscriptions.txt 2010-10-22 13:43:50 +0000 |
2341 | +++ lib/lp/registry/doc/structural-subscriptions.txt 2010-10-25 11:13:20 +0000 |
2342 | @@ -86,11 +86,7 @@ |
2343 | When notifying subscribers of bug activity, both subscribers to the |
2344 | target and to the target's parent are notified. |
2345 | |
2346 | - >>> from canonical.launchpad.ftests import syncUpdate |
2347 | >>> from lp.registry.enum import BugNotificationLevel |
2348 | - >>> from lp.registry.interfaces.structuralsubscription import BlueprintNotificationLevel |
2349 | - >>> from lp.bugs.mail.bugnotificationrecipients import ( |
2350 | - ... BugNotificationRecipients) |
2351 | |
2352 | We define some utility functions for printing out bug subscriptions and |
2353 | the recipients for the notifications they generate. |
2354 | @@ -114,89 +110,6 @@ |
2355 | >>> ubuntu_sub.bug_notification_level = BugNotificationLevel.COMMENTS |
2356 | >>> evolution_sub.bug_notification_level = BugNotificationLevel.COMMENTS |
2357 | |
2358 | -`getBugNotificationsRecipients` returns all the bug subscribers to the |
2359 | -target and its parent, and adds the rationale for the subscriptions to |
2360 | -the recipients set. Each subscriber is only added once. |
2361 | - |
2362 | - >>> recipients = BugNotificationRecipients() |
2363 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
2364 | - ... recipients=recipients) |
2365 | - >>> print_bug_subscriptions(ubuntu.bug_subscriptions) |
2366 | - name12 |
2367 | - >>> print_bug_subscriptions(evolution_package.bug_subscriptions) |
2368 | - name12 |
2369 | - >>> print_bug_subscribers(bug_subscribers) |
2370 | - name12 |
2371 | - >>> print_bug_recipients(recipients) |
2372 | - name12 "Subscriber (evolution in ubuntu)" |
2373 | - |
2374 | -Foo Bar subscribes to Ubuntu. |
2375 | - |
2376 | - >>> login('foo.bar@canonical.com') |
2377 | - >>> foobar_subscription = ubuntu.addBugSubscription(foobar, foobar) |
2378 | - >>> recipients = BugNotificationRecipients() |
2379 | - |
2380 | -The set of subscribers to the evolution package for ubuntu now includes |
2381 | -both subscribers to the package, and subscribers to the distribution. |
2382 | - |
2383 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
2384 | - ... recipients=recipients) |
2385 | - >>> print_bug_recipients(recipients) |
2386 | - name16 "Subscriber (Ubuntu)" |
2387 | - name12 "Subscriber (evolution in ubuntu)" |
2388 | - |
2389 | -We can pass the parameter `level` to getBugNotificationsRecipients(). |
2390 | -Subscribers whose subscription level is lower than the given parameter |
2391 | -are not returned. |
2392 | - |
2393 | - >>> foobar_subscription.bug_notification_level = ( |
2394 | - ... BugNotificationLevel.METADATA) |
2395 | - >>> recipients = BugNotificationRecipients() |
2396 | - >>> bug_subscribers = evolution_package.getBugNotificationsRecipients( |
2397 | - ... recipients=recipients, level=BugNotificationLevel.COMMENTS) |
2398 | - >>> print_bug_recipients(recipients) |
2399 | - name12 "Subscriber (evolution in ubuntu)" |
2400 | - |
2401 | -We remove Sample Person's bug subscription to the package. |
2402 | - |
2403 | - >>> evolution_sub.blueprint_notification_level = ( |
2404 | - ... BlueprintNotificationLevel.METADATA) |
2405 | - >>> evolution_package.removeBugSubscription(sampleperson, sampleperson) |
2406 | - >>> ubuntu.removeBugSubscription(sampleperson, sampleperson) |
2407 | - >>> syncUpdate(evolution_sub) |
2408 | - |
2409 | -Sample Person is no longer a subscriber to the package, but Foo Bar |
2410 | -is still a subscriber, by being subscribed to Ubuntu. |
2411 | - |
2412 | - >>> print_bug_subscribers( |
2413 | - ... evolution_package.getBugNotificationsRecipients( |
2414 | - ... recipients=recipients)) |
2415 | - name16 |
2416 | - |
2417 | -A project is the parent of each of its products. |
2418 | - |
2419 | -Fireox does not have any subscribers. |
2420 | - |
2421 | - >>> print_bug_subscribers(firefox.getBugNotificationsRecipients()) |
2422 | - |
2423 | -Mozilla is the parent of Fireox. |
2424 | - |
2425 | - >>> from lp.registry.interfaces.projectgroup import IProjectGroupSet |
2426 | - >>> mozilla = getUtility(IProjectGroupSet).getByName('mozilla') |
2427 | - >>> print firefox.parent_subscription_target.displayname |
2428 | - the Mozilla Project |
2429 | - |
2430 | -Foobar subscribes to bug notificatios for Mozilla. |
2431 | - |
2432 | - >>> mozilla.addBugSubscription(foobar, foobar) |
2433 | - <StructuralSubscription at ...> |
2434 | - |
2435 | -As a result of subscribing to Mozilla, Foobar is now a subscriber of |
2436 | -Firefox. |
2437 | - |
2438 | - >>> print_bug_subscribers(firefox.getBugNotificationsRecipients()) |
2439 | - name16 |
2440 | - |
2441 | |
2442 | Target type display |
2443 | =================== |
2444 | |
2445 | === modified file 'lib/lp/registry/interfaces/structuralsubscription.py' |
2446 | --- lib/lp/registry/interfaces/structuralsubscription.py 2010-10-07 10:06:55 +0000 |
2447 | +++ lib/lp/registry/interfaces/structuralsubscription.py 2010-10-25 11:13:20 +0000 |
2448 | @@ -181,20 +181,6 @@ |
2449 | def getSubscription(person): |
2450 | """Return the subscription for `person`, if it exists.""" |
2451 | |
2452 | - def getBugNotificationsRecipients(recipients=None, level=None): |
2453 | - """Return the set of bug subscribers to this target. |
2454 | - |
2455 | - :param recipients: If recipients is not None, a rationale |
2456 | - is added for each subscriber. |
2457 | - :type recipients: `INotificationRecipientSet` |
2458 | - 'param level: If level is not None, only strucutral |
2459 | - subscribers with a subscrition level greater or equal |
2460 | - to the given value are returned. |
2461 | - :type level: `BugNotificationLevel` |
2462 | - :return: An `INotificationRecipientSet` instance containing |
2463 | - the bug subscribers. |
2464 | - """ |
2465 | - |
2466 | target_type_display = Attribute("The type of the target, for display.") |
2467 | |
2468 | def userHasBugSubscriptions(user): |
2469 | |
2470 | === modified file 'lib/lp/registry/model/structuralsubscription.py' |
2471 | --- lib/lp/registry/model/structuralsubscription.py 2010-10-07 10:06:55 +0000 |
2472 | +++ lib/lp/registry/model/structuralsubscription.py 2010-10-25 11:13:20 +0000 |
2473 | @@ -448,24 +448,6 @@ |
2474 | return StructuralSubscription.select( |
2475 | query, orderBy='Person.displayname', clauseTables=['Person']) |
2476 | |
2477 | - def getBugNotificationsRecipients(self, recipients=None, level=None): |
2478 | - """See `IStructuralSubscriptionTarget`.""" |
2479 | - if level is None: |
2480 | - subscriptions = self.bug_subscriptions |
2481 | - else: |
2482 | - subscriptions = self.getSubscriptions( |
2483 | - min_bug_notification_level=level) |
2484 | - subscribers = set( |
2485 | - subscription.subscriber for subscription in subscriptions) |
2486 | - if recipients is not None: |
2487 | - for subscriber in subscribers: |
2488 | - recipients.addStructuralSubscriber(subscriber, self) |
2489 | - parent = self.parent_subscription_target |
2490 | - if parent is not None: |
2491 | - subscribers.update( |
2492 | - parent.getBugNotificationsRecipients(recipients, level)) |
2493 | - return subscribers |
2494 | - |
2495 | @property |
2496 | def bug_subscriptions(self): |
2497 | """See `IStructuralSubscriptionTarget`.""" |