~launchpad-pqm/launchpad/devel

8687.15.15 by Karl Fogel
Add the copyright header block to files under lib/lp/bugs/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
2770.1.40 by Guilherme Salgado
A few fixes Bjorn suggested and removing two unused templates.
3
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
4
__metaclass__ = type
5
13402.4.1 by Graham Binns
I can't quite believe this, but the tests pass.
6
import transaction
7
12075.3.15 by Gavin Panella
Move tests for group_comments_with_activity() over to test_bugcomment.
8
from datetime import datetime
2770.1.40 by Guilherme Salgado
A few fixes Bjorn suggested and removing two unused templates.
9
12792.8.1 by William Grant
Add failing test for BugActivityItem assignee escaping.
10
from lazr.lifecycle.event import ObjectModifiedEvent
11
from lazr.lifecycle.snapshot import Snapshot
12075.3.3 by Gavin Panella
Factor out the selection of interesting activity.
12
from pytz import UTC
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
13
from storm.store import Store
11618.1.1 by Deryck Hodge
Drive the query count down by two for bugtask+index, and fix up
14
from testtools.matchers import LessThan
12799.1.2 by Ian Booth
Rework implementation and do unit tests instead of doc tests
15
from zope.component import (
16
    getMultiAdapter,
17
    getUtility,
18
    )
12792.8.1 by William Grant
Add failing test for BugActivityItem assignee escaping.
19
from zope.event import notify
20
from zope.interface import providedBy
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
21
from zope.security.proxy import removeSecurityProxy
2770.1.40 by Guilherme Salgado
A few fixes Bjorn suggested and removing two unused templates.
22
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
23
from canonical.launchpad.ftests import (
24
    ANONYMOUS,
25
    login,
26
    login_person,
27
    )
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
28
from canonical.launchpad.testing.pages import find_tag_by_id
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
29
from canonical.launchpad.webapp import canonical_url
9521.3.1 by Tom Berger
patch from allenap
30
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
31
from canonical.testing.layers import (
32
    DatabaseFunctionalLayer,
33
    LaunchpadFunctionalLayer,
34
    )
13130.1.12 by Curtis Hovey
Sorted imports.
35
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
36
from lp.bugs.browser.bugtask import (
12792.8.1 by William Grant
Add failing test for BugActivityItem assignee escaping.
37
    BugActivityItem,
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
38
    BugTaskEditView,
39
    BugTasksAndNominationsView,
40
    )
12075.3.3 by Gavin Panella
Factor out the selection of interesting activity.
41
from lp.bugs.interfaces.bugactivity import IBugActivitySet
12799.1.2 by Ian Booth
Rework implementation and do unit tests instead of doc tests
42
from lp.bugs.interfaces.bugnomination import IBugNomination
43
from lp.bugs.interfaces.bugtask import (
44
    BugTaskStatus,
45
    IBugTask,
46
    IBugTaskSet,
47
    )
12075.3.3 by Gavin Panella
Factor out the selection of interesting activity.
48
from lp.services.propertycache import get_property_cache
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
49
from lp.soyuz.interfaces.component import IComponentSet
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
50
from lp.testing import (
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
51
    celebrity_logged_in,
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
52
    person_logged_in,
53
    TestCaseWithFactory,
54
    )
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
55
from lp.testing._webservice import QueryCollector
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
56
from lp.testing.matchers import (
57
    BrowsesWithQueryLimit,
58
    HasQueryCount,
59
    )
11474.2.1 by Robert Collins
Use sampledata constants.
60
from lp.testing.sampledata import (
61
    ADMIN_EMAIL,
62
    NO_PRIVILEGE_EMAIL,
63
    USER_EMAIL,
64
    )
11655.1.3 by Brad Crittenden
De-lint
65
from lp.testing.views import create_initialized_view
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
66
67
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
68
class TestBugTaskView(TestCaseWithFactory):
69
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
70
    layer = LaunchpadFunctionalLayer
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
71
72
    def invalidate_caches(self, obj):
73
        store = Store.of(obj)
74
        # Make sure everything is in the database.
75
        store.flush()
76
        # And invalidate the cache (not a reset, because that stops us using
77
        # the domain objects)
78
        store.invalidate()
79
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
80
    def test_rendered_query_counts_constant_with_team_memberships(self):
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
81
        login(ADMIN_EMAIL)
11618.1.1 by Deryck Hodge
Drive the query count down by two for bugtask+index, and fix up
82
        task = self.factory.makeBugTask()
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
83
        person_no_teams = self.factory.makePerson(password='test')
84
        person_with_teams = self.factory.makePerson(password='test')
11474.2.4 by Robert Collins
Testing tweaks per review.
85
        for _ in range(10):
86
            self.factory.makeTeam(members=[person_with_teams])
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
87
        # count with no teams
11618.1.1 by Deryck Hodge
Drive the query count down by two for bugtask+index, and fix up
88
        url = canonical_url(task)
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
89
        recorder = QueryCollector()
90
        recorder.register()
91
        self.addCleanup(recorder.unregister)
11618.1.1 by Deryck Hodge
Drive the query count down by two for bugtask+index, and fix up
92
        self.invalidate_caches(task)
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
93
        self.getUserBrowser(url, person_no_teams)
11655.1.3 by Brad Crittenden
De-lint
94
        # This may seem large: it is; there is easily another 30% fat in
95
        # there.
13247.1.1 by Danilo Segan
Reapply bug 772754 fix with packagecopyjob changes removed.
96
        self.assertThat(recorder, HasQueryCount(LessThan(74)))
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
97
        count_with_no_teams = recorder.count
11474.2.4 by Robert Collins
Testing tweaks per review.
98
        # count with many teams
11618.1.1 by Deryck Hodge
Drive the query count down by two for bugtask+index, and fix up
99
        self.invalidate_caches(task)
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
100
        self.getUserBrowser(url, person_with_teams)
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
101
        # Allow an increase of one because storm bug 619017 causes additional
11474.2.4 by Robert Collins
Testing tweaks per review.
102
        # queries, revalidating things unnecessarily. An increase which is
103
        # less than the number of new teams shows it is definitely not
104
        # growing per-team.
11582.2.2 by Robert Collins
Probably broken, but takes 46 queries off of a baseline BugTask:+index.
105
        self.assertThat(recorder, HasQueryCount(
11474.2.4 by Robert Collins
Testing tweaks per review.
106
            LessThan(count_with_no_teams + 3),
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
107
            ))
108
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
109
    def test_rendered_query_counts_constant_with_attachments(self):
