Merge lp:~sinzui/launchpad/bug-contacts-1 into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Merged at revision: 10918
Proposed branch: lp:~sinzui/launchpad/bug-contacts-1
Merge into: lp:launchpad
Diff against target: 980 lines (+510/-335)
9 files modified
lib/lp/bugs/browser/bugrole.py (+127/-0)
lib/lp/bugs/browser/bugsupervisor.py (+24/-96)
lib/lp/bugs/browser/securitycontact.py (+20/-24)
lib/lp/bugs/browser/tests/test_bugsupervisor.py (+167/-0)
lib/lp/bugs/browser/tests/test_securitycontact.py (+154/-0)
lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt (+6/-8)
lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt (+8/-71)
lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt (+0/-131)
lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt (+4/-5)
To merge this branch: bzr merge lp:~sinzui/launchpad/bug-contacts-1
Reviewer Review Type Date Requested Status
Henning Eggers (community) code Approve
Review via email: mp+25911@code.launchpad.net

Description of the change

This is my branch to refactor bug role view code for reuse in another view.

    lp:~sinzui/launchpad/bug-contacts-1
    Diff size: 797 (includes deletions of redundant tests)
    Launchpad bug: https://bugs.launchpad.net/bugs/133676
    Test command: ./bin/test -vv \
        -t test_bugsupervisor -t test_securitycontact -t initial-bug-contacts
    Pre-implementation: no one
    Target release: 10.05

Refactor bug role view code for reuse
-------------------------------------

+configure-bugtraker will the support bug supervisor and security contact
fields. The form views for those fields need refactoring so that common
validation and change behaviours can be shared.

Rules
-----

    * Extract the validate() and change actions of BugSupervisorEditView and
      SecurityContactEditView.
    * Use the same validation rules--security contact has no rules.
      This will fix bug 133676

QA
--

    * Visit http://bugs.edge.launchpad.net/launchpad-registry
    * Change the bug supervisor and security contact roles to persons,
      teams, and none to verify everything still works.

Lint
----

Linting changed files:
  lib/lp/bugs/browser/bugsupervisor.py
  lib/lp/bugs/browser/securitycontact.py
  lib/lp/bugs/browser/tests/test_bugsupervisor.py
  lib/lp/bugs/browser/tests/test_securitycontact.py
  lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt

Test
----

I created two tests to verify the behaviours imparted from the mixin and
the view under test. The two tests are similar because of the four implicit
states of validation and permission rules, but their messages and data were
too different to make a single test suite.

    * lib/lp/bugs/browser/tests/test_bugsupervisor.py
      * Test the validation and message behaviours.
    * lib/lp/bugs/browser/tests/test_securitycontact.py
      * Test the validation and message behaviours.
    * lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt
      * Deleted parts that tested the view, not the user experience.

Implementation
--------------

    * lib/lp/bugs/browser/bugsupervisor.py
      Extracted a mixin to do validation and data changes.
    * lib/lp/bugs/browser/securitycontact.py
      Used the mixin in the existing form. Removed the odd messages and
      assertion that the mixin controls.

To post a comment you must log in.
Revision history for this message
Henning Eggers (henninge) wrote :
Download full text (11.5 KiB)

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Am 24.05.2010 20:20, schrieb Curtis Hovey:
> This is my branch to refactor bug role view code for reuse in another view.

Thanks for doing this!

>
> lp:~sinzui/launchpad/bug-contacts-1
> Diff size: 797 (includes deletions of redundant tests)
> Launchpad bug: https://bugs.launchpad.net/bugs/133676
> Test command: ./bin/test -vv \
> -t test_bugsupervisor -t test_securitycontact -t initial-bug-contacts
> Pre-implementation: no one

I guess I should trust you that this is the right thing to do. Sure looks
right but shouldn't the bugs team at least know about it?

> Target release: 10.05
>
>
> Refactor bug role view code for reuse
> -------------------------------------

[...]

> === modified file 'lib/lp/bugs/browser/bugsupervisor.py'

All good here except that I wonder if the Mixin should go in its own file?
(See next comment)

> === modified file 'lib/lp/bugs/browser/securitycontact.py'
> --- lib/lp/bugs/browser/securitycontact.py 2010-05-18 21:24:27 +0000
> +++ lib/lp/bugs/browser/securitycontact.py 2010-05-24 18:20:55 +0000
> @@ -7,11 +7,12 @@
> __all__ = ["SecurityContactEditView"]
>
> from lp.bugs.interfaces.securitycontact import IHasSecurityContact
> +from lp.bugs.browser.bugsupervisor import BugRoleMixin

from lp.bugs.browser.bugrole import BugRoleMixin

What do you think?

[...]

> === added file 'lib/lp/bugs/browser/tests/test_bugsupervisor.py'
> --- lib/lp/bugs/browser/tests/test_bugsupervisor.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/bugs/browser/tests/test_bugsupervisor.py 2010-05-24 18:20:55 +0000

Thanks for adding this test. ;)

> @@ -0,0 +1,166 @@
> +# Copyright10 Canonical Ltd. This software is licensed under the

# Copyright 2010 Canonical Ltd...

> +# GNU Affero General Public License version (see the file LICENSE).
> +
> +"""Unit tests for bug supervisor views."""
> +
> +__metaclass__ = type
> +
> +import unittest
> +
> +from zope.app.form.interfaces import ConversionError
> +
> +from lp.bugs.browser.bugsupervisor import BugSupervisorEditSchema
> +from lp.testing import login, login_person, TestCaseWithFactory
> +from lp.testing.views import create_initialized_view
> +from canonical.testing import DatabaseFunctionalLayer
> +
> +
> +class TestBugSupervisorEditView(TestCaseWithFactory):
> +
> + layer = DatabaseFunctionalLayer
> +
> + def setUp(self):
> + super(TestBugSupervisorEditView, self).setUp()
> + self.owner = self.factory.makePerson(
> + name='splat', displayname='<splat />')

I understand that you use specific names because they appear in the messages
that you test but using a "tag" displayname here seems a bit cryptic. All
those &lt; and &gt; make the messages harder to read. I'd suggest using proper
names here and use the "tags" when you need them. See my suggestion below.

> + self.product = self.factory.makeProduct(
> + name="boing", displayname='<boing />', owner=self.owner)

Same here.

