~launchpad-pqm/launchpad/devel

13811.2.1 by Jeroen Vermeulen
Fix some of the lint people left in the past few days.
1
# Copyright 2010-2011 Canonical Ltd.  This software is licensed under the
11526.4.35 by Gavin Panella
Add StructuralSubscription.bug_filters.
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
4
"""Tests for `StructuralSubscription`."""
5
6
__metaclass__ = type
7
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
8
from storm.store import (
9
    EmptyResultSet,
10
    ResultSet,
11
    Store,
12
    )
13
from testtools.matchers import StartsWith
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
14
from zope.security.interfaces import Unauthorized
15
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
16
from lp.bugs.enum import BugNotificationLevel
17
from lp.bugs.interfaces.bugtask import (
18
    BugTaskImportance,
19
    BugTaskStatus,
20
    )
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
21
from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
22
from lp.bugs.model.bugsubscriptionfilter import (
23
    BugSubscriptionFilter,
24
    BugSubscriptionFilterMute,
7675.1094.5 by Graham Binns
Added tests to cover errors when you're not allowed to mute.
25
    MuteNotAllowed,
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
26
    )
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
27
from lp.bugs.model.structuralsubscription import (
28
    get_structural_subscribers,
29
    get_structural_subscription_targets,
13811.2.1 by Jeroen Vermeulen
Fix some of the lint people left in the past few days.
30
    get_structural_subscriptions_for_bug,
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
31
    )
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
32
from lp.testing import (
33
    anonymous_logged_in,
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
34
    login_person,
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
35
    person_logged_in,
36
    TestCaseWithFactory,
37
    )
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
38
from lp.testing.factory import is_security_proxied_or_harmless
14612.2.1 by William Grant
format-imports on lib/. So many imports.
39
from lp.testing.layers import (
40
    DatabaseFunctionalLayer,
41
    LaunchpadFunctionalLayer,
42
    )
11526.4.35 by Gavin Panella
Add StructuralSubscription.bug_filters.
43
44
45
class TestStructuralSubscription(TestCaseWithFactory):
46
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
47
    layer = DatabaseFunctionalLayer
11526.4.35 by Gavin Panella
Add StructuralSubscription.bug_filters.
48
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
49
    def setUp(self):
50
        super(TestStructuralSubscription, self).setUp()
51
        self.product = self.factory.makeProduct()
52
        with person_logged_in(self.product.owner):
53
            self.subscription = self.product.addSubscription(
54
                self.product.owner, self.product.owner)
12399.3.1 by Danilo Segan
Automatically create an empty BugSubscriptionFilter for every StructuralSubscription.
55
        self.original_filter = self.subscription.bug_filters[0]
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
56
7675.1030.2 by Gary Poster
make it possible to delete a structural subscription that has a filter, by adding a delete method to structural subscriptions that performs the cascading delete.
57
    def test_delete_requires_Edit_permission(self):
58
        # delete() is only available to the subscriber.
7675.1030.4 by Gary Poster
improve comments, per review
59
        # We use a lambda here because a security proxy around
60
        # self.subscription is giving us the behavior we want to
61
        # demonstrate.  Merely accessing the "delete" name raises
62
        # Unauthorized, before the method is even called.  Therefore,
63
        # we use a lambda to make the trigger happen within "assertRaises".
7675.1030.2 by Gary Poster
make it possible to delete a structural subscription that has a filter, by adding a delete method to structural subscriptions that performs the cascading delete.
64
        with anonymous_logged_in():
65
            self.assertRaises(Unauthorized, lambda: self.subscription.delete)
66
        with person_logged_in(self.factory.makePerson()):
67
            self.assertRaises(Unauthorized, lambda: self.subscription.delete)
68
69
    def test_simple_delete(self):
70
        with person_logged_in(self.product.owner):
71
            self.subscription.delete()
72
            self.assertEqual(
73
                self.product.getSubscription(self.product.owner), None)
74
75
    def test_delete_cascades_to_filters(self):
76
        with person_logged_in(self.product.owner):
77
            subscription_id = self.subscription.id
78
            self.subscription.newBugFilter()
79
            self.subscription.delete()
80
            self.assertEqual(
81
                self.product.getSubscription(self.product.owner), None)
82
            store = Store.of(self.product)
7675.1030.4 by Gary Poster
improve comments, per review
83
            # We know that the filter is gone, because we know the
84
            # subscription is gone, and the database would have
85
            # prevented the deletion of a subscription without first
86
            # deleting the filters.  We'll double-check, to be sure.
7675.1030.2 by Gary Poster
make it possible to delete a structural subscription that has a filter, by adding a delete method to structural subscriptions that performs the cascading delete.
87
            self.assertEqual(
88
                store.find(
89
                    BugSubscriptionFilter,
90
                    BugSubscriptionFilter.structural_subscription_id ==
12399.3.4 by Danilo Segan
Lint fixes.
91
                        subscription_id).one(),
7675.1030.2 by Gary Poster
make it possible to delete a structural subscription that has a filter, by adding a delete method to structural subscriptions that performs the cascading delete.
92
                None)