110
        with celebrity_logged_in('admin'):
111
            browses_under_limit = BrowsesWithQueryLimit(
13247.1.1 by Danilo Segan
Reapply bug 772754 fix with packagecopyjob changes removed.
112
                78, self.factory.makePerson())
12736.8.2 by William Grant
Test that BugTask:+index doesn't scale with attachments.
113
114
            # First test with a single attachment.
115
            task = self.factory.makeBugTask()
116
            self.factory.makeBugAttachment(bug=task.bug)
117
        self.assertThat(task, browses_under_limit)
118
119
        with celebrity_logged_in('admin'):
120
            # And now with 10.
121
            task = self.factory.makeBugTask()
122
            self.factory.makeBugTask(bug=task.bug)
123
            for i in range(10):
124
                self.factory.makeBugAttachment(bug=task.bug)
125
        self.assertThat(task, browses_under_limit)
126
12075.3.3 by Gavin Panella
Factor out the selection of interesting activity.
127
    def test_interesting_activity(self):
128
        # The interesting_activity property returns a tuple of interesting
129
        # `BugActivityItem`s.
130
        bug = self.factory.makeBug()
131
        view = create_initialized_view(
132
            bug.default_bugtask, name=u'+index', rootsite='bugs')
133
134
        def add_activity(what, old=None, new=None, message=None):
135
            getUtility(IBugActivitySet).new(
136
                bug, datetime.now(UTC), bug.owner, whatchanged=what,
137
                oldvalue=old, newvalue=new, message=message)
138
            del get_property_cache(view).interesting_activity
139
140
        # A fresh bug has no interesting activity.
141
        self.assertEqual((), view.interesting_activity)
142
143
        # Some activity is not considered interesting.
144
        add_activity("boring")
145
        self.assertEqual((), view.interesting_activity)
146
147
        # A description change is interesting.
148
        add_activity("description")
149
        self.assertEqual(1, len(view.interesting_activity))
150
        [activity] = view.interesting_activity
151
        self.assertEqual("description", activity.whatchanged)
152
13402.4.1 by Graham Binns
I can't quite believe this, but the tests pass.
153
    def test_error_for_changing_target_with_invalid_status(self):
154
        # If a user moves a bug task with a restricted status (say,
155
        # Triaged) to a target where they do not have permission to set
156
        # that status, they will be unable to complete the retargeting
157
        # and will instead receive an error in the UI.
158
        person = self.factory.makePerson()
159
        product = self.factory.makeProduct(
160
            name='product1', owner=person, official_malone=True)
161
        with person_logged_in(person):
162
            product.setBugSupervisor(person, person)
163
        product_2 = self.factory.makeProduct(
164
            name='product2', official_malone=True)
165
        with person_logged_in(product_2.owner):
166
            product_2.setBugSupervisor(product_2.owner, product_2.owner)
167
        bug = self.factory.makeBug(
168
            product=product, owner=person)
169
        # We need to commit here, otherwise all the sample data we
170
        # created gets destroyed when the transaction is rolled back.
171
        transaction.commit()
172
        with person_logged_in(person):
173
            form_data = {
174
                '%s.product' % product.name: product_2.name,
175
                '%s.status' % product.name: BugTaskStatus.TRIAGED.title,
176
                '%s.actions.save' % product.name: 'Save Changes',
177
                }
178
            view = create_initialized_view(
179
                bug.default_bugtask, name=u'+editstatus',
180
                form=form_data)
181
            # The bugtask's target won't have changed, since an error
182
            # happend. The error will be listed in the view.
13402.4.5 by Graham Binns
Tweaked according to Rob's requirements.
183
            self.assertEqual(1, len(view.errors))
13402.4.1 by Graham Binns
I can't quite believe this, but the tests pass.
184
            self.assertEqual(product, bug.default_bugtask.target)