> + self.team = self.factory.makeTeam(name='thud', owner=self.owner)
> + login_person(self.owner)
> +
> + def _makeForm(self, person):
> + if person is None:
> + ...

review: Approve (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (12.1 KiB)

On Tue, 2010-05-25 at 16:34 +0000, Henning Eggers wrote:
...
> > === modified file 'lib/lp/bugs/browser/securitycontact.py'
> > --- lib/lp/bugs/browser/securitycontact.py 2010-05-18 21:24:27 +0000
> > +++ lib/lp/bugs/browser/securitycontact.py 2010-05-24 18:20:55 +0000
> > @@ -7,11 +7,12 @@
> > __all__ = ["SecurityContactEditView"]
> >
> > from lp.bugs.interfaces.securitycontact import IHasSecurityContact
> > +from lp.bugs.browser.bugsupervisor import BugRoleMixin
>
> from lp.bugs.browser.bugrole import BugRoleMixin
>
> What do you think?

I was thinking of __init__ since it is common. I did not create a module
because it would inflate the diff. Deryck agrees with
lp.bugs.browser.bugroles.

> [...]
>
> > === added file 'lib/lp/bugs/browser/tests/test_bugsupervisor.py'
> > --- lib/lp/bugs/browser/tests/test_bugsupervisor.py 1970-01-01 00:00:00 +0000
> > +++ lib/lp/bugs/browser/tests/test_bugsupervisor.py 2010-05-24 18:20:55 +0000
>
> Thanks for adding this test. ;)
>
> > @@ -0,0 +1,166 @@
> > +# Copyright10 Canonical Ltd. This software is licensed under the
>
> # Copyright 2010 Canonical Ltd...
>
> > +# GNU Affero General Public License version (see the file LICENSE).
> > +
> > +"""Unit tests for bug supervisor views."""
> > +
> > +__metaclass__ = type
> > +
> > +import unittest
> > +
> > +from zope.app.form.interfaces import ConversionError
> > +
> > +from lp.bugs.browser.bugsupervisor import BugSupervisorEditSchema
> > +from lp.testing import login, login_person, TestCaseWithFactory
> > +from lp.testing.views import create_initialized_view
> > +from canonical.testing import DatabaseFunctionalLayer
> > +
> > +
> > +class TestBugSupervisorEditView(TestCaseWithFactory):
> > +
> > + layer = DatabaseFunctionalLayer
> > +
> > + def setUp(self):
> > + super(TestBugSupervisorEditView, self).setUp()
> > + self.owner = self.factory.makePerson(
> > + name='splat', displayname='<splat />')
>
> I understand that you use specific names because they appear in the messages
> that you test but using a "tag" displayname here seems a bit cryptic. All
> those &lt; and &gt; make the messages harder to read. I'd suggest using proper
> names here and use the "tags" when you need them. See my suggestion below.

I rather not since these *are* only used when they are needed. I am not
a bug domain expert, but I am certain that showing a notification for
the change, and including links in it is a strong indication something
is fundamentally wrong with these roles. It is clear that changing the
roles is dangerous and the UI did not set my expectation *before* I made
the change. The markup has historically been an XSS issue and the
testing for this is weak, Each name is tested exactly once for each
message. I think this qualifies for just enough testing.

> > + self.product = self.factory.makeProduct(
> > + name="boing", displayname='<boing />', owner=self.owner)
>
> Same here.

^ as above.