93
12399.3.1 by Danilo Segan
Automatically create an empty BugSubscriptionFilter for every StructuralSubscription.
94
    def test_bug_filters_default(self):
12411.3.6 by Danilo Segan
Update StructuralSubscription API test.
95
        # The bug_filters attribute has a default non-filtering bug filter
12399.3.1 by Danilo Segan
Automatically create an empty BugSubscriptionFilter for every StructuralSubscription.
96
        # to begin with.
97
        self.assertEqual([self.original_filter],
98
                         list(self.subscription.bug_filters))
11526.4.35 by Gavin Panella
Add StructuralSubscription.bug_filters.
99
100
    def test_bug_filters(self):
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
101
        # The bug_filters attribute returns the BugSubscriptionFilter records
102
        # associated with this subscription.
103
        subscription_filter = BugSubscriptionFilter()
104
        subscription_filter.structural_subscription = self.subscription
12399.3.1 by Danilo Segan
Automatically create an empty BugSubscriptionFilter for every StructuralSubscription.
105
        self.assertContentEqual(
106
            [subscription_filter, self.original_filter],
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
107
            list(self.subscription.bug_filters))
11526.4.36 by Gavin Panella
Test for StructuralSubscription.newBugFilter().
108
109
    def test_newBugFilter(self):
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
110
        # newBugFilter() creates a new subscription filter linked to the
111
        # subscription.
112
        with person_logged_in(self.product.owner):
113
            subscription_filter = self.subscription.newBugFilter()
114
        self.assertEqual(
115
            self.subscription,
116
            subscription_filter.structural_subscription)
12399.3.1 by Danilo Segan
Automatically create an empty BugSubscriptionFilter for every StructuralSubscription.
117
        self.assertContentEqual(
118
            [subscription_filter, self.original_filter],
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
119
            list(self.subscription.bug_filters))
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
120
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
121
    def test_newBugFilter_by_anonymous(self):
122
        # newBugFilter() is not available to anonymous users.
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
123
        with anonymous_logged_in():
124
            self.assertRaises(
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
125
                Unauthorized, lambda: self.subscription.newBugFilter)
126
127
    def test_newBugFilter_by_other_user(self):
128
        # newBugFilter() is only available to the subscriber.
12164.3.14 by Gavin Panella
Restrict newBugFilter() to subscriber only.
129
        with person_logged_in(self.factory.makePerson()):
130
            self.assertRaises(
12164.3.15 by Gavin Panella
Refactor test_structuralsubscription.
131
                Unauthorized, lambda: self.subscription.newBugFilter)
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
132
133
134
class FilteredStructuralSubscriptionTestBase:
135
    """Tests for filtered structural subscriptions."""
136
137
    layer = LaunchpadFunctionalLayer
138
139
    def makeTarget(self):
140
        raise NotImplementedError(self.makeTarget)
141
142
    def makeBugTask(self):
143
        return self.factory.makeBugTask(target=self.target)
144
145
    def setUp(self):
146
        super(FilteredStructuralSubscriptionTestBase, self).setUp()
147
        self.ordinary_subscriber = self.factory.makePerson()
148
        login_person(self.ordinary_subscriber)
149
        self.target = self.makeTarget()
150
        self.bugtask = self.makeBugTask()
151
        self.bug = self.bugtask.bug
152
        self.subscription = self.target.addSubscription(
153
            self.ordinary_subscriber, self.ordinary_subscriber)
154
        self.initial_filter = self.subscription.bug_filters[0]
155
156
    def assertSubscribers(
7675.1139.1 by Danilo Segan
Get rid of all NOTHING usage.
157
        self, expected_subscribers, level=BugNotificationLevel.LIFECYCLE):
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
158
        observed_subscribers = list(
159
            get_structural_subscribers(self.bugtask, None, level))
160
        self.assertEqual(expected_subscribers, observed_subscribers)
161
162
    def test_getStructuralSubscribers(self):
163
        # If no one has a filtered subscription for the given bug, the result
164
        # of get_structural_subscribers() is the same as for
165
        # the set of people from each subscription in getSubscriptions().
166
        subscriptions = self.target.getSubscriptions()
167
        self.assertSubscribers([sub.subscriber for sub in subscriptions])
168
169
    def test_getStructuralSubscribers_with_filter_on_status(self):
170
        # If a status filter exists for a subscription, the result of
171
        # get_structural_subscribers() may be a subset of getSubscriptions().
172
173
        # Without any filters the subscription is found.
174
        self.assertSubscribers([self.ordinary_subscriber])
175
176
        # Filter the subscription to bugs in the CONFIRMED state.
177
        self.initial_filter.statuses = [BugTaskStatus.CONFIRMED]
178
179
        # With the filter the subscription is not found.
180
        self.assertSubscribers([])
181
182
        # If the filter is adjusted, the subscription is found again.
183
        self.initial_filter.statuses = [self.bugtask.status]