185
11474.2.3 by Robert Collins
Add getSubscribersForPerson to IBug, permitting single query setup of bug index pages, which according to OOPS is about 1.2 seconds (but if we're underestimating could be more) and will make analysing performance on the page easier.
186
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
187
class TestBugTasksAndNominationsView(TestCaseWithFactory):
188
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
189
    layer = DatabaseFunctionalLayer
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
190
191
    def setUp(self):
192
        super(TestBugTasksAndNominationsView, self).setUp()
11474.2.1 by Robert Collins
Use sampledata constants.
193
        login(ADMIN_EMAIL)
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
194
        self.bug = self.factory.makeBug()
9521.3.1 by Tom Berger
patch from allenap
195
        self.view = BugTasksAndNominationsView(
196
            self.bug, LaunchpadTestRequest())
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
197
11582.2.5 by Robert Collins
Fix up test fallout.
198
    def refresh(self):
11582.2.6 by Robert Collins
More tests not-quite-right.
199
        # The view caches, to see different scenarios, a refresh is needed.
11582.2.5 by Robert Collins
Fix up test fallout.
200
        self.view = BugTasksAndNominationsView(
201
            self.bug, LaunchpadTestRequest())
202
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
203
    def test_current_user_affected_status(self):
204
        self.failUnlessEqual(
205
            None, self.view.current_user_affected_status)
11582.2.5 by Robert Collins
Fix up test fallout.
206
        self.bug.markUserAffected(self.view.user, True)
207
        self.refresh()
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
208
        self.failUnlessEqual(
209
            True, self.view.current_user_affected_status)
11582.2.5 by Robert Collins
Fix up test fallout.
210
        self.bug.markUserAffected(self.view.user, False)
211
        self.refresh()
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
212
        self.failUnlessEqual(
213
            False, self.view.current_user_affected_status)
214
215
    def test_current_user_affected_js_status(self):
216
        self.failUnlessEqual(
217
            'null', self.view.current_user_affected_js_status)
11582.2.5 by Robert Collins
Fix up test fallout.
218
        self.bug.markUserAffected(self.view.user, True)
219
        self.refresh()
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
220
        self.failUnlessEqual(
221
            'true', self.view.current_user_affected_js_status)
11582.2.5 by Robert Collins
Fix up test fallout.
222
        self.bug.markUserAffected(self.view.user, False)
223
        self.refresh()
8971.26.2 by Gavin Panella
New property current_user_affected_js_status, and tests.
224
        self.failUnlessEqual(
225
            'false', self.view.current_user_affected_js_status)
226
9521.3.1 by Tom Berger
patch from allenap
227
    def test_not_many_bugtasks(self):
228
        for count in range(10 - len(self.bug.bugtasks) - 1):
229
            self.factory.makeBugTask(bug=self.bug)
230
        self.view.initialize()
231
        self.failIf(self.view.many_bugtasks)
232
        row_view = self.view._getTableRowView(
233
            self.bug.default_bugtask, False, False)
234
        self.failIf(row_view.many_bugtasks)
235
236
    def test_many_bugtasks(self):
237
        for count in range(10 - len(self.bug.bugtasks)):
238
            self.factory.makeBugTask(bug=self.bug)
239
        self.view.initialize()
240
        self.failUnless(self.view.many_bugtasks)
241
        row_view = self.view._getTableRowView(
242
            self.bug.default_bugtask, False, False)
243
        self.failUnless(row_view.many_bugtasks)
244
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
245
    def test_other_users_affected_count(self):
246
        # The number of other users affected does not change when the
247
        # logged-in user marked him or herself as affected or not.
248
        self.failUnlessEqual(
249
            1, self.view.other_users_affected_count)
11582.2.5 by Robert Collins
Fix up test fallout.
250
        self.bug.markUserAffected(self.view.user, True)
251
        self.refresh()
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
252
        self.failUnlessEqual(
253
            1, self.view.other_users_affected_count)
11582.2.5 by Robert Collins
Fix up test fallout.
254
        self.bug.markUserAffected(self.view.user, False)
255
        self.refresh()
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
256
        self.failUnlessEqual(
257
            1, self.view.other_users_affected_count)
258
259
    def test_other_users_affected_count_other_users(self):
260
        # The number of other users affected only changes when other
261
        # users mark themselves as affected.
262
        self.failUnlessEqual(
263
            1, self.view.other_users_affected_count)
264
        other_user_1 = self.factory.makePerson()
11582.2.5 by Robert Collins
Fix up test fallout.
265
        self.bug.markUserAffected(other_user_1, True)
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
266
        self.failUnlessEqual(
267
            2, self.view.other_users_affected_count)
268
        other_user_2 = self.factory.makePerson()
11582.2.5 by Robert Collins
Fix up test fallout.
269
        self.bug.markUserAffected(other_user_2, True)
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
270
        self.failUnlessEqual(
271
            3, self.view.other_users_affected_count)
11582.2.5 by Robert Collins
Fix up test fallout.
272
        self.bug.markUserAffected(other_user_1, False)
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
273
        self.failUnlessEqual(
274
            2, self.view.other_users_affected_count)
11582.2.5 by Robert Collins
Fix up test fallout.
275
        self.bug.markUserAffected(self.view.user, True)
276
        self.refresh()
10015.1.2 by Gavin Panella
New view property other_users_affected_count.
277
        self.failUnlessEqual(
278
            2, self.view.other_users_affected_count)
279
10015.1.3 by Gavin Panella
New view property affected_statement.
280
    def test_affected_statement_no_one_affected(self):
281
        self.bug.markUserAffected(self.bug.owner, False)
282
        self.failUnlessEqual(
283
            0, self.view.other_users_affected_count)
284
        self.failUnlessEqual(
285
            "Does this bug affect you?",
286
            self.view.affected_statement)
287
288
    def test_affected_statement_only_you(self):
289
        self.view.context.markUserAffected(self.view.user, True)
290
        self.failUnless(self.bug.isUserAffected(self.view.user))
291
        self.view.context.markUserAffected(self.bug.owner, False)
292
        self.failUnlessEqual(
293
            0, self.view.other_users_affected_count)
294
        self.failUnlessEqual(
10015.1.7 by Gavin Panella
Remove trailing full-stops from the affected statement.
295
            "This bug affects you",
10015.1.3 by Gavin Panella
New view property affected_statement.
296
            self.view.affected_statement)
297
10015.1.8 by Gavin Panella
Make affected_statement different when a user has explicitly said they are not affected, and when they have not.
298
    def test_affected_statement_only_not_you(self):
299
        self.view.context.markUserAffected(self.view.user, False)
300
        self.failIf(self.bug.isUserAffected(self.view.user))
301
        self.view.context.markUserAffected(self.bug.owner, False)
302
        self.failUnlessEqual(
303
            0, self.view.other_users_affected_count)
304
        self.failUnlessEqual(
10015.1.10 by Gavin Panella
Say "doesn't" instead of "does not".
305
            "This bug doesn't affect you",
10015.1.8 by Gavin Panella
Make affected_statement different when a user has explicitly said they are not affected, and when they have not.
306
            self.view.affected_statement)
307
10015.1.3 by Gavin Panella
New view property affected_statement.
308
    def test_affected_statement_1_person_not_you(self):
309
        self.assertIs(None, self.bug.isUserAffected(self.view.user))
310
        self.failUnlessEqual(
311
            1, self.view.other_users_affected_count)
312
        self.failUnlessEqual(
313
            "This bug affects 1 person. Does this bug affect you?",
314
            self.view.affected_statement)
315
316
    def test_affected_statement_1_person_and_you(self):
317
        self.view.context.markUserAffected(self.view.user, True)
318
        self.failUnless(self.bug.isUserAffected(self.view.user))
319
        self.failUnlessEqual(
320
            1, self.view.other_users_affected_count)
321
        self.failUnlessEqual(
10015.1.7 by Gavin Panella
Remove trailing full-stops from the affected statement.
322
            "This bug affects you and 1 other person",
10015.1.3 by Gavin Panella
New view property affected_statement.
323
            self.view.affected_statement)
324
10015.1.8 by Gavin Panella
Make affected_statement different when a user has explicitly said they are not affected, and when they have not.
325
    def test_affected_statement_1_person_and_not_you(self):
326
        self.view.context.markUserAffected(self.view.user, False)
327
        self.failIf(self.bug.isUserAffected(self.view.user))
328
        self.failUnlessEqual(
329
            1, self.view.other_users_affected_count)
330
        self.failUnlessEqual(
331
            "This bug affects 1 person, but not you",
332
            self.view.affected_statement)
333
10015.1.3 by Gavin Panella
New view property affected_statement.
334
    def test_affected_statement_more_than_1_person_not_you(self):
335
        self.assertIs(None, self.bug.isUserAffected(self.view.user))
336
        other_user = self.factory.makePerson()
337
        self.view.context.markUserAffected(other_user, True)
338
        self.failUnlessEqual(
339
            2, self.view.other_users_affected_count)
340
        self.failUnlessEqual(
341
            "This bug affects 2 people. Does this bug affect you?",
342
            self.view.affected_statement)
343
344
    def test_affected_statement_more_than_1_person_and_you(self):
345
        self.view.context.markUserAffected(self.view.user, True)
346
        self.failUnless(self.bug.isUserAffected(self.view.user))
347
        other_user = self.factory.makePerson()
348
        self.view.context.markUserAffected(other_user, True)
349
        self.failUnlessEqual(
350
            2, self.view.other_users_affected_count)
351
        self.failUnlessEqual(
10015.1.7 by Gavin Panella
Remove trailing full-stops from the affected statement.
352
            "This bug affects you and 2 other people",
10015.1.3 by Gavin Panella
New view property affected_statement.
353
            self.view.affected_statement)
354
10015.1.8 by Gavin Panella
Make affected_statement different when a user has explicitly said they are not affected, and when they have not.
355
    def test_affected_statement_more_than_1_person_and_not_you(self):
356
        self.view.context.markUserAffected(self.view.user, False)
357
        self.failIf(self.bug.isUserAffected(self.view.user))
358
        other_user = self.factory.makePerson()
359
        self.view.context.markUserAffected(other_user, True)
360
        self.failUnlessEqual(
361
            2, self.view.other_users_affected_count)
362
        self.failUnlessEqual(
363
            "This bug affects 2 people, but not you",
364
            self.view.affected_statement)
365
10015.1.18 by Gavin Panella
New view property anon_affected_statement.
366
    def test_anon_affected_statement_no_one_affected(self):
367
        self.bug.markUserAffected(self.bug.owner, False)
368
        self.failUnlessEqual(0, self.bug.users_affected_count)
369
        self.assertIs(None, self.view.anon_affected_statement)
370
371
    def test_anon_affected_statement_1_user_affected(self):
372
        self.failUnlessEqual(1, self.bug.users_affected_count)
373
        self.failUnlessEqual(
374
            "This bug affects 1 person",
375
            self.view.anon_affected_statement)
376
377
    def test_anon_affected_statement_2_users_affected(self):
378
        self.view.context.markUserAffected(self.view.user, True)
379
        self.failUnlessEqual(2, self.bug.users_affected_count)
380
        self.failUnlessEqual(
381
            "This bug affects 2 people",
382
            self.view.anon_affected_statement)
383
12561.3.19 by Curtis Hovey
Moved product and productseries getTargetLinkTitle tests to a unit test.
384
    def test_getTargetLinkTitle_product(self):
385
        # The target link title is always none for products.
386
        target = self.factory.makeProduct()
387
        bug_task = self.factory.makeBugTask(bug=self.bug, target=target)
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
388
        self.view.initialize()
389
        self.assertEqual(None, self.view.getTargetLinkTitle(bug_task.target))
12561.3.19 by Curtis Hovey
Moved product and productseries getTargetLinkTitle tests to a unit test.
390
391
    def test_getTargetLinkTitle_productseries(self):
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
392
        # The target link title is always none for productseries.
12561.3.19 by Curtis Hovey
Moved product and productseries getTargetLinkTitle tests to a unit test.
393
        target = self.factory.makeProductSeries()
394
        bug_task = self.factory.makeBugTask(bug=self.bug, target=target)
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
395
        self.view.initialize()
396
        self.assertEqual(None, self.view.getTargetLinkTitle(bug_task.target))
397
398
    def test_getTargetLinkTitle_distribution(self):
399
        # The target link title is always none for distributions.
400
        target = self.factory.makeDistribution()
401
        bug_task = self.factory.makeBugTask(bug=self.bug, target=target)
402
        self.view.initialize()
403
        self.assertEqual(None, self.view.getTargetLinkTitle(bug_task.target))
404
405
    def test_getTargetLinkTitle_distroseries(self):
406
        # The target link title is always none for distroseries.
407
        target = self.factory.makeDistroSeries()
408
        bug_task = self.factory.makeBugTask(bug=self.bug, target=target)
409
        self.view.initialize()
410
        self.assertEqual(None, self.view.getTargetLinkTitle(bug_task.target))
411
412
    def test_getTargetLinkTitle_unpublished_distributionsourcepackage(self):
413
        # The target link title states that the package is not published
414
        # in the current release.
415
        distribution = self.factory.makeDistribution(name='boy')
416
        spn = self.factory.makeSourcePackageName('badger')
417
        component = getUtility(IComponentSet)['universe']
418
        maintainer = self.factory.makePerson(name="jim")
419
        creator = self.factory.makePerson(name="tim")
420
        self.factory.makeSourcePackagePublishingHistory(
421
            distroseries=distribution.currentseries, version='2.0',
422
            component=component, sourcepackagename=spn,
423
            date_uploaded=datetime(2008, 7, 18, 10, 20, 30, tzinfo=UTC),
424
            maintainer=maintainer, creator=creator)
425
        target = distribution.getSourcePackage('badger')
426
        bug_task = self.factory.makeBugTask(
427
            bug=self.bug, target=target, publish=False)
428
        self.view.initialize()
12561.3.21 by Curtis Hovey
Removed duplicate test.
429
        self.assertEqual({}, self.view.target_releases)
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
430
        self.assertEqual(
431
            'No current release for this source package in Boy',
432
            self.view.getTargetLinkTitle(bug_task.target))
433
434
    def test_getTargetLinkTitle_published_distributionsourcepackage(self):
435
        # The target link title states the information about the current
436
        # package in the distro.
437
        distribution = self.factory.makeDistribution(name='koi')
438
        distroseries = self.factory.makeDistroSeries(
439
            distribution=distribution)
440
        spn = self.factory.makeSourcePackageName('finch')
441
        component = getUtility(IComponentSet)['universe']
442
        maintainer = self.factory.makePerson(name="jim")
443
        creator = self.factory.makePerson(name="tim")
444
        self.factory.makeSourcePackagePublishingHistory(
445
            distroseries=distroseries, version='2.0',
446
            component=component, sourcepackagename=spn,
447
            date_uploaded=datetime(2008, 7, 18, 10, 20, 30, tzinfo=UTC),
448
            maintainer=maintainer, creator=creator)
449
        target = distribution.getSourcePackage('finch')
450
        bug_task = self.factory.makeBugTask(
451
            bug=self.bug, target=target, publish=False)
452
        self.view.initialize()
12561.3.21 by Curtis Hovey
Removed duplicate test.
453
        self.assertTrue(
454
            target in self.view.target_releases.keys())
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
455
        self.assertEqual(
456
            'Latest release: 2.0, uploaded to universe on '
457
            '2008-07-18 10:20:30+00:00 by Tim (tim), maintained by Jim (jim)',
458
            self.view.getTargetLinkTitle(bug_task.target))
459
460
    def test_getTargetLinkTitle_published_sourcepackage(self):
461
        # The target link title states the information about the current
462
        # package in the distro.
463
        distroseries = self.factory.makeDistroSeries()
464
        spn = self.factory.makeSourcePackageName('bunny')
465
        component = getUtility(IComponentSet)['universe']
466
        maintainer = self.factory.makePerson(name="jim")
467
        creator = self.factory.makePerson(name="tim")
468
        self.factory.makeSourcePackagePublishingHistory(
469
            distroseries=distroseries, version='2.0',
470
            component=component, sourcepackagename=spn,
471
            date_uploaded=datetime(2008, 7, 18, 10, 20, 30, tzinfo=UTC),
472
            maintainer=maintainer, creator=creator)
473
        target = distroseries.getSourcePackage('bunny')
474
        bug_task = self.factory.makeBugTask(
475
            bug=self.bug, target=target, publish=False)
476
        self.view.initialize()
12561.3.21 by Curtis Hovey
Removed duplicate test.
477
        self.assertTrue(
478
            target in self.view.target_releases.keys())
12561.3.20 by Curtis Hovey
Reconstructed the BugTasksAndNominationsView.getTargetLinkTitle test.
479
        self.assertEqual(
480
            'Latest release: 2.0, uploaded to universe on '
481
            '2008-07-18 10:20:30+00:00 by Tim (tim), maintained by Jim (jim)',
482
            self.view.getTargetLinkTitle(bug_task.target))
12561.3.19 by Curtis Hovey
Moved product and productseries getTargetLinkTitle tests to a unit test.
483
12799.1.2 by Ian Booth
Rework implementation and do unit tests instead of doc tests
484
    def _get_object_type(self, task_or_nomination):
485
        if IBugTask.providedBy(task_or_nomination):
486
            return "bugtask"
487
        elif IBugNomination.providedBy(task_or_nomination):
488
            return "nomination"
489
        else:
490
            return "unknown"
491
492
    def test_bugtask_listing_for_inactive_projects(self):
493
        # Bugtasks should only be listed for active projects.
494
495
        product_foo = self.factory.makeProduct(name="foo")
496
        product_bar = self.factory.makeProduct(name="bar")
497
        foo_bug = self.factory.makeBug(product=product_foo)
498
        bugtask_set = getUtility(IBugTaskSet)
13571.2.2 by William Grant
BugTaskSet.createTask now takes an IBugTarget, not a key. Blergh.
499
        bugtask_set.createTask(foo_bug, foo_bug.owner, product_bar)
12799.1.2 by Ian Booth
Rework implementation and do unit tests instead of doc tests
500
501
        removeSecurityProxy(product_bar).active = False
502
503
        request = LaunchpadTestRequest()
504
        foo_bugtasks_and_nominations_view = getMultiAdapter(
505
            (foo_bug, request), name="+bugtasks-and-nominations-table")
506
        foo_bugtasks_and_nominations_view.initialize()
507
508
        task_and_nomination_views = (
509
            foo_bugtasks_and_nominations_view.getBugTaskAndNominationViews())
510
        actual_results = []
511
        for task_or_nomination_view in task_and_nomination_views:
512
            task_or_nomination = task_or_nomination_view.context
513
            actual_results.append((
514
                self._get_object_type(task_or_nomination),
515
                task_or_nomination.status.title,
516
                task_or_nomination.target.bugtargetdisplayname))
517
        # Only the one active project's task should be listed.
518
        self.assertEqual([("bugtask", "New", "Foo")], actual_results)
519
520
    def test_listing_with_no_bugtasks(self):
521
        # Test the situation when there are no bugtasks to show.
522
523
        product_foo = self.factory.makeProduct(name="foo")
524
        foo_bug = self.factory.makeBug(product=product_foo)
525
        removeSecurityProxy(product_foo).active = False
526
527
        request = LaunchpadTestRequest()
528
        foo_bugtasks_and_nominations_view = getMultiAdapter(
529
            (foo_bug, request), name="+bugtasks-and-nominations-table")
530
        foo_bugtasks_and_nominations_view.initialize()
531
532
        task_and_nomination_views = (
533
            foo_bugtasks_and_nominations_view.getBugTaskAndNominationViews())
534
        self.assertEqual([], task_and_nomination_views)
535
13543.10.1 by William Grant
Display a faded statusless row for the parents of orphaned bugtasks.
536
    def test_bugtarget_parent_shown_for_orphaned_series_tasks(self):
537
        # Test that a row is shown for the parent of a series task, even
538
        # if the parent doesn't actually have a task.
539
        series = self.factory.makeProductSeries()
540
        bug = self.factory.makeBug(series=series)
541
        self.assertEqual(2, len(bug.bugtasks))
542
        new_prod = self.factory.makeProduct()
543
        bug.getBugTask(series.product).transitionToTarget(new_prod)
544
545
        view = create_initialized_view(bug, "+bugtasks-and-nominations-table")
546
        subviews = view.getBugTaskAndNominationViews()
547
        self.assertEqual([
548
            (series.product, '+bugtasks-and-nominations-table-row'),
549
            (bug.getBugTask(series), '+bugtasks-and-nominations-table-row'),
550
            (bug.getBugTask(new_prod), '+bugtasks-and-nominations-table-row'),
551
            ], [(v.context, v.__name__) for v in subviews])
552
553
        content = subviews[0]()
554
        self.assertIn(
555
            'href="%s"' % canonical_url(
556
                series.product, path_only_if_possible=True),
557
            content)
558
        self.assertIn(series.product.displayname, content)
559
2770.1.40 by Guilherme Salgado
A few fixes Bjorn suggested and removing two unused templates.
560
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
561
class TestBugTaskEditViewStatusField(TestCaseWithFactory):
562
    """We show only those options as possible value in the status
563
    field that the user can select.
564
    """
565
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
566
    layer = DatabaseFunctionalLayer
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
567
568
    def setUp(self):
569
        super(TestBugTaskEditViewStatusField, self).setUp()
10680.2.4 by Abel Deuring
replaced the no longer existing method factoy.makPersonNoCommit(9 by makePerson(); ensure that only project owners and supervisors can set a bugtask status to 'expired'
570
        product_owner = self.factory.makePerson(name='product-owner')
571
        bug_supervisor = self.factory.makePerson(name='bug-supervisor')
572
        product = self.factory.makeProduct(
10680.2.2 by Abel Deuring
implemented reviewer's comments
573
            owner=product_owner, bug_supervisor=bug_supervisor)
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
574
        self.bug = self.factory.makeBug(product=product)
575
576
    def getWidgetOptionTitles(self, widget):
577
        """Return the titles of options of the given choice widget."""
578
        return [
579
            item.value.title for item in widget.field.vocabulary]
580
581
    def test_status_field_items_for_anonymous(self):
582
        # Anonymous users see only the current value.
583
        login(ANONYMOUS)
584
        view = BugTaskEditView(
585
            self.bug.default_bugtask, LaunchpadTestRequest())
586
        view.initialize()
587
        self.assertEqual(
588
            ['New'], self.getWidgetOptionTitles(view.form_fields['status']))
589
590
    def test_status_field_items_for_ordinary_users(self):
591
        # Ordinary users can set the status to all values except Won't fix,
592
        # Expired, Triaged, Unknown.
11474.2.1 by Robert Collins
Use sampledata constants.
593
        login(NO_PRIVILEGE_EMAIL)
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
594
        view = BugTaskEditView(
595
            self.bug.default_bugtask, LaunchpadTestRequest())
596
        view.initialize()
597
        self.assertEqual(
7675.718.1 by Abel Deuring
add a bug task status OPINION
598
            ['New', 'Incomplete', 'Opinion', 'Invalid', 'Confirmed',
599
             'In Progress', 'Fix Committed', 'Fix Released'],
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
600
            self.getWidgetOptionTitles(view.form_fields['status']))
601
602
    def test_status_field_privileged_persons(self):
603
        # The bug target owner and the bug target supervisor can set
604
        # the status to any value except Unknown and Expired.
605
        for user in (
606
            self.bug.default_bugtask.pillar.owner,
607
            self.bug.default_bugtask.pillar.bug_supervisor):
608
            login_person(user)
609
            view = BugTaskEditView(
610
                self.bug.default_bugtask, LaunchpadTestRequest())
611
            view.initialize()
612
            self.assertEqual(
7675.718.1 by Abel Deuring
add a bug task status OPINION
613
                ['New', 'Incomplete', 'Opinion', 'Invalid', "Won't Fix",
614
                 'Confirmed', 'Triaged', 'In Progress', 'Fix Committed',
615
                 'Fix Released'],
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
616
                self.getWidgetOptionTitles(view.form_fields['status']),
617
                'Unexpected set of settable status options for %s'
618
                % user.name)
619
620
    def test_status_field_bug_task_in_status_unknown(self):
621
        # If a bugtask has the status Unknown, this status is included
622
        # in the options.
10680.2.2 by Abel Deuring
implemented reviewer's comments
623
        owner = self.bug.default_bugtask.pillar.owner
624
        login_person(owner)
625
        self.bug.default_bugtask.transitionToStatus(
626
            BugTaskStatus.UNKNOWN, owner)
11474.2.1 by Robert Collins
Use sampledata constants.
627
        login(NO_PRIVILEGE_EMAIL)
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
628
        view = BugTaskEditView(
629
            self.bug.default_bugtask, LaunchpadTestRequest())
630
        view.initialize()
631
        self.assertEqual(
7675.718.1 by Abel Deuring
add a bug task status OPINION
632
            ['New', 'Incomplete', 'Opinion', 'Invalid', 'Confirmed',
633
             'In Progress', 'Fix Committed', 'Fix Released', 'Unknown'],
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
634
            self.getWidgetOptionTitles(view.form_fields['status']))
635
636
    def test_status_field_bug_task_in_status_expired(self):
10680.2.2 by Abel Deuring
implemented reviewer's comments
637
        # If a bugtask has the status Expired, this status is included
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
638
        # in the options.
639
        removeSecurityProxy(self.bug.default_bugtask).status = (
640
            BugTaskStatus.EXPIRED)
11474.2.1 by Robert Collins
Use sampledata constants.
641
        login(NO_PRIVILEGE_EMAIL)
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
642
        view = BugTaskEditView(
643
            self.bug.default_bugtask, LaunchpadTestRequest())
644
        view.initialize()
645
        self.assertEqual(
7675.718.1 by Abel Deuring
add a bug task status OPINION
646
            ['New', 'Incomplete', 'Opinion', 'Invalid', 'Expired',
647
             'Confirmed', 'In Progress', 'Fix Committed', 'Fix Released'],
10680.2.1 by Abel Deuring
Add a new bug task status 'Expired'; the bug task expiration script sets expired bug tasks to this status; related adjustments to the bug task views
648
            self.getWidgetOptionTitles(view.form_fields['status']))
649
650
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
651
class TestBugTaskEditViewAssigneeField(TestCaseWithFactory):
652
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
653
    layer = DatabaseFunctionalLayer
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
654
655
    def setUp(self):
656
        super(TestBugTaskEditViewAssigneeField, self).setUp()
11435.6.11 by Deryck Hodge
Fix browser test.
657
        self.owner = self.factory.makePerson()
658
        self.product = self.factory.makeProduct(owner=self.owner)
659
        self.bugtask = self.factory.makeBug(
660
            product=self.product).default_bugtask
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
661
11435.6.11 by Deryck Hodge
Fix browser test.
662
    def test_assignee_vocabulary_regular_user_with_bug_supervisor(self):
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
663
        # For regular users, the assignee vocabulary is
11435.6.11 by Deryck Hodge
Fix browser test.
664
        # AllUserTeamsParticipation if there is a bug supervisor defined.
665
        login_person(self.owner)
666
        self.product.setBugSupervisor(self.owner, self.owner)
11474.2.1 by Robert Collins
Use sampledata constants.
667
        login(USER_EMAIL)
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
668
        view = BugTaskEditView(self.bugtask, LaunchpadTestRequest())
669
        view.initialize()
670
        self.assertEqual(
671
            'AllUserTeamsParticipation',
672
            view.form_fields['assignee'].field.vocabularyName)
673
11435.6.11 by Deryck Hodge
Fix browser test.
674
    def test_assignee_vocabulary_regular_user_without_bug_supervisor(self):
675
        # For regular users, the assignee vocabulary is
676
        # ValidAssignee is there is not a bug supervisor defined.
677
        login_person(self.owner)
678
        self.product.setBugSupervisor(None, self.owner)
11474.2.7 by Robert Collins
Resolve conflicts with trunk.
679
        login(USER_EMAIL)
11435.6.11 by Deryck Hodge
Fix browser test.
680
        view = BugTaskEditView(self.bugtask, LaunchpadTestRequest())
681
        view.initialize()
682
        self.assertEqual(
683
            'ValidAssignee',
684
            view.form_fields['assignee'].field.vocabularyName)
685
10788.5.2 by Abel Deuring
ordinary users can (un)assign a bug task only to hemselves and their teams
686
    def test_assignee_field_vocabulary_privileged_user(self):
687
        # Privileged users, like the bug task target owner, can
688
        # assign anybody.
689
        login_person(self.bugtask.target.owner)
690
        view = BugTaskEditView(self.bugtask, LaunchpadTestRequest())
691
        view.initialize()
692
        self.assertEqual(
693
            'ValidAssignee',
694
            view.form_fields['assignee'].field.vocabularyName)
695
696
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
697
class TestBugTaskEditView(TestCaseWithFactory):
12622.5.1 by Curtis Hovey
Always remove the bugtask milestone when retargeting the product.
698
    """Test the bug task edit form."""
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
699
700
    layer = DatabaseFunctionalLayer
701
12599.4.2 by Leonard Richardson
Merge from trunk.
702
    def test_retarget_already_exists_error(self):
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
703
        user = self.factory.makePerson()
704
        login_person(user)
705
        ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
706
        dsp_1 = self.factory.makeDistributionSourcePackage(
707
            distribution=ubuntu, sourcepackagename='mouse')
13247.1.1 by Danilo Segan
Reapply bug 772754 fix with packagecopyjob changes removed.
708
        self.factory.makeSourcePackagePublishingHistory(
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
709
            distroseries=ubuntu.currentseries,
710
            sourcepackagename=dsp_1.sourcepackagename)
711
        bug_task_1 = self.factory.makeBugTask(target=dsp_1)
712
        dsp_2 = self.factory.makeDistributionSourcePackage(
713
            distribution=ubuntu, sourcepackagename='rabbit')
13247.1.1 by Danilo Segan
Reapply bug 772754 fix with packagecopyjob changes removed.
714
        self.factory.makeSourcePackagePublishingHistory(
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
715
            distroseries=ubuntu.currentseries,
716
            sourcepackagename=dsp_2.sourcepackagename)
717
        bug_task_2 = self.factory.makeBugTask(
718
            bug=bug_task_1.bug, target=dsp_2)
719
        form = {
720
            'ubuntu_rabbit.actions.save': 'Save Changes',
721
            'ubuntu_rabbit.status': 'In Progress',
722
            'ubuntu_rabbit.importance': 'High',
723
            'ubuntu_rabbit.assignee.option':
724
                'ubuntu_rabbit.assignee.assign_to_nobody',
725
            'ubuntu_rabbit.sourcepackagename': 'mouse',
726
            }
727
        view = create_initialized_view(
12599.4.2 by Leonard Richardson
Merge from trunk.
728
            bug_task_2, name='+editstatus', form=form, principal=user)
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
729
        self.assertEqual(1, len(view.errors))
730
        self.assertEqual(
13506.4.17 by William Grant
Fix test.
731
            'A fix for this bug has already been requested for mouse in '
732
            'Ubuntu',
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
733
            view.errors[0])
734
12622.5.2 by Curtis Hovey
Added a test to verify that assiging a milestone during a retargeting is ignored.
735
    def setUpRetargetMilestone(self):
736
        """Setup a bugtask with a milestone and a product to retarget to."""
12622.5.1 by Curtis Hovey
Always remove the bugtask milestone when retargeting the product.
737
        first_product = self.factory.makeProduct(name='bunny')
738
        with person_logged_in(first_product.owner):
739
            first_product.official_malone = True
740
            bug = self.factory.makeBug(product=first_product)
741
            bug_task = bug.bugtasks[0]
742
            milestone = self.factory.makeMilestone(
743
                productseries=first_product.development_focus, name='1.0')
744
            bug_task.transitionToMilestone(milestone, first_product.owner)
745
        second_product = self.factory.makeProduct(name='duck')
746
        with person_logged_in(second_product.owner):
747
            second_product.official_malone = True
12622.5.2 by Curtis Hovey
Added a test to verify that assiging a milestone during a retargeting is ignored.
748
        return bug_task, second_product
749
750
    def test_retarget_product_with_milestone(self):
751
        # Milestones are always cleared when retargeting a product bug task.
752
        bug_task, second_product = self.setUpRetargetMilestone()
12622.5.1 by Curtis Hovey
Always remove the bugtask milestone when retargeting the product.
753
        user = self.factory.makePerson()
754
        login_person(user)
755
        form = {
756
            'bunny.status': 'In Progress',
757
            'bunny.assignee.option': 'bunny.assignee.assign_to_nobody',
758
            'bunny.product': 'duck',
759
            'bunny.actions.save': 'Save Changes',
760
            }
761
        view = create_initialized_view(
762
            bug_task, name='+editstatus', form=form)
763
        self.assertEqual([], view.errors)
764
        self.assertEqual(second_product, bug_task.target)
765
        self.assertEqual(None, bug_task.milestone)
12622.5.2 by Curtis Hovey
Added a test to verify that assiging a milestone during a retargeting is ignored.
766
        notifications = view.request.response.notifications
767
        self.assertEqual(1, len(notifications))
768
        expected = ('The Bunny 1.0 milestone setting has been removed')
769
        self.assertTrue(notifications.pop().message.startswith(expected))
770
771
    def test_retarget_product_and_assign_milestone(self):
772
        # Milestones are always cleared when retargeting a product bug task.
773
        bug_task, second_product = self.setUpRetargetMilestone()
774
        login_person(bug_task.target.owner)
775
        milestone_id = bug_task.milestone.id
776
        bug_task.transitionToMilestone(None, bug_task.target.owner)
777
        form = {
778
            'bunny.status': 'In Progress',
779
            'bunny.assignee.option': 'bunny.assignee.assign_to_nobody',
780
            'bunny.product': 'duck',
781
            'bunny.milestone': milestone_id,
782
            'bunny.actions.save': 'Save Changes',
783
            }
784
        view = create_initialized_view(
785
            bug_task, name='+editstatus', form=form)
786
        self.assertEqual([], view.errors)
787
        self.assertEqual(second_product, bug_task.target)
788
        self.assertEqual(None, bug_task.milestone)
789
        notifications = view.request.response.notifications
790
        self.assertEqual(1, len(notifications))
791
        expected = ('The milestone setting was ignored')
792
        self.assertTrue(notifications.pop().message.startswith(expected))
12622.5.1 by Curtis Hovey
Always remove the bugtask milestone when retargeting the product.
793
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
794
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
795
class TestProjectGroupBugs(TestCaseWithFactory):
796
    """Test the bugs overview page for Project Groups."""
797
12561.3.12 by Curtis Hovey
Fixed broken view while rewriting a broken test.
798
    layer = DatabaseFunctionalLayer
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
799
800
    def setUp(self):
801
        super(TestProjectGroupBugs, self).setUp()
802
        self.owner = self.factory.makePerson(name='bob')
803
        self.projectgroup = self.factory.makeProject(name='container',
804
                                                     owner=self.owner)
805
806
    def makeSubordinateProduct(self, tracks_bugs_in_lp):
807
        """Create a new product and add it to the project group."""
808
        product = self.factory.makeProduct(official_malone=tracks_bugs_in_lp)
809
        with person_logged_in(product.owner):
810
            product.project = self.projectgroup
811
812
    def test_empty_project_group(self):
813
        # An empty project group does not use Launchpad for bugs.
814
        view = create_initialized_view(
815
            self.projectgroup, name=u'+bugs', rootsite='bugs')
816
        self.assertFalse(self.projectgroup.hasProducts())
817
        self.assertFalse(view.should_show_bug_information)
818
819
    def test_project_group_with_subordinate_not_using_launchpad(self):
820
        # A project group with all subordinates not using Launchpad
821
        # will itself be marked as not using Launchpad for bugs.
822
        self.makeSubordinateProduct(False)
823
        self.assertTrue(self.projectgroup.hasProducts())
824
        view = create_initialized_view(
825
            self.projectgroup, name=u'+bugs', rootsite='bugs')
826
        self.assertFalse(view.should_show_bug_information)
827
828
    def test_project_group_with_subordinate_using_launchpad(self):
829
        # A project group with one subordinate using Launchpad
830
        # will itself be marked as using Launchpad for bugs.
831
        self.makeSubordinateProduct(True)
832
        self.assertTrue(self.projectgroup.hasProducts())
833
        view = create_initialized_view(
834
            self.projectgroup, name=u'+bugs', rootsite='bugs')
835
        self.assertTrue(view.should_show_bug_information)
836
837
    def test_project_group_with_mixed_subordinates(self):
838
        # A project group with one or more subordinates using Launchpad
839
        # will itself be marked as using Launchpad for bugs.
840
        self.makeSubordinateProduct(False)
841
        self.makeSubordinateProduct(True)
842
        self.assertTrue(self.projectgroup.hasProducts())
843
        view = create_initialized_view(
844
            self.projectgroup, name=u'+bugs', rootsite='bugs')
845
        self.assertTrue(view.should_show_bug_information)
846
847
    def test_project_group_has_no_portlets_if_not_using_LP(self):
11655.1.5 by Brad Crittenden
Post review fixes
848
        # A project group that has no projects using Launchpad will not have
849
        # bug portlets.
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
850
        self.makeSubordinateProduct(False)
851
        view = create_initialized_view(
852
            self.projectgroup, name=u'+bugs', rootsite='bugs',
853
            current_request=True)
854
        self.assertFalse(view.should_show_bug_information)
855
        contents = view.render()
856
        report_a_bug = find_tag_by_id(contents, 'bug-portlets')
857
        self.assertIs(None, report_a_bug)
858
859
    def test_project_group_has_portlets_link_if_using_LP(self):
11655.1.5 by Brad Crittenden
Post review fixes
860
        # A project group that has projects using Launchpad will have a
861
        # portlets.
11655.1.1 by Brad Crittenden
Do not show bug information on a project group page with no projects that use Launchpad.
862
        self.makeSubordinateProduct(True)
863
        view = create_initialized_view(
864
            self.projectgroup, name=u'+bugs', rootsite='bugs',
865
            current_request=True)
866
        self.assertTrue(view.should_show_bug_information)
867
        contents = view.render()
868
        report_a_bug = find_tag_by_id(contents, 'bug-portlets')
869
        self.assertIsNot(None, report_a_bug)
870
11655.1.9 by Brad Crittenden
Add help link for configuring bugs
871
    def test_project_group_has_help_link_if_not_using_LP(self):
872
        # A project group that has no projects using Launchpad will have
873
        # a 'Getting started' help link.
874
        self.makeSubordinateProduct(False)
875
        view = create_initialized_view(
876
            self.projectgroup, name=u'+bugs', rootsite='bugs',
877
            current_request=True)
878
        contents = view.render()
879
        help_link = find_tag_by_id(contents, 'getting-started-help')
880
        self.assertIsNot(None, help_link)
881
882
    def test_project_group_has_no_help_link_if_using_LP(self):
883
        # A project group that has no projects using Launchpad will not have
884
        # a 'Getting started' help link.
885
        self.makeSubordinateProduct(True)
886
        view = create_initialized_view(
887
            self.projectgroup, name=u'+bugs', rootsite='bugs',
888
            current_request=True)
889
        contents = view.render()
890
        help_link = find_tag_by_id(contents, 'getting-started-help')
891
        self.assertIs(None, help_link)
12792.8.1 by William Grant
Add failing test for BugActivityItem assignee escaping.
892
893
894
class TestBugActivityItem(TestCaseWithFactory):
895
896
    layer = DatabaseFunctionalLayer
897
898
    def setAttribute(self, obj, attribute, value):
899
        obj_before_modification = Snapshot(obj, providing=providedBy(obj))
900
        setattr(removeSecurityProxy(obj), attribute, value)
901
        notify(ObjectModifiedEvent(
902
            obj, obj_before_modification, [attribute],
903
            self.factory.makePerson()))
904
905
    def test_escapes_assignee(self):
906
        with celebrity_logged_in('admin'):
907
            task = self.factory.makeBugTask()
908
            self.setAttribute(
909
                task, 'assignee',
910
                self.factory.makePerson(displayname="Foo &<>", name='foo'))
911
        self.assertEquals(
912
            "nobody &#8594; Foo &amp;&lt;&gt; (foo)",
913
            BugActivityItem(task.bug.activity[-1]).change_details)
914
915
    def test_escapes_title(self):
916
        with celebrity_logged_in('admin'):
917
            bug = self.factory.makeBug(title="foo")
918
            self.setAttribute(bug, 'title', "bar &<>")
919
        self.assertEquals(
920
            "- foo<br />+ bar &amp;&lt;&gt;",
921
            BugActivityItem(bug.activity[-1]).change_details)