~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/model/tests/test_bugsubscriptioninfo.py

[r=jtv][bug=874250] Load preferredemail when calculating bug
 subscribers of any kind via BugSubscriptionInfo.

Show diffs side-by-side

added added

removed removed

Lines of Context:
46
46
            ]
47
47
        observed = load_people(
48
48
            Person.id.is_in(person.id for person in expected))
49
 
        self.assertEqual(set(expected), set(observed))
 
49
        self.assertContentEqual(expected, observed)
50
50
 
51
51
 
52
52
class TestSubscriptionRelatedSets(TestCaseWithFactory):
128
128
        with person_logged_in(self.bug.owner):
129
129
            self.bug.unsubscribe(self.bug.owner, self.bug.owner)
130
130
 
131
 
    def getInfo(self):
132
 
        return BugSubscriptionInfo(
133
 
            self.bug, BugNotificationLevel.LIFECYCLE)
 
131
    def getInfo(self, level=BugNotificationLevel.LIFECYCLE):
 
132
        return BugSubscriptionInfo(self.bug, level)
134
133
 
135
134
    def _create_direct_subscriptions(self):
136
135
        subscribers = (
142
141
                for subscriber in subscribers)
143
142
        return subscribers, subscriptions
144
143
 
 
144
    def test_forTask(self):
 
145
        # `forTask` returns a new `BugSubscriptionInfo` narrowed to the given
 
146
        # bugtask.
 
147
        info = self.getInfo()
 
148
        self.assertIs(None, info.bugtask)
 
149
        # If called with the current bugtask the same `BugSubscriptionInfo`
 
150
        # instance is returned.
 
151
        self.assertIs(info, info.forTask(info.bugtask))
 
152
        # If called with a different bugtask a new `BugSubscriptionInfo` is
 
153
        # created.
 
154
        bugtask = self.bug.default_bugtask
 
155
        info_for_task = info.forTask(bugtask)
 
156
        self.assertIs(bugtask, info_for_task.bugtask)
 
157
        self.assertIsNot(info, info_for_task)
 
158
        # The instances share a cache of `BugSubscriptionInfo` instances.
 
159
        expected_cache = {
 
160
            info.cache_key: info,
 
161
            info_for_task.cache_key: info_for_task,
 
162
            }
 
163
        self.assertEqual(expected_cache, info.cache)
 
164
        self.assertIs(info.cache, info_for_task.cache)
 
165
        # Calling `forTask` again looks in the cache first.
 
166
        self.assertIs(info, info_for_task.forTask(info.bugtask))
 
167
        self.assertIs(info_for_task, info.forTask(info_for_task.bugtask))
 
168
        # The level is the same.
 
169
        self.assertEqual(info.level, info_for_task.level)
 
170
 
 
171
    def test_forLevel(self):
 
172
        # `forLevel` returns a new `BugSubscriptionInfo` narrowed to the given
 
173
        # subscription level.
 
174
        info = self.getInfo(BugNotificationLevel.LIFECYCLE)
 
175
        # If called with the current level the same `BugSubscriptionInfo`
 
176
        # instance is returned.
 
177
        self.assertIs(info, info.forLevel(info.level))
 
178
        # If called with a different level a new `BugSubscriptionInfo` is
 
179
        # created.
 
180
        level = BugNotificationLevel.METADATA
 
181
        info_for_level = info.forLevel(level)
 
182
        self.assertEqual(level, info_for_level.level)
 
183
        self.assertIsNot(info, info_for_level)
 
184
        # The instances share a cache of `BugSubscriptionInfo` instances.
 
185
        expected_cache = {
 
186
            info.cache_key: info,
 
187
            info_for_level.cache_key: info_for_level,
 
188
            }
 
189
        self.assertEqual(expected_cache, info.cache)
 
190
        self.assertIs(info.cache, info_for_level.cache)
 
191
        # Calling `forLevel` again looks in the cache first.
 
192
        self.assertIs(info, info_for_level.forLevel(info.level))
 
193
        self.assertIs(info_for_level, info.forLevel(info_for_level.level))
 
194
        # The bugtask is the same.
 
195
        self.assertIs(info.bugtask, info_for_level.bugtask)
 