184
        self.assertSubscribers([self.ordinary_subscriber])
185
186
    def test_getStructuralSubscribers_with_filter_on_importance(self):
187
        # If an importance filter exists for a subscription, the result of
188
        # get_structural_subscribers() may be a subset of getSubscriptions().
189
190
        # Without any filters the subscription is found.
191
        self.assertSubscribers([self.ordinary_subscriber])
192
193
        # Filter the subscription to bugs in the CRITICAL state.
194
        self.initial_filter.importances = [BugTaskImportance.CRITICAL]
195
196
        # With the filter the subscription is not found.
197
        self.assertSubscribers([])
198
199
        # If the filter is adjusted, the subscription is found again.
200
        self.initial_filter.importances = [self.bugtask.importance]
201
        self.assertSubscribers([self.ordinary_subscriber])
202
203
    def test_getStructuralSubscribers_with_filter_on_level(self):
204
        # All structural subscriptions have a level for bug notifications
205
        # which get_structural_subscribers() observes.
206
207
        # Adjust the subscription level to METADATA.
208
        self.initial_filter.bug_notification_level = (
209
            BugNotificationLevel.METADATA)
210
7675.1139.1 by Danilo Segan
Get rid of all NOTHING usage.
211
        # The subscription is found when looking for LIFECYCLE or above.
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
212
        self.assertSubscribers(
7675.1139.1 by Danilo Segan
Get rid of all NOTHING usage.
213
            [self.ordinary_subscriber], BugNotificationLevel.LIFECYCLE)
12541.2.3 by Gary Poster
move tests from test_structuralsubscriptiontarget.py to test_structuralsubscription.py
214
        # The subscription is found when looking for METADATA or above.
215
        self.assertSubscribers(
216
            [self.ordinary_subscriber], BugNotificationLevel.METADATA)
217
        # The subscription is not found when looking for COMMENTS or above.
218
        self.assertSubscribers(
219
            [], BugNotificationLevel.COMMENTS)
220
221
    def test_getStructuralSubscribers_with_filter_include_any_tags(self):
222
        # If a subscription filter has include_any_tags, a bug with one or
223
        # more tags is matched.
224
225
        self.initial_filter.include_any_tags = True
226
227
        # Without any tags the subscription is not found.
228
        self.assertSubscribers([])
229
230
        # With any tag the subscription is found.
231
        self.bug.tags = ["foo"]
232
        self.assertSubscribers([self.ordinary_subscriber])
233
234
    def test_getStructuralSubscribers_with_filter_exclude_any_tags(self):
235
        # If a subscription filter has exclude_any_tags, only bugs with no
236
        # tags are matched.
237
238
        self.initial_filter.exclude_any_tags = True
239
240
        # Without any tags the subscription is found.
241
        self.assertSubscribers([self.ordinary_subscriber])
242
243
        # With any tag the subscription is not found.
244
        self.bug.tags = ["foo"]
245
        self.assertSubscribers([])
246
247
    def test_getStructuralSubscribers_with_filter_for_any_tag(self):
248
        # If a subscription filter specifies that any of one or more specific
249
        # tags must be present, bugs with any of those tags are matched.
250
251
        # Looking for either the "foo" or the "bar" tag.
252
        self.initial_filter.tags = [u"foo", u"bar"]
253
        self.initial_filter.find_all_tags = False
254
255
        # Without either tag the subscription is not found.
256
        self.assertSubscribers([])
257
258
        # With either tag the subscription is found.
259
        self.bug.tags = ["bar", "baz"]
260
        self.assertSubscribers([self.ordinary_subscriber])
261
262
    def test_getStructuralSubscribers_with_filter_for_all_tags(self):
263
        # If a subscription filter specifies that all of one or more specific
264
        # tags must be present, bugs with all of those tags are matched.
265
266
        # Looking for both the "foo" and the "bar" tag.
267
        self.initial_filter.tags = [u"foo", u"bar"]
268
        self.initial_filter.find_all_tags = True
269
270
        # Without either tag the subscription is not found.
271
        self.assertSubscribers([])
272
273
        # Without only one of the required tags the subscription is not found.
274
        self.bug.tags = ["foo"]
275
        self.assertSubscribers([])
276
277
        # With both required tags the subscription is found.
278
        self.bug.tags = ["foo", "bar"]
279
        self.assertSubscribers([self.ordinary_subscriber])
280
281
    def test_getStructuralSubscribers_with_filter_for_not_any_tag(self):
282
        # If a subscription filter specifies that any of one or more specific
283
        # tags must not be present, bugs without any of those tags are
284
        # matched.
285
286
        # Looking to exclude the "foo" or "bar" tags.
287
        self.initial_filter.tags = [u"-foo", u"-bar"]
288
        self.initial_filter.find_all_tags = False
289
290
        # Without either tag the subscription is found.
291
        self.assertSubscribers([self.ordinary_subscriber])
292
293
        # With both tags, the subscription is omitted.