> > + self.team = self.factory.makeTeam(name='thud', owner=self.owner)
> > + login_person(self.owner)
> > +
> > + def _makeForm(self, person):
> > + if person is None:
> > + name = ''
> ...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'lib/lp/bugs/browser/bugrole.py'
2--- lib/lp/bugs/browser/bugrole.py 1970-01-01 00:00:00 +0000
3+++ lib/lp/bugs/browser/bugrole.py 2010-05-25 21:21:40 +0000
4@@ -0,0 +1,127 @@
5+# Copyright 2010 Canonical Ltd. This software is licensed under the
6+# GNU Affero General Public License version (see the file LICENSE).
7+
8+"""Common classes to support bug roles."""
9+
10+__metaclass__ = type
11+
12+__all__ = [
13+ 'BugRoleMixin',
14+ ]
15+
16+from canonical.launchpad.webapp.menu import structured
17+from canonical.launchpad.webapp.publisher import canonical_url
18+
19+
20+class BugRoleMixin:
21+
22+ INVALID_PERSON = object()
23+ OTHER_USER = object()
24+ OTHER_TEAM = object()
25+ OK = object()
26+
27+ def _getFieldState(self, field_name, data):
28+ """Return the enum that summarises the field state."""
29+ # The field_name will not be in the data if the user did not enter
30+ # a person in the ValidPersonOrTeam vocabulary.
31+ if field_name not in data:
32+ return self.INVALID_PERSON
33+ role = data[field_name]
34+ user = self.user
35+ # The user may assign the role to None, himself, or a team he admins.
36+ if role is None or self.context.userCanAlterSubscription(role, user):
37+ return self.OK
38+ # The user is not an admin of the team, or he entered another user.
39+ if role.isTeam():
40+ return self.OTHER_TEAM
41+ else:
42+ return self.OTHER_USER
43+
44+ def validateBugSupervisor(self, data):
45+ """Validates the new bug supervisor.
46+
47+ Verify that the value is None, the user, or a team he administers,
48+ otherwise, set a field error.
49+ """
50+ field_state = self._getFieldState('bug_supervisor', data)
51+ if field_state is self.INVALID_PERSON:
52+ error = (
53+ 'You must choose a valid person or team to be the '
54+ 'bug supervisor for %s.' % self.context.displayname)
55+ elif field_state is self.OTHER_TEAM:
56+ supervisor = data['bug_supervisor']
57+ team_url = canonical_url(
58+ supervisor, rootsite='mainsite', view_name='+members')
59+ error = structured(
60+ 'You cannot set %(team)s as the bug supervisor for '
61+ '%(target)s because you are not an administrator of that '
62+ 'team.<br />If you believe that %(team)s should be the '
63+ 'bug supervisor for %(target)s, notify one of the '
64+ '<a href="%(url)s">%(team)s administrators</a>. See '
65+ '<a href="https://help.launchpad.net/BugSupervisors">'
66+ 'the help wiki</a> for information about setting a bug '
67+ 'supervisor.',
68+ team=supervisor.displayname,
69+ target=self.context.displayname,
70+ url=team_url)
71+ elif field_state is self.OTHER_USER:
72+ error = structured(
73+ 'You cannot set another person as the bug supervisor for '
74+ '%(target)s.<br />See '
75+ '<a href="https://help.launchpad.net/BugSupervisors">'
76+ 'the help wiki</a> for information about setting a bug '
77+ 'supervisor.',
78+ target=self.context.displayname)
79+ else:
80+ # field_state is self.OK.
81+ return
82+ self.setFieldError('bug_supervisor', error)
83+
84+ def changeBugSupervisor(self, bug_supervisor):
85+ self.context.setBugSupervisor(bug_supervisor, self.user)
86+ if bug_supervisor is not None:
87+ self.request.response.addNotification(structured(
88+ 'Successfully changed the bug supervisor to '
89+ '<a href="%(supervisor_url)s">%(displayname)s</a>.'
90+ '<br /><a href="%(supervisor_url)s">%(displayname)s</a> '
91+ 'has also been subscribed to bug notifications for '
92+ '%(targetname)s.<br />You can '
93+ '<a href="%(targeturl)s/+subscribe">change the '
94+ 'subscriptions</a> for %(targetname)s at any time.',
95+ supervisor_url=canonical_url(bug_supervisor),
96+ displayname=bug_supervisor.displayname,
97+ targetname=self.context.displayname,
98+ targeturl=canonical_url(self.context)))
99+
100+ def validateSecurityContact(self, data):
101+ """Validates the new security contact.
102+
103+ Verify that the value is None, the user, or a team he administers,
104+ otherwise, set a field error.
105+ """
106+ field_state = self._getFieldState('security_contact', data)
107+ if field_state is self.INVALID_PERSON:
108+ error = (
109+ 'You must choose a valid person or team to be the '
110+ 'security contact for %s.' % self.context.displayname)
111+ elif field_state is self.OTHER_TEAM:
112+ supervisor = data['security_contact']
113+ team_url = canonical_url(
114+ supervisor, rootsite='mainsite', view_name='+members')
115+ error = structured(
116+ 'You cannot set %(team)s as the security contact for '
117+ '%(target)s because you are not an administrator of that '
118+ 'team.<br />If you believe that %(team)s should be the '
119+ 'security contact for %(target)s, notify one of the '
120+ '<a href="%(url)s">%(team)s administrators</a>.',
121+ team=supervisor.displayname,
122+ target=self.context.displayname,
123+ url=team_url)
124+ elif field_state is self.OTHER_USER:
125+ error = structured(
126+ 'You cannot set another person as the security contact for '
127+ '%(target)s.', target=self.context.displayname)
128+ else:
129+ # field_state is self.OK.
130+ return
131+ self.setFieldError('security_contact', error)
132
133=== modified file 'lib/lp/bugs/browser/bugsupervisor.py'
134--- lib/lp/bugs/browser/bugsupervisor.py 2010-04-29 15:32:46 +0000
135+++ lib/lp/bugs/browser/bugsupervisor.py 2010-05-25 21:21:40 +0000
136@@ -1,19 +1,23 @@
137-# Copyright 2009 Canonical Ltd. This software is licensed under the
138+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
139 # GNU Affero General Public License version 3 (see the file LICENSE).
140
141 """Browser view for bug supervisor."""
142
143 __metaclass__ = type
144
145-__all__ = ['BugSupervisorEditView']
146+__all__ = [
147+ 'BugSupervisorEditView',
148+ ]
149
150-from canonical.launchpad.webapp import (
151- action, canonical_url, LaunchpadEditFormView)
152-from canonical.launchpad.webapp.menu import structured
153-from lazr.restful.interface import copy_field, use_template
154 from zope.interface import Interface
155
156+from lazr.restful.interface import copy_field
157+
158+from canonical.launchpad.webapp.launchpadform import (
159+ action, LaunchpadEditFormView)
160+from canonical.launchpad.webapp.publisher import canonical_url
161 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
162+from lp.bugs.browser.bugrole import BugRoleMixin
163
164
165 class BugSupervisorEditSchema(Interface):
166@@ -26,7 +30,7 @@
167 IHasBugSupervisor['bug_supervisor'], readonly=False)
168
169
170-class BugSupervisorEditView(LaunchpadEditFormView):
171+class BugSupervisorEditView(BugRoleMixin, LaunchpadEditFormView):
172 """Browser view class for editing the bug supervisor."""
173
174 schema = BugSupervisorEditSchema
175@@ -47,99 +51,23 @@
176 """See `LaunchpadFormView`"""
177 return {BugSupervisorEditSchema: self.context}
178
179+ @property
180+ def next_url(self):
181+ """See `LaunchpadFormView`."""
182+ return canonical_url(self.context)
183+
184+ cancel_url = next_url
185+
186+ def validate(self, data):
187+ """See `LaunchpadFormView`."""
188+ self.validateBugSupervisor(data)
189+
190 @action('Change', name='change')
191 def change_action(self, action, data):
192 """Redirect to the target page with a success message."""
193- target = self.context
194 bug_supervisor = data['bug_supervisor']
195- target.setBugSupervisor(bug_supervisor, self.user)
196-
197- if bug_supervisor is not None:
198- self.request.response.addNotification(structured(
199- 'Successfully changed the bug supervisor to '
200- '<a href="%(supervisor_url)s">%(displayname)s</a>.'
201- '<br />'
202- '<a href="%(supervisor_url)s">%(displayname)s</a> '
203- 'has also been '
204- 'subscribed to bug notifications for %(targetname)s. '
205- '<br />'
206- 'You can '
207- '<a href="%(targeturl)s/+subscribe">'
208- 'change the subscriptions</a> for '
209- '%(targetname)s at any time.',
210- supervisor_url=canonical_url(bug_supervisor),
211- displayname=bug_supervisor.displayname,
212- targetname=self.context.displayname,
213- targeturl=canonical_url(self.context)))
214- else:
215+ self.changeBugSupervisor(bug_supervisor)
216+ if bug_supervisor is None:
217 self.request.response.addNotification(
218 "Successfully cleared the bug supervisor. "
219 "You can set the bug supervisor again at any time.")
220-
221- self.request.response.redirect(canonical_url(target))
222-
223- def validate(self, data):
224- """Validates the new bug supervisor.
225-
226- The following values are valid as bug supervisors:
227- * None, indicating that the bug supervisor field for the target
228- should be cleared in change_action().
229- * A valid Person (email address or launchpad id).
230- * A valid Team of which the current user is an administrator.
231-
232- If the bug supervisor entered does not meet any of the above
233- criteria then the submission will fail and the user will be notified
234- of the error.
235- """
236-
237- # `data` will not have a bug_supervisor entry in cases where the
238- # bug_supervisor the user entered is valid according to the
239- # ValidPersonOrTeam vocabulary
240- # (i.e. is not a Person, Team or None).
241- if not data.has_key('bug_supervisor'):
242- self.setFieldError(
243- 'bug_supervisor',
244- 'You must choose a valid person or team to be the'
245- ' bug supervisor for %s.' %
246- self.context.displayname)
247-
248- return
249-
250- supervisor = data['bug_supervisor']
251-
252- # Making a person the bug supervisor implies subscribing him
253- # to all bug mail. Ensure that the current user can indeed
254- # do this.
255- if (supervisor is not None and
256- not self.context.userCanAlterSubscription(supervisor, self.user)):
257- if supervisor.isTeam():
258- error = structured(
259- "You cannot set %(team)s as the bug supervisor for "
260- "%(target)s because you are not an administrator of that "
261- "team.<br />If you believe that %(team)s should be the "
262- "bug supervisor for %(target)s, please notify one of the "
263- "<a href=\"%(url)s\">%(team)s administrators</a>. See "
264- "<a href=\"https://help.launchpad.net/BugSupervisors\">"
265- "the help wiki</a> for information about setting a bug "
266- "supervisor.",
267- team=supervisor.displayname,
268- target=self.context.displayname,
269- url=(canonical_url(supervisor, rootsite='mainsite') +
270- '/+members'))
271- self.setFieldError('bug_supervisor', error)
272- else:
273- error = structured(
274- "You cannot set another person as the bug supervisor for "
275- "%(target)s.<br />See "
276- "<a href=\"https://help.launchpad.net/BugSupervisors\">"
277- "the help wiki</a> for information about setting a bug "
278- "supervisor.",
279- person=supervisor.displayname,
280- target=self.context.displayname)
281- self.setFieldError('bug_supervisor', error)
282-
283- def cancel_url(self):
284- """See `LaunchpadFormView`."""
285- return canonical_url(self.context)
286-
287-
288
289=== modified file 'lib/lp/bugs/browser/securitycontact.py'
290--- lib/lp/bugs/browser/securitycontact.py 2010-05-18 21:24:27 +0000
291+++ lib/lp/bugs/browser/securitycontact.py 2010-05-25 21:21:40 +0000
292@@ -1,17 +1,22 @@
293-# Copyright 2009 Canonical Ltd. This software is licensed under the
294+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
295 # GNU Affero General Public License version 3 (see the file LICENSE).
296
297 """Browser view classes for security contacts."""
298
299 __metaclass__ = type
300-__all__ = ["SecurityContactEditView"]
301-
302+
303+__all__ = [
304+ "SecurityContactEditView",
305+ ]
306+
307+from canonical.launchpad.webapp.launchpadform import (
308+ action, LaunchpadFormView)
309+from canonical.launchpad.webapp.publisher import canonical_url
310 from lp.bugs.interfaces.securitycontact import IHasSecurityContact
311-from canonical.launchpad.webapp import (
312- canonical_url, LaunchpadFormView, action)
313-
314-
315-class SecurityContactEditView(LaunchpadFormView):
316+from lp.bugs.browser.bugrole import BugRoleMixin
317+
318+
319+class SecurityContactEditView(BugRoleMixin, LaunchpadFormView):
320 """Browser view for editing the security contact.
321
322 self.context is assumed to implement IHasSecurityContact.
323@@ -35,6 +40,10 @@
324 return {
325 'security_contact': self.context.security_contact}
326
327+ def validate(self, data):
328+ """See `LaunchpadFormView`."""
329+ self.validateSecurityContact(data)
330+
331 @action('Change', name='change')
332 def change_action(self, action, data):
333 security_contact = data['security_contact']
334@@ -43,25 +52,12 @@
335
336 self.context.security_contact = security_contact
337 if security_contact:
338- security_contact_display_value = None
339- if security_contact.preferredemail:
340- # The security contact was set to a new person or team.
341- security_contact_display_value = (
342- security_contact.preferredemail.email)
343- else:
344- # The security contact doesn't have a preferred email address,
345- # so it must be a team.
346- assert security_contact.isTeam(), (
347- "Expected security contact with no email address "
348- "to be a team.")
349- security_contact_display_value = security_contact.displayname
350-
351 self.request.response.addNotification(
352- "Successfully changed the security contact to %s" %
353- security_contact_display_value)
354+ "Successfully changed the security contact to %s." %
355+ security_contact.displayname)
356 else:
357 self.request.response.addNotification(
358- "Successfully removed the security contact")
359+ "Successfully removed the security contact.")
360
361 @property
362 def next_url(self):
363
364=== added file 'lib/lp/bugs/browser/tests/test_bugsupervisor.py'
365--- lib/lp/bugs/browser/tests/test_bugsupervisor.py 1970-01-01 00:00:00 +0000
366+++ lib/lp/bugs/browser/tests/test_bugsupervisor.py 2010-05-25 21:21:40 +0000
367@@ -0,0 +1,167 @@
368+# Copyright10 Canonical Ltd. This software is licensed under the
369+# GNU Affero General Public License version (see the file LICENSE).
370+
371+"""Unit tests for bug supervisor views."""
372+
373+__metaclass__ = type
374+
375+import unittest
376+
377+from zope.app.form.interfaces import ConversionError
378+
379+from lp.bugs.browser.bugsupervisor import BugSupervisorEditSchema
380+from lp.testing import login, login_person, TestCaseWithFactory
381+from lp.testing.views import create_initialized_view
382+from canonical.testing import DatabaseFunctionalLayer
383+
384+
385+class TestBugSupervisorEditView(TestCaseWithFactory):
386+
387+ layer = DatabaseFunctionalLayer
388+
389+ def setUp(self):
390+ super(TestBugSupervisorEditView, self).setUp()
391+ self.owner = self.factory.makePerson(
392+ name='splat', displayname='<splat />')
393+ self.product = self.factory.makeProduct(
394+ name="boing", displayname='<boing />', owner=self.owner)
395+ self.team = self.factory.makeTeam(name='thud', owner=self.owner)
396+ login_person(self.owner)
397+
398+ def _makeForm(self, person):
399+ if person is None:
400+ name = ''
401+ else:
402+ name = person.name
403+ return {
404+ 'field.bug_supervisor': name,
405+ 'field.actions.change': 'Change',
406+ }
407+
408+ def test_view_attributes(self):
409+ self.product.displayname = 'Boing'
410+ view = create_initialized_view(
411+ self.product, name='+bugsupervisor')
412+ label = 'Edit bug supervisor for Boing'
413+ self.assertEqual(label, view.label)
414+ self.assertEqual(label, view.page_title)
415+ fields = ['bug_supervisor']
416+ self.assertEqual(fields, view.field_names)
417+ adapter, context = view.adapters.popitem()
418+ self.assertEqual(BugSupervisorEditSchema, adapter)
419+ self.assertEqual(self.product, context)
420+ self.assertEqual('http://launchpad.dev/boing', view.next_url)
421+ self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
422+
423+ def test_owner_appoint_self_from_none(self):
424+ # This also verifies that displaynames are escaped.
425+ form = self._makeForm(self.owner)
426+ view = create_initialized_view(
427+ self.product, name='+bugsupervisor', form=form)
428+ self.assertEqual([], view.errors)
429+ self.assertEqual(self.product.bug_supervisor, self.owner)
430+ notifications = view.request.response.notifications
431+ self.assertEqual(1, len(notifications))
432+ expected = (
433+ 'Successfully changed the bug supervisor to '
434+ '<a href="http://launchpad.dev/~splat">&lt;splat /&gt;</a>.'
435+ '<br /><a href="http://launchpad.dev/~splat">&lt;splat /&gt;</a> '
436+ 'has also been subscribed to bug notifications for '
437+ '&lt;boing /&gt;.<br />You can '
438+ '<a href="http://launchpad.dev/boing/+subscribe">change '
439+ 'the subscriptions</a> for &lt;boing /&gt; at any time.')
440+ self.assertEqual(expected, notifications.pop().message)
441+
442+ def test_owner_appoint_self_from_another(self):
443+ self.product.setBugSupervisor(self.team, self.owner)
444+ form = self._makeForm(self.owner)
445+ view = create_initialized_view(
446+ self.product, name='+bugsupervisor', form=form)
447+ self.assertEqual([], view.errors)
448+ self.assertEqual(self.owner, self.product.bug_supervisor)
449+
450+ def test_owner_appoint_none(self):
451+ self.product.setBugSupervisor(self.owner, self.owner)
452+ form = self._makeForm(None)
453+ view = create_initialized_view(
454+ self.product, name='+bugsupervisor', form=form)
455+ self.assertEqual([], view.errors)
456+ self.assertEqual(self.product.bug_supervisor, None)
457+ notifications = view.request.response.notifications
458+ self.assertEqual(1, len(notifications))
459+ expected = (
460+ 'Successfully cleared the bug supervisor. '
461+ 'You can set the bug supervisor again at any time.')
462+ self.assertEqual(expected, notifications.pop().message)
463+
464+ def test_owner_appoint_his_team(self):
465+ form = self._makeForm(self.team)
466+ view = create_initialized_view(
467+ self.product, name='+bugsupervisor', form=form)
468+ self.assertEqual([], view.errors)
469+ self.assertEqual(self.team, self.product.bug_supervisor)
470+
471+ def test_owner_cannot_appoint_another_team(self):
472+ team = self.factory.makeTeam(name='smack', displayname='<smack />')
473+ form = self._makeForm(team)
474+ view = create_initialized_view(
475+ self.product, name='+bugsupervisor', form=form)
476+ self.assertEqual(1, len(view.errors))
477+ expected = (
478+ 'You cannot set &lt;smack /&gt; as the bug supervisor for '
479+ '&lt;boing /&gt; because you are not an administrator of that '
480+ 'team.<br />If you believe that &lt;smack /&gt; should be the '
481+ 'bug supervisor for &lt;boing /&gt;, notify one of the '
482+ '<a href="http://launchpad.dev/~smack/+members">&lt;smack /&gt; '
483+ 'administrators</a>. See '
484+ '<a href="https://help.launchpad.net/BugSupervisors">the '
485+ 'help wiki</a> for information about setting a bug supervisor.')
486+ self.assertEqual(expected, view.errors.pop())
487+
488+ def test_owner_cannot_appoint_a_nonvalid_user(self):
489+ # The vocabulary only accepts valid users.
490+ form = self._makeForm(None)
491+ form['field.bug_supervisor'] = 'fnord'
492+ view = create_initialized_view(
493+ self.product, name='+bugsupervisor', form=form)
494+ self.assertEqual(2, len(view.errors))
495+ expected = (
496+ 'You must choose a valid person or team to be the bug supervisor '
497+ 'for &lt;boing /&gt;.')
498+ self.assertEqual(expected, view.errors.pop())
499+ self.assertTrue(isinstance(view.errors.pop(), ConversionError))
500+
501+ def test_owner_cannot_appoint_another_user(self):
502+ another_user = self.factory.makePerson()
503+ form = self._makeForm(another_user)
504+ view = create_initialized_view(
505+ self.product, name='+bugsupervisor', form=form)
506+ self.assertEqual(1, len(view.errors))
507+ expected = (
508+ 'You cannot set another person as the bug supervisor for '
509+ '&lt;boing /&gt;.<br />See '
510+ '<a href="https://help.launchpad.net/BugSupervisors">the help '
511+ 'wiki</a> for information about setting a bug supervisor.')
512+ self.assertEqual(expected, view.errors.pop())
513+
514+ def test_admin_appoint_another_user(self):
515+ another_user = self.factory.makePerson()
516+ login('admin@canonical.com')
517+ form = self._makeForm(another_user)
518+ view = create_initialized_view(
519+ self.product, name='+bugsupervisor', form=form)
520+ self.assertEqual([], view.errors)
521+ self.assertEqual(another_user, self.product.bug_supervisor)
522+
523+ def test_admin_appoint_another_team(self):
524+ another_team = self.factory.makeTeam()
525+ login('admin@canonical.com')
526+ form = self._makeForm(another_team)
527+ view = create_initialized_view(
528+ self.product, name='+bugsupervisor', form=form)
529+ self.assertEqual([], view.errors)
530+ self.assertEqual(another_team, self.product.bug_supervisor)
531+
532+
533+def test_suite():
534+ return unittest.TestLoader().loadTestsFromName(__name__)
535
536=== added file 'lib/lp/bugs/browser/tests/test_securitycontact.py'
537--- lib/lp/bugs/browser/tests/test_securitycontact.py 1970-01-01 00:00:00 +0000
538+++ lib/lp/bugs/browser/tests/test_securitycontact.py 2010-05-25 21:21:40 +0000
539@@ -0,0 +1,154 @@
540+# Copyright10 Canonical Ltd. This software is licensed under the
541+# GNU Affero General Public License version (see the file LICENSE).
542+
543+"""Unit tests for security contact views."""
544+
545+__metaclass__ = type
546+
547+import unittest
548+
549+from zope.app.form.interfaces import ConversionError
550+
551+from lp.testing import login, login_person, TestCaseWithFactory
552+from lp.testing.views import create_initialized_view
553+from canonical.testing import DatabaseFunctionalLayer
554+
555+
556+class TestSecurityContactEditView(TestCaseWithFactory):
557+
558+ layer = DatabaseFunctionalLayer
559+
560+ def setUp(self):
561+ super(TestSecurityContactEditView, self).setUp()
562+ self.owner = self.factory.makePerson(
563+ name='splat', displayname='<splat />')
564+ self.product = self.factory.makeProduct(
565+ name="boing", displayname='<boing />', owner=self.owner)
566+ self.team = self.factory.makeTeam(name='thud', owner=self.owner)
567+ login_person(self.owner)
568+
569+ def _makeForm(self, person):
570+ if person is None:
571+ name = ''
572+ else:
573+ name = person.name
574+ return {
575+ 'field.security_contact': name,
576+ 'field.actions.change': 'Change',
577+ }
578+
579+ def test_view_attributes(self):
580+ self.product.displayname = 'Boing'
581+ view = create_initialized_view(
582+ self.product, name='+securitycontact')
583+ label = 'Edit Boing security contact'
584+ self.assertEqual(label, view.label)
585+ self.assertEqual(label, view.page_title)
586+ fields = ['security_contact']
587+ self.assertEqual(fields, view.field_names)
588+ self.assertEqual('http://launchpad.dev/boing', view.next_url)
589+ self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
590+
591+ def test_owner_appoint_self_from_none(self):
592+ # This also verifies that displaynames are escaped.
593+ form = self._makeForm(self.owner)
594+ view = create_initialized_view(
595+ self.product, name='+securitycontact', form=form)
596+ self.assertEqual([], view.errors)
597+ self.assertEqual(self.product.security_contact, self.owner)
598+ notifications = view.request.response.notifications
599+ self.assertEqual(1, len(notifications))
600+ expected = (
601+ 'Successfully changed the security contact to &lt;splat /&gt;.')
602+ self.assertEqual(expected, notifications.pop().message)
603+
604+ def test_owner_appoint_self_from_another(self):
605+ self.product.security_contact = self.team
606+ form = self._makeForm(self.owner)
607+ view = create_initialized_view(
608+ self.product, name='+securitycontact', form=form)
609+ self.assertEqual([], view.errors)
610+ self.assertEqual(self.owner, self.product.security_contact)
611+
612+ def test_owner_appoint_none(self):
613+ self.product.security_contact = self.owner
614+ form = self._makeForm(None)
615+ view = create_initialized_view(
616+ self.product, name='+securitycontact', form=form)
617+ self.assertEqual([], view.errors)
618+ self.assertEqual(self.product.security_contact, None)
619+ notifications = view.request.response.notifications
620+ self.assertEqual(1, len(notifications))
621+ expected = ('Successfully removed the security contact.')
622+ self.assertEqual(expected, notifications.pop().message)
623+
624+ def test_owner_appoint_his_team(self):
625+ form = self._makeForm(self.team)
626+ view = create_initialized_view(
627+ self.product, name='+securitycontact', form=form)
628+ self.assertEqual([], view.errors)
629+ self.assertEqual(self.product.security_contact, self.team)
630+ notifications = view.request.response.notifications
631+ self.assertEqual(1, len(notifications))
632+ expected = ('Successfully changed the security contact to Thud.')
633+ self.assertEqual(expected, notifications.pop().message)
634+
635+ def test_owner_cannot_appoint_another_team(self):
636+ team = self.factory.makeTeam(name='smack', displayname='<smack />')
637+ form = self._makeForm(team)
638+ view = create_initialized_view(
639+ self.product, name='+securitycontact', form=form)
640+ self.assertEqual(1, len(view.errors))
641+ expected = (
642+ 'You cannot set &lt;smack /&gt; as the security contact for '
643+ '&lt;boing /&gt; because you are not an administrator of that '
644+ 'team.<br />If you believe that &lt;smack /&gt; should be the '
645+ 'security contact for &lt;boing /&gt;, notify one of the '
646+ '<a href="http://launchpad.dev/~smack/+members">&lt;smack /&gt; '
647+ 'administrators</a>.')
648+ self.assertEqual(expected, view.errors.pop())
649+
650+ def test_owner_cannot_appoint_a_nonvalid_user(self):
651+ form = self._makeForm(None)
652+ form['field.security_contact'] = 'fnord'
653+ view = create_initialized_view(
654+ self.product, name='+securitycontact', form=form)
655+ self.assertEqual(2, len(view.errors))
656+ expected = (
657+ 'You must choose a valid person or team to be the '
658+ 'security contact for &lt;boing /&gt;.')
659+ self.assertEqual(expected, view.errors.pop())
660+ self.assertTrue(isinstance(view.errors.pop(), ConversionError))
661+
662+ def test_owner_cannot_appoint_another_user(self):
663+ another_user = self.factory.makePerson()
664+ form = self._makeForm(another_user)
665+ view = create_initialized_view(
666+ self.product, name='+securitycontact', form=form)
667+ self.assertEqual(1, len(view.errors))
668+ expected = (
669+ 'You cannot set another person as the security contact for '
670+ '&lt;boing /&gt;.')
671+ self.assertEqual(expected, view.errors.pop())
672+
673+ def test_admin_appoint_another_user(self):
674+ another_user = self.factory.makePerson()
675+ login('admin@canonical.com')
676+ form = self._makeForm(another_user)
677+ view = create_initialized_view(
678+ self.product, name='+securitycontact', form=form)
679+ self.assertEqual([], view.errors)
680+ self.assertEqual(another_user, self.product.security_contact)
681+
682+ def test_admin_appoint_another_team(self):
683+ another_team = self.factory.makeTeam()
684+ login('admin@canonical.com')
685+ form = self._makeForm(another_team)
686+ view = create_initialized_view(
687+ self.product, name='+securitycontact', form=form)
688+ self.assertEqual([], view.errors)
689+ self.assertEqual(another_team, self.product.security_contact)
690+
691+
692+def test_suite():
693+ return unittest.TestLoader().loadTestsFromName(__name__)
694
695=== modified file 'lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt'
696--- lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt 2009-06-12 16:36:02 +0000
697+++ lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt 2010-05-25 21:21:40 +0000
698@@ -6,17 +6,15 @@
699 >>> browser.open("http://localhost:9000/ubuntu/+securitycontact")
700 >>> browser.getControl("Security Contact").value = "name16"
701 >>> browser.getControl("Change").click()
702- >>> print browser.contents
703- <!DOCTYPE...
704- ...Successfully changed the security contact to foo.bar@canonical.com...
705- ...
706+ >>> for message in get_feedback_messages(browser.contents):
707+ ... print extract_text(message)
708+ Successfully changed the security contact to Foo Bar.
709
710 Or on an upstream.
711
712 >>> browser.open("http://localhost:9000/firefox/+securitycontact")
713 >>> browser.getControl("Security Contact").value = "name21"
714 >>> browser.getControl("Change").click()
715- >>> print browser.contents
716- <!DOCTYPE...
717- ...Successfully changed the security contact to Hoary Gnome Team...
718- ...
719+ >>> for message in get_feedback_messages(browser.contents):
720+ ... print extract_text(message)
721+ Successfully changed the security contact to Hoary Gnome Team.
722
723=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt'
724--- lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt 2009-11-12 15:33:27 +0000
725+++ lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt 2010-05-25 21:21:40 +0000
726@@ -42,74 +42,11 @@
727
728 And then we're redirected to the distribution page.
729
730- >>> colin_browser.url
731- 'http://launchpad.dev/ubuntu'
732-
733- >>> for tag in find_tags_by_class(colin_browser.contents,
734- ... "informational message"):
735- ... print tag
736- <div...Successfully changed the bug supervisor to ...Colin Watson...
737-
738-Now that the bug supervisor is set, there's a link to him on the
739-distribution's bug page page.
740-
741- >>> browser.open('http://bugs.launchpad.dev/ubuntu')
742- >>> browser.getLink(url='/~kamion').url
743- 'http://launchpad.dev/~kamion'
744-
745-When the bug supervisor doesn't have an email address, the displayname is
746-shown instead in the feedback message. Let's appoint the Ubuntu Team as
747-the bug supervisor. Since Colin is an administrator of this team, he can
748-do it.
749-
750- >>> colin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
751- >>> colin_browser.getControl("Bug Supervisor").value = 'ubuntu-team'
752- >>> colin_browser.getControl("Change").click()
753-
754- >>> print extract_text(colin_browser.contents)
755- Ubuntu Linux in Launchpad
756- ...
757- Successfully changed the bug supervisor to Ubuntu Team.
758- ...
759-
760-While anybody with edit permissions can set the bug supervisor, only
761-certain persons can be successfully selected. All users can set
762-themselves self and teams they administer as the bug supervisor, as
763-shown above. But ordinary users cannot appoint other persons and teams
764-they do not administer.
765-
766- >>> colin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
767- >>> colin_browser.getControl("Bug Supervisor").value = 'admins'
768- >>> colin_browser.getControl("Change").click()
769- >>> print extract_text(colin_browser.contents)
770- Edit bug supervisor for Ubuntu : Ubuntu
771- ...
772- You cannot set Launchpad Administrators as the bug supervisor for
773- Ubuntu because you are not an administrator of that team.
774- If you believe that Launchpad Administrators should be the bug
775- supervisor for Ubuntu, please notify one of the Launchpad
776- Administrators administrators.
777- ...
778-
779-But members of the Launchpad administration team can appoint anybody
780-as bug supervisors, teams...
781-
782- >>> admin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
783- >>> admin_browser.getControl("Bug Supervisor").value = 'ubuntu-translators'
784- >>> admin_browser.getControl("Change").click()
785- >>> print extract_text(admin_browser.contents)
786- Ubuntu Linux in Launchpad
787- ...
788- Successfully changed the bug supervisor to Ubuntu Translators.
789- ...
790-
791-...as well as persons.
792-
793- >>> admin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
794- >>> admin_browser.getControl("Bug Supervisor").value = 'carlos'
795- >>> admin_browser.getControl("Change").click()
796- >>> print extract_text(admin_browser.contents)
797- Ubuntu Linux in Launchpad
798- ...
799- Successfully changed the bug supervisor to Carlos Perelló Marín.
800- ...
801+ >>> colin_browser.url
802+ 'http://launchpad.dev/ubuntu'
803+
804+ >>> for message in get_feedback_messages(colin_browser.contents):
805+ ... print extract_text(message)
806+ Successfully changed the bug supervisor to Colin Watson.
807+ Colin Watson has also been subscribed to bug notifications for Ubuntu.
808+ You can change the subscriptions for Ubuntu at any time.
809
810=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt'
811--- lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt 2010-03-31 20:25:33 +0000
812+++ lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt 2010-05-25 21:21:40 +0000
813@@ -38,39 +38,6 @@
814 Successfully changed the bug supervisor to Landscape Developers.
815 ...
816
817-If Sample Person tries to appoint another person, he'll get an error message.
818-
819- >>> sample_browser.open("http://localhost:9000/firefox/+bugsupervisor")
820- >>> sample_browser.getControl(name="field.bug_supervisor").value = (
821- ... "robertc@robertcollins.net")
822- >>> sample_browser.getControl("Change").click()
823- >>> print sample_browser.url
824- http://localhost:9000/firefox/+bugsupervisor
825- >>> print extract_text(sample_browser.contents)
826- Edit bug supervisor for Mozilla Firefox : Mozilla Firefox
827- ...
828- You cannot set another person as the bug supervisor for Mozilla Firefox.
829- See the help wiki for information about setting a bug supervisor.
830- The person or team responsible for bug management.
831- ...
832-
833-If he tries to appoint a team which he does not administer, he'll get
834-too an an error message.
835-
836- >>> sample_browser.getControl(name="field.bug_supervisor").value = (
837- ... "guadamen")
838- >>> sample_browser.getControl("Change").click()
839- >>> print sample_browser.url
840- http://localhost:9000/firefox/+bugsupervisor
841- >>> print extract_text(sample_browser.contents)
842- Edit bug supervisor for Mozilla Firefox : Mozilla Firefox
843- ...
844- You cannot set GuadaMen as the bug supervisor for Mozilla Firefox
845- because you are not an administrator of that team.
846- If you believe that GuadaMen should be the bug supervisor for Mozilla
847- Firefox, please notify one of the GuadaMen administrators.
848- ...
849-
850 Launchpad administrators can appoint anybody.
851
852 >>> admin_browser = setupBrowser()
853@@ -86,101 +53,3 @@
854 ...
855 Successfully changed the bug supervisor to Robert Collins.
856 ...
857-
858-
859-= Teams as Bug Supervisors =
860-
861-First, some setup. We create a new product owned by "Sample Person", using a
862-separate browser instance to avoid breaking the other page tests.
863-
864- >>> sample_browser.open('http://launchpad.dev/projects/+new')
865- >>> sample_browser.getControl('URL').value = 'testy'
866- >>> sample_browser.getControl('Name').value = 'Test Product'
867- >>> sample_browser.getControl('Title').value = (
868- ... 'A product for testing things')
869- >>> sample_browser.getControl('Summary').value = (
870- ... 'Testing team bug supervisors')
871- >>> sample_browser.getControl('Continue').click()
872-
873- # XXX Add Description: field
874- >>> #sample_browser.getControl('Description').value = 'See bug 109652'
875- >>> sample_browser.getControl(name='field.licenses').value = ['GNU_GPL_V2']
876- >>> sample_browser.getControl(name='field.license_info').value = 'foo'
877- >>> sample_browser.getControl('Complete Registration').click()
878- >>> sample_browser.url
879- 'http://launchpad.dev/testy'
880-
881-By default, a newly created project doesn't use Launchpad for bug
882-tracking, and none of the bug lists or edit controls will be
883-available.
884-
885- >>> sample_browser.getLink('Bugs').click()
886- >>> uses_malone_p = find_tag_by_id(sample_browser.contents, 'no-malone')
887- >>> print extract_text(uses_malone_p)
888- A product for testing things does not use Launchpad for bug tracking.
889-
890-We can change the settings to use Launchpad for bug tracking.
891-
892- >>> sample_browser.getLink(
893- ... 'Configure bug tracker').click()
894- >>> print sample_browser.title
895- Configure bug tracker : Bugs : Test Product
896- >>> sample_browser.getControl('In Launchpad').click()
897- >>> sample_browser.getControl('Change').click()
898-
899-Now, information about bug tracking (bug supervisor, security contact)
900-and their edit controls are available on the page.
901-
902- >>> bug_supervisor = find_tag_by_id(
903- ... sample_browser.contents, 'bug-supervisor')
904- >>> print extract_text(bug_supervisor)
905- Bug supervisor:
906- None set
907-
908-Sample Person is a member of "Warty Security Team". However, he should not be
909-able to set Warty Security Team as a bug supervisor for Test Project as he's
910-not an admin of that team.
911-
912- >>> sample_browser.getLink('Change bug supervisor').click()
913- >>> sample_browser.url
914- 'http://bugs.launchpad.dev/testy/+bugsupervisor'
915-
916- >>> sample_browser.getControl('Bug Supervisor').value = 'name20'
917- >>> sample_browser.getControl('Change').click()
918- >>> 'Successfully changed the bug supervisor' not in sample_browser.contents
919- True
920-
921- >>> print sample_browser.contents
922- <!DOCTYPE...
923- ...You cannot set Warty Security Team as the bug supervisor...
924- ...you are not an administrator of that team...
925-
926-In the warning, the user will be pointed to the Warty Security Team's
927-membership page and advised to contact an administrator if she believes that
928-the team should be a bug supervisor.
929-
930- >>> sample_browser.getLink('Warty Security Team administrators').click()
931- >>> sample_browser.url
932- 'http://launchpad.dev/~name20/+members'
933-
934-Launchpad admins can set a team as bug supervisor without any warnings,
935-however, whether they're in the team or not.
936-
937- >>> login('test@canonical.com')
938-
939- >>> from zope.component import getUtility
940- >>> from canonical.launchpad.interfaces import (
941- ... IPersonSet)
942- >>> security_team = getUtility(IPersonSet).getByName('name20')
943- >>> foo_bar = getUtility(IPersonSet).getByName('name16')
944- >>> foo_bar.inTeam(security_team)
945- False
946-
947- >>> logout()
948-
949- >>> admin_browser.open('http://bugs.launchpad.dev/testy/+bugsupervisor')
950- >>> admin_browser.getControl('Bug Supervisor').value = 'name20'
951- >>> admin_browser.getControl('Change').click()
952- >>> 'Successfully changed the bug supervisor' in admin_browser.contents
953- True
954-
955
956=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt'
957--- lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt 2009-11-12 12:01:22 +0000
958+++ lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt 2010-05-25 21:21:40 +0000
959@@ -19,9 +19,9 @@
960 >>> print browser.url.replace(bug_id, "BUG-ID")
961 http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/BUG-ID
962
963-We should have six subscribers now. The bug reporter (also a package
964-subscriber), mark, the distro bug supervisor, ubuntu-team, and carlos
965-and foobar, who are subscribed to the distribution.
966+We should have three subscribers now. The bug reporter (also a package
967+subscriber), mark, the distro bug supervisor kamion, and foobar, who is
968+subscribed to the distribution.
969
970 >>> from zope.component import getUtility
971 >>> from canonical.launchpad.interfaces import IBugSet
972@@ -36,8 +36,7 @@
973
974 >>> bugset = getUtility(IBugSet)
975 >>> subscriber_names(bugset.get(bug_id))
976- [u'carlos', u'kamion', u'mark', u'name16', u'ubuntu-team',
977- u'ubuntu-translators']
978+ [u'kamion', u'mark', u'name16']
979
980 >>> logout()
981