196
 
 
197
    def test_muted(self):
 
198
        # The set of muted subscribers for the bug.
 
199
        subscribers, subscriptions = self._create_direct_subscriptions()
 
200
        sub1, sub2 = subscribers
 
201
        with person_logged_in(sub1):
 
202
            self.bug.mute(sub1, sub1)
 
203
        self.assertContentEqual([sub1], self.getInfo().muted_subscribers)
 
204
 
145
205
    def test_direct(self):
146
206
        # The set of direct subscribers.
147
207
        subscribers, subscriptions = self._create_direct_subscriptions()
148
208
        found_subscriptions = self.getInfo().direct_subscriptions
149
 
        self.assertEqual(set(subscriptions), found_subscriptions)
150
 
        self.assertEqual(subscriptions, found_subscriptions.sorted)
151
 
        self.assertEqual(set(subscribers), found_subscriptions.subscribers)
152
 
        self.assertEqual(subscribers, found_subscriptions.subscribers.sorted)
 
209
        self.assertContentEqual(subscriptions, found_subscriptions)
 
210
        self.assertContentEqual(subscribers, found_subscriptions.subscribers)
153
211
 
154
 
    def test_muted_direct(self):
 
212
    def test_direct_muted(self):
155
213
        # If a direct is muted, it is not listed.
156
214
        subscribers, subscriptions = self._create_direct_subscriptions()
157
215
        with person_logged_in(subscribers[0]):
158
216
            self.bug.mute(subscribers[0], subscribers[0])
159
217
        found_subscriptions = self.getInfo().direct_subscriptions
160
 
        self.assertEqual(set([subscriptions[1]]), found_subscriptions)
 
218
        self.assertContentEqual([subscriptions[1]], found_subscriptions)
 
219
 
 
220
    def test_all_direct(self):
 
221
        # The set of all direct subscribers, regardless of level.
 
222
        subscribers, subscriptions = self._create_direct_subscriptions()
 
223
        # Change the first subscription to be for comments only.
 
224
        sub1, sub2 = subscriptions
 
225
        with person_logged_in(sub1.person):
 
226
            sub1.bug_notification_level = BugNotificationLevel.LIFECYCLE
 
227
        info = self.getInfo(BugNotificationLevel.COMMENTS)
 
228
        self.assertContentEqual([sub2], info.direct_subscriptions)
 
229
        self.assertContentEqual(
 
230
            [sub1, sub2], info.direct_subscriptions_at_all_levels)
161
231
 
162
232
    def _create_duplicate_subscription(self):
163
233
        duplicate_bug = self.factory.makeBug(product=self.target)
171
241
    def test_duplicate(self):
172
242
        # The set of subscribers from duplicate bugs.
173
243
        found_subscriptions = self.getInfo().duplicate_subscriptions
174
 
        self.assertEqual(set(), found_subscriptions)
175
 
        self.assertEqual((), found_subscriptions.sorted)
176
 
        self.assertEqual(set(), found_subscriptions.subscribers)
177
 
        self.assertEqual((), found_subscriptions.subscribers.sorted)
 
244
        self.assertContentEqual([], found_subscriptions)
 
245
        self.assertContentEqual([], found_subscriptions.subscribers)
178
246
        duplicate_bug, duplicate_bug_subscription = (
179
247
            self._create_duplicate_subscription())
180
248
        found_subscriptions = self.getInfo().duplicate_subscriptions