294
        self.bug.tags = ["foo", "bar"]
295
        self.assertSubscribers([])
296
297
        # With only one tag, the subscription is found again.
298
        self.bug.tags = ["foo"]
299
        self.assertSubscribers([self.ordinary_subscriber])
300
301
        # However, if find_all_tags is True, even a single excluded tag
302
        # causes the subscription to be skipped.
303
        self.initial_filter.find_all_tags = True
304
        self.assertSubscribers([])
305
306
        # This is also true, of course, if the bug has both tags.
307
        self.bug.tags = ["foo", "bar"]
308
        self.assertSubscribers([])
309
310
    def test_getStructuralSubscribers_with_filter_for_not_all_tags(self):
311
        # If a subscription filter specifies that all of one or more specific
312
        # tags must not be present, bugs without all of those tags are
313
        # matched.
314
315
        # Looking to exclude the "foo" and "bar" tags.
316
        self.initial_filter.tags = [u"-foo", u"-bar"]
317
        self.initial_filter.find_all_tags = True
318
319
        # Without either tag the subscription is found.
320
        self.assertSubscribers([self.ordinary_subscriber])
321
322
        # With only one of the excluded tags the subscription is not
323
        # found--we are saying that we want to find both an absence of foo
324
        # and an absence of bar, and yet foo exists.
325
        self.bug.tags = ["foo"]
326
        self.assertSubscribers([])
327
328
        # With both tags the subscription is also not found.
329
        self.bug.tags = ["foo", "bar"]
330
        self.assertSubscribers([])
331
332
    def test_getStructuralSubscribers_with_multiple_filters(self):
333
        # If multiple filters exist for a subscription, all filters must
334
        # match.
335
336
        # Add the "foo" tag to the bug.
337
        self.bug.tags = ["foo"]
338
        self.assertSubscribers([self.ordinary_subscriber])
339
340
        # Filter the subscription to bugs in the CRITICAL state.
341
        self.initial_filter.statuses = [BugTaskStatus.CONFIRMED]
342
        self.initial_filter.importances = [BugTaskImportance.CRITICAL]
343
344
        # With the filter the subscription is not found.
345
        self.assertSubscribers([])
346
347
        # If the filter is adjusted to match status but not importance, the
348
        # subscription is still not found.
349
        self.initial_filter.statuses = [self.bugtask.status]
350
        self.assertSubscribers([])
351
352
        # If the filter is adjusted to also match importance, the subscription
353
        # is found again.
354
        self.initial_filter.importances = [self.bugtask.importance]
355
        self.assertSubscribers([self.ordinary_subscriber])
356
357
        # If the filter is given some tag criteria, the subscription is not
358
        # found.
359
        self.initial_filter.tags = [u"-foo", u"bar", u"baz"]
360
        self.initial_filter.find_all_tags = False
361
        self.assertSubscribers([])
362
363
        # After removing the "foo" tag and adding the "bar" tag, the
364
        # subscription is found.
365
        self.bug.tags = ["bar"]
366
        self.assertSubscribers([self.ordinary_subscriber])
367
368
        # Requiring that all tag criteria are fulfilled causes the
369
        # subscription to no longer be found.
370
        self.initial_filter.find_all_tags = True
371
        self.assertSubscribers([])
372
373
        # After adding the "baz" tag, the subscription is found again.
374
        self.bug.tags = ["bar", "baz"]
375
        self.assertSubscribers([self.ordinary_subscriber])
376
377
    def test_getStructuralSubscribers_any_filter_is_a_match(self):
378
        # If a subscription has multiple filters, the subscription is selected
379
        # when any filter is found to match. Put another way, the filters are
380
        # ORed together.
381
        subscription_filter1 = self.initial_filter
382
        subscription_filter1.statuses = [BugTaskStatus.CONFIRMED]
383
        subscription_filter2 = self.subscription.newBugFilter()
384
        subscription_filter2.tags = [u"foo"]
385
386
        # With the filter the subscription is not found.
387
        self.assertSubscribers([])
388
389
        # If the bugtask is adjusted to match the criteria of the first filter
390
        # but not those of the second, the subscription is found.
391
        self.bugtask.transitionToStatus(
392
            BugTaskStatus.CONFIRMED, self.ordinary_subscriber)
393
        self.assertSubscribers([self.ordinary_subscriber])
394
395
        # If the filter is adjusted to also match the criteria of the second
396
        # filter, the subscription is still found.
397
        self.bugtask.bug.tags = [u"foo"]
398
        self.assertSubscribers([self.ordinary_subscriber])
399
400
        # If the bugtask is adjusted to no longer match the criteria of the
401
        # first filter, the subscription is still found.
402
        self.bugtask.transitionToStatus(
403
            BugTaskStatus.INPROGRESS, self.ordinary_subscriber)
404
        self.assertSubscribers([self.ordinary_subscriber])
405
406
407
class TestStructuralSubscriptionFiltersForDistro(
408
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
409
410
    def makeTarget(self):
411
        return self.factory.makeDistribution()
412
413
414
class TestStructuralSubscriptionFiltersForProduct(
415
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
416
417
    def makeTarget(self):
418
        return self.factory.makeProduct()
419
420
421
class TestStructuralSubscriptionFiltersForDistroSourcePackage(
422
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
423
424
    def makeTarget(self):
425
        return self.factory.makeDistributionSourcePackage()
426
427
428
class TestStructuralSubscriptionFiltersForMilestone(
429
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
430
431
    def makeTarget(self):
432
        return self.factory.makeMilestone()
433
434
    def makeBugTask(self):
435
        bug = self.factory.makeBug(milestone=self.target)
436
        return bug.bugtasks[0]
437
438
439
class TestStructuralSubscriptionFiltersForDistroSeries(
440
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
441
442
    def makeTarget(self):
443
        return self.factory.makeDistroSeries()
444
445
446
class TestStructuralSubscriptionFiltersForProjectGroup(
447
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
448
449
    def makeTarget(self):
450
        return self.factory.makeProject()
451
452
    def makeBugTask(self):
453
        return self.factory.makeBugTask(
454
            target=self.factory.makeProduct(project=self.target))
455
456
457
class TestStructuralSubscriptionFiltersForProductSeries(
458
    FilteredStructuralSubscriptionTestBase, TestCaseWithFactory):
459
460
    def makeTarget(self):
461
        return self.factory.makeProductSeries()
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
462
463
464
class TestGetStructuralSubscriptionTargets(TestCaseWithFactory):
465
466
    layer = DatabaseFunctionalLayer
467
468
    def test_product_target(self):
469
        product = self.factory.makeProduct()
470
        bug = self.factory.makeBug(product=product)
471
        bugtask = bug.bugtasks[0]
472
        result = get_structural_subscription_targets(bug.bugtasks)
473
        self.assertEqual(list(result), [(bugtask, product)])
474
475
    def test_milestone_target(self):
476
        actor = self.factory.makePerson()
477
        login_person(actor)
478
        product = self.factory.makeProduct()
479
        milestone = self.factory.makeMilestone(product=product)
480
        bug = self.factory.makeBug(product=product, milestone=milestone)
481
        bugtask = bug.bugtasks[0]
482
        result = get_structural_subscription_targets(bug.bugtasks)
483
        self.assertEqual(set(result), set(
484
            ((bugtask, product), (bugtask, milestone))))
485
486
    def test_sourcepackage_target(self):
487
        actor = self.factory.makePerson()
488
        login_person(actor)
489
        distroseries = self.factory.makeDistroSeries()
490
        sourcepackage = self.factory.makeSourcePackage(
13756.1.1 by William Grant
Fix tests broken by fixing tests in r13754.
491
            distroseries=distroseries, publish=True)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
492
        product = self.factory.makeProduct()
493
        bug = self.factory.makeBug(product=product)
494
        bug.addTask(actor, sourcepackage)
495
        product_bugtask = bug.bugtasks[0]
496
        sourcepackage_bugtask = bug.bugtasks[1]
497
        result = get_structural_subscription_targets(bug.bugtasks)
498
        self.assertEqual(set(result), set(
499
            ((product_bugtask, product),
500
             (sourcepackage_bugtask, distroseries))))
501
502
    def test_distribution_source_package_target(self):
503
        actor = self.factory.makePerson()
504
        login_person(actor)
505
        distribution = self.factory.makeDistribution()
506
        dist_sourcepackage = self.factory.makeDistributionSourcePackage(
507
            distribution=distribution)
508
        product = self.factory.makeProduct()
509
        bug = self.factory.makeBug(product=product)
510
        bug.addTask(actor, dist_sourcepackage)
511
        product_bugtask = bug.bugtasks[0]
512
        dist_sourcepackage_bugtask = bug.bugtasks[1]
513
        result = get_structural_subscription_targets(bug.bugtasks)
514
        self.assertEqual(set(result), set(
515
            ((product_bugtask, product),
516
             (dist_sourcepackage_bugtask, dist_sourcepackage),
517
             (dist_sourcepackage_bugtask, distribution))))
518
13023.5.1 by Graham Binns
Added regression tests.
519
    def test_product_with_project_group(self):
520
        # get_structural_subscription_targets() will yield both a
521
        # product and its parent project group if it has one.
522
        project = self.factory.makeProject()
523
        product = self.factory.makeProduct(
524
            project=project, owner=project.owner)
525
        subscriber = self.factory.makePerson()
526
        with person_logged_in(subscriber):
13811.2.1 by Jeroen Vermeulen
Fix some of the lint people left in the past few days.
527
            project.addBugSubscription(subscriber, subscriber)
13023.5.1 by Graham Binns
Added regression tests.
528
        # This is a sanity check.
529
        self.assertEqual(project, product.parent_subscription_target)
530
        bug = self.factory.makeBug(product=product)
531
        result = get_structural_subscription_targets(bug.bugtasks)
532
        self.assertEqual(
533
            set([(bug.bugtasks[0], product), (bug.bugtasks[0], project)]),
534
            set(result))
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
535
13811.2.1 by Jeroen Vermeulen
Fix some of the lint people left in the past few days.
536
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
537
class TestGetStructuralSubscriptionsForBug(TestCaseWithFactory):
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
538
539
    layer = DatabaseFunctionalLayer
540
541
    def setUp(self):
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
542
        super(TestGetStructuralSubscriptionsForBug, self).setUp()
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
543
        self.subscriber = self.factory.makePerson()
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
544
        self.team = self.factory.makeTeam(members=[self.subscriber])
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
545
        login_person(self.subscriber)
546
        self.product = self.factory.makeProduct()
547
        self.milestone = self.factory.makeMilestone(product=self.product)
548
        self.bug = self.factory.makeBug(
549
            product=self.product, milestone=self.milestone)
550
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
551
    def getSubscriptions(self, person=None):
552
        result = get_structural_subscriptions_for_bug(self.bug, person)
553
        self.assertTrue(is_security_proxied_or_harmless(result))
554
        return result
555
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
556
    def test_no_subscriptions(self):
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
557
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
558
        self.assertEqual([], list(subscriptions))
559
560
    def test_one_subscription(self):
561
        sub = self.product.addBugSubscription(
562
            self.subscriber, self.subscriber)
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
563
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
564
        self.assertEqual([sub], list(subscriptions))
565
566
    def test_two_subscriptions(self):
567
        sub1 = self.product.addBugSubscription(
568
            self.subscriber, self.subscriber)
569
        sub2 = self.milestone.addBugSubscription(
570
            self.subscriber, self.subscriber)
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
571
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
572
        self.assertEqual(set([sub1, sub2]), set(subscriptions))
573
574
    def test_two_bugtasks_one_subscription(self):
575
        sub = self.product.addBugSubscription(
576
            self.subscriber, self.subscriber)
577
        product2 = self.factory.makeProduct()
578
        self.bug.addTask(self.subscriber, product2)
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
579
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
580
        self.assertEqual([sub], list(subscriptions))
581
582
    def test_two_bugtasks_two_subscriptions(self):
583
        sub1 = self.product.addBugSubscription(
584
            self.subscriber, self.subscriber)
585
        product2 = self.factory.makeProduct()
586
        self.bug.addTask(self.subscriber, product2)
587
        sub2 = product2.addBugSubscription(
588
            self.subscriber, self.subscriber)
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
589
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
590
        self.assertEqual(set([sub1, sub2]), set(subscriptions))
591
592
    def test_ignore_other_subscriptions(self):
593
        sub1 = self.product.addBugSubscription(
594
            self.subscriber, self.subscriber)
595
        another_subscriber = self.factory.makePerson()
596
        login_person(another_subscriber)
597
        sub2 = self.product.addBugSubscription(
598
            another_subscriber, another_subscriber)
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
599
        subscriptions = self.getSubscriptions(self.subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
600
        self.assertEqual([sub1], list(subscriptions))
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
601
        subscriptions = self.getSubscriptions(another_subscriber)
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
602
        self.assertEqual([sub2], list(subscriptions))
603
12393.10.2 by Gary Poster
fix some broken code, and eliminate some redendant code. JS is still broken.
604
    def test_team_subscription(self):
605
        with person_logged_in(self.team.teamowner):
606
            sub = self.product.addBugSubscription(
607
                self.team, self.team.teamowner)
608
        subscriptions = self.getSubscriptions(self.subscriber)
609
        self.assertEqual([sub], list(subscriptions))
610
611
    def test_both_subscriptions(self):
612
        self_sub = self.product.addBugSubscription(
613
            self.subscriber, self.subscriber)
614
        with person_logged_in(self.team.teamowner):
615
            team_sub = self.product.addBugSubscription(
616
                self.team, self.team.teamowner)
617
        subscriptions = self.getSubscriptions(self.subscriber)
618
        self.assertEqual(set([self_sub, team_sub]), set(subscriptions))
619
13023.5.1 by Graham Binns
Added regression tests.
620
    def test_subscriptions_from_parent(self):
621
        # get_structural_subscriptions_for_bug() will return any
622
        # structural subscriptions from the parents of the targets of
623
        # that bug.
624
        project = self.factory.makeProject()
625
        product = self.factory.makeProduct(
626
            project=project, owner=project.owner)
627
        subscriber = self.factory.makePerson()
628
        self_sub = project.addBugSubscription(subscriber, subscriber)
629
        # This is a sanity check.
630
        self.assertEqual(project, product.parent_subscription_target)
631
        bug = self.factory.makeBug(product=product)
632
        subscriptions = get_structural_subscriptions_for_bug(
633
            bug, subscriber)
634
        self.assertEqual(set([self_sub]), set(subscriptions))
635
12541.2.7 by Gary Poster
make remaining changes to eliminate unneccessary APIs
636
637
class TestGetStructuralSubscribers(TestCaseWithFactory):
638
639
    layer = DatabaseFunctionalLayer
640
641
    def make_product_with_bug(self):
642
        product = self.factory.makeProduct()
643
        bug = self.factory.makeBug(product=product)
644
        return product, bug
645
646
    def test_getStructuralSubscribers_no_subscribers(self):
647
        # If there are no subscribers for any of the bug's targets then no
648
        # subscribers will be returned by get_structural_subscribers().
649
        product, bug = self.make_product_with_bug()
650
        subscribers = get_structural_subscribers(bug, None, None, None)
651
        self.assertIsInstance(subscribers, (ResultSet, EmptyResultSet))
652
        self.assertEqual([], list(subscribers))
653
654
    def test_getStructuralSubscribers_single_target(self):
655
        # Subscribers for any of the bug's targets are returned.
656
        subscriber = self.factory.makePerson()
657
        login_person(subscriber)
658
        product, bug = self.make_product_with_bug()
659
        product.addBugSubscription(subscriber, subscriber)
660
        self.assertEqual(
661
            [subscriber], list(
662
                get_structural_subscribers(bug, None, None, None)))
663
664
    def test_getStructuralSubscribers_multiple_targets(self):
665
        # Subscribers for any of the bug's targets are returned.
666
        actor = self.factory.makePerson()
667
        login_person(actor)
668
669
        subscriber1 = self.factory.makePerson()
670
        subscriber2 = self.factory.makePerson()
671
672
        product1 = self.factory.makeProduct(owner=actor)
673
        product1.addBugSubscription(subscriber1, subscriber1)
674
        product2 = self.factory.makeProduct(owner=actor)
675
        product2.addBugSubscription(subscriber2, subscriber2)
676
677
        bug = self.factory.makeBug(product=product1)
678
        bug.addTask(actor, product2)
679
680
        subscribers = get_structural_subscribers(bug, None, None, None)
681
        self.assertIsInstance(subscribers, ResultSet)
682
        self.assertEqual(set([subscriber1, subscriber2]), set(subscribers))
683
684
    def test_getStructuralSubscribers_recipients(self):
685
        # If provided, get_structural_subscribers() calls the appropriate
686
        # methods on a BugNotificationRecipients object.
687
        subscriber = self.factory.makePerson()
688
        login_person(subscriber)
689
        product, bug = self.make_product_with_bug()
690
        product.addBugSubscription(subscriber, subscriber)
691
        recipients = BugNotificationRecipients()
692
        subscribers = get_structural_subscribers(bug, recipients, None, None)
693
        # The return value is a list only when populating recipients.
694
        self.assertIsInstance(subscribers, list)
695
        self.assertEqual([subscriber], recipients.getRecipients())
696
        reason, header = recipients.getReason(subscriber)
697
        self.assertThat(
698
            reason, StartsWith(
699
                u"You received this bug notification because "
700
                u"you are subscribed to "))
701
        self.assertThat(header, StartsWith(u"Subscriber "))
702
703
    def test_getStructuralSubscribers_level(self):
704
        # get_structural_subscribers() respects the given level.
705
        subscriber = self.factory.makePerson()
706
        login_person(subscriber)
707
        product, bug = self.make_product_with_bug()
708
        subscription = product.addBugSubscription(subscriber, subscriber)
709
        filter = subscription.bug_filters.one()
710
        filter.bug_notification_level = BugNotificationLevel.METADATA
711
        self.assertEqual(
712
            [subscriber], list(
713
                get_structural_subscribers(
714
                    bug, None, BugNotificationLevel.METADATA, None)))
715
        filter.bug_notification_level = BugNotificationLevel.METADATA
716
        self.assertEqual(
717
            [], list(
718
                get_structural_subscribers(
719
                    bug, None, BugNotificationLevel.COMMENTS, None)))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
720
721
722
class TestBugSubscriptionFilterMute(TestCaseWithFactory):
723
    """Tests for the BugSubscriptionFilterMute class."""
724
725
    layer = DatabaseFunctionalLayer
726
727
    def setUp(self):
728
        super(TestBugSubscriptionFilterMute, self).setUp()
729
        self.target = self.factory.makeProduct()
730
        self.team = self.factory.makeTeam()
731
        self.team_member = self.factory.makePerson()
732
        with person_logged_in(self.team.teamowner):
733
            self.team.addMember(self.team_member, self.team.teamowner)
734
            self.team_subscription = self.target.addBugSubscription(
735
                self.team, self.team.teamowner)
736
            self.filter = self.team_subscription.bug_filters.one()
737
738
    def test_isMuteAllowed_returns_true_for_team_subscriptions(self):
739
        # BugSubscriptionFilter.isMuteAllowed() will return True for
740
        # subscriptions where the owner of the subscription is a team.
7675.1094.5 by Graham Binns
Added tests to cover errors when you're not allowed to mute.
741
        self.assertTrue(self.filter.isMuteAllowed(self.team_member))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
742
743
    def test_isMuteAllowed_returns_false_for_non_team_subscriptions(self):
744
        # BugSubscriptionFilter.isMuteAllowed() will return False for
745
        # subscriptions where the owner of the subscription is not a team.
746
        person = self.factory.makePerson()
747
        with person_logged_in(person):
748
            non_team_subscription = self.target.addBugSubscription(
749
                person, person)
750
        filter = non_team_subscription.bug_filters.one()
7675.1094.5 by Graham Binns
Added tests to cover errors when you're not allowed to mute.
751
        self.assertFalse(filter.isMuteAllowed(person))
752
753
    def test_isMuteAllowed_returns_false_for_non_team_members(self):
754
        # BugSubscriptionFilter.isMuteAllowed() will return False if the
755
        # user passed to it is not a member of the subscribing team.
756
        non_team_person = self.factory.makePerson()
757
        self.assertFalse(self.filter.isMuteAllowed(non_team_person))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
758
759
    def test_mute_adds_mute(self):
760
        # BugSubscriptionFilter.mute() adds a mute for the filter.
761
        filter_id = self.filter.id
762
        person_id = self.team_member.id
763
        store = Store.of(self.filter)
764
        mutes = store.find(
765
            BugSubscriptionFilterMute,
766
            BugSubscriptionFilterMute.filter == filter_id,
767
            BugSubscriptionFilterMute.person == person_id)
768
        self.assertTrue(mutes.is_empty())
7675.1096.4 by Gary Poster
add UI to mute and unmute structural subsscriptions per person
769
        self.assertFalse(self.filter.muted(self.team_member))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
770
        self.filter.mute(self.team_member)
7675.1096.4 by Gary Poster
add UI to mute and unmute structural subsscriptions per person
771
        self.assertTrue(self.filter.muted(self.team_member))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
772
        store.flush()
7675.1096.3 by Gary Poster
add more server side changes needed for UI
773
        self.assertFalse(mutes.is_empty())
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
774
775
    def test_unmute_removes_mute(self):
776
        # BugSubscriptionFilter.unmute() removes any mute for a given
777
        # person on that filter.
778
        filter_id = self.filter.id
779
        person_id = self.team_member.id
780
        store = Store.of(self.filter)
781
        self.filter.mute(self.team_member)
782
        store.flush()
783
        mutes = store.find(
784
            BugSubscriptionFilterMute,
785
            BugSubscriptionFilterMute.filter == filter_id,
786
            BugSubscriptionFilterMute.person == person_id)
787
        self.assertFalse(mutes.is_empty())
7675.1096.4 by Gary Poster
add UI to mute and unmute structural subsscriptions per person
788
        self.assertTrue(self.filter.muted(self.team_member))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
789
        self.filter.unmute(self.team_member)
7675.1096.4 by Gary Poster
add UI to mute and unmute structural subsscriptions per person
790
        self.assertFalse(self.filter.muted(self.team_member))
7675.1094.3 by Graham Binns
Added mute, unmute and isMuteAllowed() methods. Bit of a code-drop.
791
        store.flush()
792
        self.assertTrue(mutes.is_empty())
7675.1094.4 by Graham Binns
Added tests for idempotence.
793
794
    def test_mute_is_idempotent(self):
795
        # Muting works even if the user is already muted.
796
        store = Store.of(self.filter)
797
        mute = self.filter.mute(self.team_member)
798
        store.flush()
799
        second_mute = self.filter.mute(self.team_member)
800
        self.assertEqual(mute, second_mute)
801
802
    def test_unmute_is_idempotent(self):
803
        # Unmuting works even if the user is not muted
804
        store = Store.of(self.filter)
805
        mutes = store.find(
806
            BugSubscriptionFilterMute,
807
            BugSubscriptionFilterMute.filter == self.filter.id,
808
            BugSubscriptionFilterMute.person == self.team_member.id)
809
        self.assertTrue(mutes.is_empty())
810
        self.filter.unmute(self.team_member)
811
        self.assertTrue(mutes.is_empty())
7675.1094.5 by Graham Binns
Added tests to cover errors when you're not allowed to mute.
812
813
    def test_mute_raises_error_for_non_team_subscriptions(self):
814
        # BugSubscriptionFilter.mute() will raise an error if called on
815
        # a non-team subscription.
816
        person = self.factory.makePerson()
817
        with person_logged_in(person):
818
            non_team_subscription = self.target.addBugSubscription(
819
                person, person)
820
        filter = non_team_subscription.bug_filters.one()
821
        self.assertFalse(filter.isMuteAllowed(person))
822
        self.assertRaises(MuteNotAllowed, filter.mute, person)
823
824
    def test_mute_raises_error_for_non_team_members(self):
825
        # BugSubscriptionFilter.mute() will raise an error if called on
826
        # a subscription of which the calling person is not a member.
827
        non_team_person = self.factory.makePerson()
828
        self.assertFalse(self.filter.isMuteAllowed(non_team_person))
829
        self.assertRaises(MuteNotAllowed, self.filter.mute, non_team_person)