181
 
        self.assertEqual(
182
 
            set([duplicate_bug_subscription]),
 
249
        self.assertContentEqual(
 
250
            [duplicate_bug_subscription],
183
251
            found_subscriptions)
184
 
        self.assertEqual(
185
 
            (duplicate_bug_subscription,),
186
 
            found_subscriptions.sorted)
187
 
        self.assertEqual(
188
 
            set([duplicate_bug.owner]),
 
252
        self.assertContentEqual(
 
253
            [duplicate_bug.owner],
189
254
            found_subscriptions.subscribers)
190
 
        self.assertEqual(
191
 
            (duplicate_bug.owner,),
192
 
            found_subscriptions.subscribers.sorted)
193
255
 
194
 
    def test_muted_duplicate(self):
 
256
    def test_duplicate_muted(self):
195
257
        # If a duplicate is muted, it is not listed.
196
258
        duplicate_bug, duplicate_bug_subscription = (
197
259
            self._create_duplicate_subscription())
198
260
        with person_logged_in(duplicate_bug.owner):
199
261
            self.bug.mute(duplicate_bug.owner, duplicate_bug.owner)
200
262
        found_subscriptions = self.getInfo().duplicate_subscriptions
201
 
        self.assertEqual(set(), found_subscriptions)
 
263
        self.assertContentEqual([], found_subscriptions)
202
264
 
203
265
    def test_duplicate_for_private_bug(self):
204
266
        # The set of subscribers from duplicate bugs is always empty when the
209
271
        with person_logged_in(self.bug.owner):
210
272
            self.bug.setPrivate(True, self.bug.owner)
211
273
        found_subscriptions = self.getInfo().duplicate_subscriptions
212
 
        self.assertEqual(set(), found_subscriptions)
213
 
        self.assertEqual((), found_subscriptions.sorted)
214
 
        self.assertEqual(set(), found_subscriptions.subscribers)
215
 
        self.assertEqual((), found_subscriptions.subscribers.sorted)
 
274
        self.assertContentEqual([], found_subscriptions)
 
275
        self.assertContentEqual([], found_subscriptions.subscribers)
216
276
 
217
277
    def test_duplicate_only(self):
218
278
        # The set of duplicate subscriptions where the subscriber has no other
224
284
                duplicate_bug.getSubscriptionForPerson(
225
285
                    duplicate_bug.owner))
226
286
        found_subscriptions = self.getInfo().duplicate_only_subscriptions
227
 
        self.assertEqual(
228
 
            set([duplicate_bug_subscription]),
 
287
        self.assertContentEqual(
 
288
            [duplicate_bug_subscription],
229
289
            found_subscriptions)
230
290
        # If a user is subscribed to a duplicate bug and is a bugtask
231
291
        # assignee, for example, their duplicate subscription will not be
234
294
            self.bug.default_bugtask.transitionToAssignee(
235
295
                duplicate_bug_subscription.person)
236
296
        found_subscriptions = self.getInfo().duplicate_only_subscriptions
237
 
        self.assertEqual(set(), found_subscriptions)
238
 
 
239
 
    def test_structural(self):
 
297
        self.assertContentEqual([], found_subscriptions)
 
298
 
 
299
    def test_structural_subscriptions(self):
 
300
        # The set of structural subscriptions.
 
301
        subscribers = (
 
302
            self.factory.makePerson(),
 
303
            self.factory.makePerson())
 
304
        with person_logged_in(self.bug.owner):
 
305
            subscriptions = tuple(
 
306
                self.target.addBugSubscription(subscriber, subscriber)
 
307
                for subscriber in subscribers)
 
308
        found_subscriptions = self.getInfo().structural_subscriptions
 
309
        self.assertContentEqual(subscriptions, found_subscriptions)
 
310
 
 
311
    def test_structural_subscriptions_muted(self):
 
312
        # The set of structural subscriptions DOES NOT exclude muted
 
313
        # subscriptions.
 
314
        subscriber = self.factory.makePerson()
 
315
        with person_logged_in(subscriber):
 
316
            self.bug.mute(subscriber, subscriber)
 
317
        with person_logged_in(self.bug.owner):
 
318
            subscription = self.target.addBugSubscription(
 
319
                subscriber, subscriber)
 
320
        found_subscriptions = self.getInfo().structural_subscriptions
 
321
        self.assertContentEqual([subscription], found_subscriptions)
 
322
 
 
323
    def test_structural_subscribers(self):
240
324
        # The set of structural subscribers.
241
325
        subscribers = (
242
326
            self.factory.makePerson(),
243
327
            self.factory.makePerson())
244
328
        with person_logged_in(self.bug.owner):
245
 
            subscriptions = tuple(
 
329
            for subscriber in subscribers:
246
330
                self.target.addBugSubscription(subscriber, subscriber)
247
 
                for subscriber in subscribers)
248
 
        found_subscriptions = self.getInfo().structural_subscriptions
249
 
        self.assertEqual(set(subscriptions), found_subscriptions)
250
 
        self.assertEqual(subscriptions, found_subscriptions.sorted)
251
 
        self.assertEqual(set(subscribers), found_subscriptions.subscribers)
252
 
        self.assertEqual(subscribers, found_subscriptions.subscribers.sorted)
 
331
        found_subscribers = self.getInfo().structural_subscribers
 
332
        self.assertContentEqual(subscribers, found_subscribers)
 
333
 
 
334
    def test_structural_subscribers_muted(self):
 
335
        # The set of structural subscribers DOES NOT exclude muted
 
336
        # subscribers.
 
337
        subscriber = self.factory.makePerson()
 
338
        with person_logged_in(subscriber):
 
339
            self.bug.mute(subscriber, subscriber)
 
340
        with person_logged_in(self.bug.owner):
 
341
            self.target.addBugSubscription(subscriber, subscriber)
 
342
        found_subscribers = self.getInfo().structural_subscribers
 
343
        self.assertContentEqual([subscriber], found_subscribers)
253
344
 
254
345
    def test_all_assignees(self):
255
346
        # The set of bugtask assignees for bugtasks that have been assigned.
256
347
        found_assignees = self.getInfo().all_assignees
257
 
        self.assertEqual(set(), found_assignees)
258
 
        self.assertEqual((), found_assignees.sorted)
 
348
        self.assertContentEqual([], found_assignees)
259
349
        bugtask = self.bug.default_bugtask
260
350
        with person_logged_in(bugtask.pillar.bug_supervisor):
261
351
            bugtask.transitionToAssignee(self.bug.owner)
262
352
        found_assignees = self.getInfo().all_assignees
263
 
        self.assertEqual(set([self.bug.owner]), found_assignees)
264
 
        self.assertEqual((self.bug.owner,), found_assignees.sorted)
 
353
        self.assertContentEqual([self.bug.owner], found_assignees)
265
354
        bugtask2 = self.factory.makeBugTask(bug=self.bug)
266
355
        with person_logged_in(bugtask2.pillar.owner):
267
356
            bugtask2.transitionToAssignee(bugtask2.owner)
268
357
        found_assignees = self.getInfo().all_assignees
269
 
        self.assertEqual(
270
 
            set([self.bug.owner, bugtask2.owner]),
 
358
        self.assertContentEqual(
 
359
            [self.bug.owner, bugtask2.owner],
271
360
            found_assignees)
272
 
        self.assertEqual(
273
 
            (self.bug.owner, bugtask2.owner),
274
 
            found_assignees.sorted)
 
361
        # Getting info for a specific bugtask will return the assignee for
 
362
        # that bugtask only.
 
363
        self.assertContentEqual(
 
364
            [bugtask2.owner],
 
365
            self.getInfo().forTask(bugtask2).all_assignees)
275
366
 
276
367
    def test_all_pillar_owners_without_bug_supervisors(self):
277
368
        # The set of owners of pillars for which no bug supervisor is
278
 
        # configured.
 
369
        # configured and which use Launchpad for bug tracking.
279
370
        [bugtask] = self.bug.bugtasks
280
371
        found_owners = (
281
372
            self.getInfo().all_pillar_owners_without_bug_supervisors)
282
 
        self.assertEqual(set(), found_owners)
283
 
        self.assertEqual((), found_owners.sorted)
284
 
        # Clear the supervisor for the first bugtask's target.
 
373
        self.assertContentEqual([], found_owners)
 
374
        # Clear the supervisor for the bugtask's target and ensure that the
 
375
        # project uses Launchpad Bugs.
285
376
        with person_logged_in(bugtask.target.owner):
286
377
            bugtask.target.setBugSupervisor(None, bugtask.owner)
 
378
            bugtask.pillar.official_malone = True
 
379
        # The collection includes the pillar's owner.
287
380
        found_owners = (
288
381
            self.getInfo().all_pillar_owners_without_bug_supervisors)
289
 
        self.assertEqual(set([bugtask.pillar.owner]), found_owners)
290
 
        self.assertEqual((bugtask.pillar.owner,), found_owners.sorted)
291
 
        # Add another bugtask with a bug supervisor.
292
 
        target2 = self.factory.makeProduct(bug_supervisor=None)
 
382
        self.assertContentEqual([bugtask.pillar.owner], found_owners)
 
383
        # Add another bugtask for a pillar that uses Launchpad but does not
 
384
        # have a bug supervisor.
 
385
        target2 = self.factory.makeProduct(
 
386
            bug_supervisor=None, official_malone=True)
293
387
        bugtask2 = self.factory.makeBugTask(bug=self.bug, target=target2)
294
388
        found_owners = (
295
389
            self.getInfo().all_pillar_owners_without_bug_supervisors)
296
 
        self.assertEqual(
297
 
            set([bugtask.pillar.owner, bugtask2.pillar.owner]),
 
390
        self.assertContentEqual(
 
391
            [bugtask.pillar.owner, bugtask2.pillar.owner],
298
392
            found_owners)
299
 
        self.assertEqual(
300
 
            (bugtask.pillar.owner, bugtask2.pillar.owner),
301
 
            found_owners.sorted)
 
393
 
 
394
    def test_all_pillar_owners_without_bug_supervisors_not_using_malone(self):
 
395
        # The set of owners of pillars for which no bug supervisor is
 
396
        # configured and which do not use Launchpad for bug tracking is empty.
 
397
        [bugtask] = self.bug.bugtasks
 
398
        # Clear the supervisor for the first bugtask's target and ensure the
 
399
        # project does not use Launchpad Bugs.
 
400
        with person_logged_in(bugtask.target.owner):
 
401
            bugtask.target.setBugSupervisor(None, bugtask.owner)
 
402
            bugtask.pillar.official_malone = False
 
403
        found_owners = (
 
404
            self.getInfo().all_pillar_owners_without_bug_supervisors)
 
405
        self.assertContentEqual([], found_owners)
 
406
 
 
407
    def test_all_pillar_owners_without_bug_supervisors_for_bugtask(self):
 
408
        # The set of the owner of the chosen bugtask's pillar when no bug
 
409
        # supervisor is configured and which uses Launchpad for bug tracking.
 
410
        [bugtask] = self.bug.bugtasks
 
411
        # Clear the supervisor for the bugtask's target and ensure that the
 
412
        # project uses Launchpad Bugs.
 
413
        with person_logged_in(bugtask.target.owner):
 
414
            bugtask.target.setBugSupervisor(None, bugtask.owner)
 
415
            bugtask.pillar.official_malone = True
 
416
        # Add another bugtask for a pillar that uses Launchpad but does not
 
417
        # have a bug supervisor.
 
418
        target2 = self.factory.makeProduct(
 
419
            bug_supervisor=None, official_malone=True)
 
420
        bugtask2 = self.factory.makeBugTask(bug=self.bug, target=target2)
 
421
        # Getting subscription info for just a specific bugtask will yield
 
422
        # owners for only the pillar associated with that bugtask.
 
423
        info_for_bugtask2 = self.getInfo().forTask(bugtask2)
 
424
        self.assertContentEqual(
 
425
            [bugtask2.pillar.owner],
 
426
            info_for_bugtask2.all_pillar_owners_without_bug_supervisors)
302
427
 
303
428
    def _create_also_notified_subscribers(self):
304
429
        # Add an assignee, a bug supervisor and a structural subscriber.
318
443
    def test_also_notified_subscribers(self):
319
444
        # The set of also notified subscribers.
320
445
        found_subscribers = self.getInfo().also_notified_subscribers
321
 
        self.assertEqual(set(), found_subscribers)
322
 
        self.assertEqual((), found_subscribers.sorted)
 
446
        self.assertContentEqual([], found_subscribers)
323
447
        assignee, supervisor, structural_subscriber = (
324
448
            self._create_also_notified_subscribers())
325
449
        # Add a direct subscription.
329
453
        # The direct subscriber does not appear in the also notified set, but
330
454
        # the assignee, supervisor and structural subscriber do.
331
455
        found_subscribers = self.getInfo().also_notified_subscribers
332
 
        self.assertEqual(
333
 
            set([assignee, supervisor, structural_subscriber]),
 
456
        self.assertContentEqual(
 
457
            [assignee, supervisor, structural_subscriber],
334
458
            found_subscribers)
335
 
        self.assertEqual(
336
 
            (assignee, supervisor, structural_subscriber),
337
 
            found_subscribers.sorted)
338
459
 
339
 
    def test_muted_also_notified_subscribers(self):
 
460
    def test_also_notified_subscribers_muted(self):
340
461
        # If someone is muted, they are not listed in the
341
462
        # also_notified_subscribers.
342
463
        assignee, supervisor, structural_subscriber = (
345
466
        # the assignee, supervisor and structural subscriber do show up
346
467
        # when they are not muted.
347
468
        found_subscribers = self.getInfo().also_notified_subscribers
348
 
        self.assertEqual(
349
 
            set([assignee, supervisor, structural_subscriber]),
 
469
        self.assertContentEqual(
 
470
            [assignee, supervisor, structural_subscriber],
350
471
            found_subscribers)
351
472
        # Now we mute all of the subscribers.
352
473
        with person_logged_in(assignee):
357
478
            self.bug.mute(structural_subscriber, structural_subscriber)
358
479
        # Now we don't see them.
359
480
        found_subscribers = self.getInfo().also_notified_subscribers
360
 
        self.assertEqual(
361
 
            set(), found_subscribers)
 
481
        self.assertContentEqual([], found_subscribers)
362
482
 
363
483
    def test_also_notified_subscribers_for_private_bug(self):
364
484
        # The set of also notified subscribers is always empty when the master
370
490
        with person_logged_in(self.bug.owner):
371
491
            self.bug.setPrivate(True, self.bug.owner)
372
492
        found_subscribers = self.getInfo().also_notified_subscribers
373
 
        self.assertEqual(set(), found_subscribers)
374
 
        self.assertEqual((), found_subscribers.sorted)
 
493
        self.assertContentEqual([], found_subscribers)
375
494
 
376
495
    def test_indirect_subscribers(self):
377
496
        # The set of indirect subscribers is the union of also notified
384
503
        with person_logged_in(duplicate_bug.owner):
385
504
            duplicate_bug.markAsDuplicate(self.bug)
386
505
        found_subscribers = self.getInfo().indirect_subscribers
387
 
        self.assertEqual(
388
 
            set([assignee, duplicate_bug.owner]),
 
506
        self.assertContentEqual(
 
507
            [assignee, duplicate_bug.owner],
389
508
            found_subscribers)
390
 
        self.assertEqual(
391
 
            (assignee, duplicate_bug.owner),
392
 
            found_subscribers.sorted)
393
509
 
394
510
 
395
511
class TestBugSubscriptionInfoPermissions(TestCaseWithFactory):
406
522
 
407
523
        # All attributes require launchpad.View.
408
524
        permissions = set(checker.get_permissions.itervalues())
409
 
        self.assertEqual(set(["launchpad.View"]), permissions)
 
525
        self.assertContentEqual(["launchpad.View"], permissions)
410
526
 
411
527
        # The security adapter for launchpad.View lets anyone reference the
412
528
        # attributes unless the bug is private, in which case only explicit
444
560
            yield recorder
445
561
        self.assertThat(recorder, condition)
446
562
 
447
 
    def exercise_subscription_set(self, set_name):
 
563
    def exercise_subscription_set(self, set_name, counts=(1, 1, 0)):
 
564
        """Test the number of queries it takes to inspect a subscription set.
 
565
 
 
566
        :param set_name: The name of the set, e.g. "direct_subscriptions".
 
567
        :param counts: A triple of the expected query counts for each of three
 
568
            operations: get the set, get the set's subscribers, get the set's
 
569
            subscribers in order.
 
570
        """
448
571
        # Looking up subscriptions takes a single query.
449
 
        with self.exactly_x_queries(1):
 
572
        with self.exactly_x_queries(counts[0]):
450
573
            getattr(self.info, set_name)
451
574
        # Getting the subscribers results in one additional query.
452
 
        with self.exactly_x_queries(1):
 
575
        with self.exactly_x_queries(counts[1]):
453
576
            getattr(self.info, set_name).subscribers
454
577
        # Everything is now cached so no more queries are needed.
455
 
        with self.exactly_x_queries(0):
 
578
        with self.exactly_x_queries(counts[2]):
456
579
            getattr(self.info, set_name).subscribers
457
580
            getattr(self.info, set_name).subscribers.sorted
458
581
 
459
 
    def exercise_subscription_set_sorted_first(self, set_name):
 
582
    def exercise_subscription_set_sorted_first(
 
583
        self, set_name, counts=(1, 1, 0)):
 
584
        """Test the number of queries it takes to inspect a subscription set.
 
585
 
 
586
        This differs from `exercise_subscription_set` in its second step, when
 
587
        it looks at the sorted subscription list instead of the subscriber
 
588
        set.
 
589
 
 
590
        :param set_name: The name of the set, e.g. "direct_subscriptions".
 
591
        :param counts: A triple of the expected query counts for each of three
 
592
            operations: get the set, get the set in order, get the set's
 
593
            subscribers in order.
 
594
        """
460
595
        # Looking up subscriptions takes a single query.
461
 
        with self.exactly_x_queries(1):
 
596
        with self.exactly_x_queries(counts[0]):
462
597
            getattr(self.info, set_name)
463
598
        # Getting the sorted subscriptions takes one additional query.
464
 
        with self.exactly_x_queries(1):
 
599
        with self.exactly_x_queries(counts[1]):
465
600
            getattr(self.info, set_name).sorted
466
601
        # Everything is now cached so no more queries are needed.
467
 
        with self.exactly_x_queries(0):
 
602
        with self.exactly_x_queries(counts[2]):
468
603
            getattr(self.info, set_name).subscribers
469
604
            getattr(self.info, set_name).subscribers.sorted
470
605
 
476
611
        self.exercise_subscription_set_sorted_first(
477
612
            "direct_subscriptions")
478
613
 
 
614
    def test_direct_subscriptions_at_all_levels(self):
 
615
        self.exercise_subscription_set(
 
616
            "direct_subscriptions_at_all_levels")
 
617
 
479
618
    def make_duplicate_bug(self):
480
619
        duplicate_bug = self.factory.makeBug(product=self.target)
481
620
        with person_logged_in(duplicate_bug.owner):
501
640
            self.info.duplicate_subscriptions.subscribers
502
641
 
503
642
    def add_structural_subscriber(self):
504
 
        with person_logged_in(self.bug.owner):
505
 
            self.target.addSubscription(self.bug.owner, self.bug.owner)
 
643
        subscriber = self.factory.makePerson()
 
644
        with person_logged_in(subscriber):
 
645
            self.target.addSubscription(subscriber, subscriber)
506
646
 
507
647
    def test_structural_subscriptions(self):
508
648
        self.add_structural_subscriber()
509
649
        self.exercise_subscription_set(
510
 
            "structural_subscriptions")
 
650
            "structural_subscriptions", (2, 1, 0))
511
651
 
512
652
    def test_structural_subscriptions_sorted_first(self):
513
653
        self.add_structural_subscriber()
514
654
        self.exercise_subscription_set_sorted_first(
515
 
            "structural_subscriptions")
 
655
            "structural_subscriptions", (2, 1, 0))
516
656
 
517
657
    def test_all_assignees(self):
518
658
        with self.exactly_x_queries(1):
522
662
        # Getting all bug supervisors and pillar owners can take several
523
663
        # queries. However, there are typically few tasks so the trade for
524
664
        # simplicity of implementation is acceptable. Only the simplest case
525
 
        # is tested here.
526
 
        with self.exactly_x_queries(1):
 
665
        # is tested here (everything is already cached).
 
666
        with self.exactly_x_queries(0):
527
667
            self.info.all_pillar_owners_without_bug_supervisors
528
668
 
529
669
    def test_also_notified_subscribers(self):
536
676
        self.info.all_assignees
537
677
        self.info.all_pillar_owners_without_bug_supervisors
538
678
        self.info.direct_subscriptions.subscribers
539
 
        self.info.structural_subscriptions.subscribers
 
679
        self.info.structural_subscribers
540
680
        with self.exactly_x_queries(1):
541
681
            self.info.also_notified_subscribers
542
682