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
=== added file 'lib/lp/bugs/browser/bugrole.py'
--- lib/lp/bugs/browser/bugrole.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/bugrole.py 2010-05-25 21:21:40 +0000
@@ -0,0 +1,127 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version (see the file LICENSE).
3
4"""Common classes to support bug roles."""
5
6__metaclass__ = type
7
8__all__ = [
9 'BugRoleMixin',
10 ]
11
12from canonical.launchpad.webapp.menu import structured
13from canonical.launchpad.webapp.publisher import canonical_url
14
15
16class BugRoleMixin:
17
18 INVALID_PERSON = object()
19 OTHER_USER = object()
20 OTHER_TEAM = object()
21 OK = object()
22
23 def _getFieldState(self, field_name, data):
24 """Return the enum that summarises the field state."""
25 # The field_name will not be in the data if the user did not enter
26 # a person in the ValidPersonOrTeam vocabulary.
27 if field_name not in data:
28 return self.INVALID_PERSON
29 role = data[field_name]
30 user = self.user
31 # The user may assign the role to None, himself, or a team he admins.
32 if role is None or self.context.userCanAlterSubscription(role, user):
33 return self.OK
34 # The user is not an admin of the team, or he entered another user.
35 if role.isTeam():
36 return self.OTHER_TEAM
37 else:
38 return self.OTHER_USER
39
40 def validateBugSupervisor(self, data):
41 """Validates the new bug supervisor.
42
43 Verify that the value is None, the user, or a team he administers,
44 otherwise, set a field error.
45 """
46 field_state = self._getFieldState('bug_supervisor', data)
47 if field_state is self.INVALID_PERSON:
48 error = (
49 'You must choose a valid person or team to be the '
50 'bug supervisor for %s.' % self.context.displayname)
51 elif field_state is self.OTHER_TEAM:
52 supervisor = data['bug_supervisor']
53 team_url = canonical_url(
54 supervisor, rootsite='mainsite', view_name='+members')
55 error = structured(
56 'You cannot set %(team)s as the bug supervisor for '
57 '%(target)s because you are not an administrator of that '
58 'team.<br />If you believe that %(team)s should be the '
59 'bug supervisor for %(target)s, notify one of the '
60 '<a href="%(url)s">%(team)s administrators</a>. See '
61 '<a href="https://help.launchpad.net/BugSupervisors">'
62 'the help wiki</a> for information about setting a bug '
63 'supervisor.',
64 team=supervisor.displayname,
65 target=self.context.displayname,
66 url=team_url)
67 elif field_state is self.OTHER_USER:
68 error = structured(
69 'You cannot set another person as the bug supervisor for '
70 '%(target)s.<br />See '
71 '<a href="https://help.launchpad.net/BugSupervisors">'
72 'the help wiki</a> for information about setting a bug '
73 'supervisor.',
74 target=self.context.displayname)
75 else:
76 # field_state is self.OK.
77 return
78 self.setFieldError('bug_supervisor', error)
79
80 def changeBugSupervisor(self, bug_supervisor):
81 self.context.setBugSupervisor(bug_supervisor, self.user)
82 if bug_supervisor is not None:
83 self.request.response.addNotification(structured(
84 'Successfully changed the bug supervisor to '
85 '<a href="%(supervisor_url)s">%(displayname)s</a>.'
86 '<br /><a href="%(supervisor_url)s">%(displayname)s</a> '
87 'has also been subscribed to bug notifications for '
88 '%(targetname)s.<br />You can '
89 '<a href="%(targeturl)s/+subscribe">change the '
90 'subscriptions</a> for %(targetname)s at any time.',
91 supervisor_url=canonical_url(bug_supervisor),
92 displayname=bug_supervisor.displayname,
93 targetname=self.context.displayname,
94 targeturl=canonical_url(self.context)))
95
96 def validateSecurityContact(self, data):
97 """Validates the new security contact.
98
99 Verify that the value is None, the user, or a team he administers,
100 otherwise, set a field error.
101 """
102 field_state = self._getFieldState('security_contact', data)
103 if field_state is self.INVALID_PERSON:
104 error = (
105 'You must choose a valid person or team to be the '
106 'security contact for %s.' % self.context.displayname)
107 elif field_state is self.OTHER_TEAM:
108 supervisor = data['security_contact']
109 team_url = canonical_url(
110 supervisor, rootsite='mainsite', view_name='+members')
111 error = structured(
112 'You cannot set %(team)s as the security contact for '
113 '%(target)s because you are not an administrator of that '
114 'team.<br />If you believe that %(team)s should be the '
115 'security contact for %(target)s, notify one of the '
116 '<a href="%(url)s">%(team)s administrators</a>.',
117 team=supervisor.displayname,
118 target=self.context.displayname,
119 url=team_url)
120 elif field_state is self.OTHER_USER:
121 error = structured(
122 'You cannot set another person as the security contact for '
123 '%(target)s.', target=self.context.displayname)
124 else:
125 # field_state is self.OK.
126 return
127 self.setFieldError('security_contact', error)
0128
=== modified file 'lib/lp/bugs/browser/bugsupervisor.py'
--- lib/lp/bugs/browser/bugsupervisor.py 2010-04-29 15:32:46 +0000
+++ lib/lp/bugs/browser/bugsupervisor.py 2010-05-25 21:21:40 +0000
@@ -1,19 +1,23 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Browser view for bug supervisor."""4"""Browser view for bug supervisor."""
55
6__metaclass__ = type6__metaclass__ = type
77
8__all__ = ['BugSupervisorEditView']8__all__ = [
9 'BugSupervisorEditView',
10 ]
911
10from canonical.launchpad.webapp import (
11 action, canonical_url, LaunchpadEditFormView)
12from canonical.launchpad.webapp.menu import structured
13from lazr.restful.interface import copy_field, use_template
14from zope.interface import Interface12from zope.interface import Interface
1513
14from lazr.restful.interface import copy_field
15
16from canonical.launchpad.webapp.launchpadform import (
17 action, LaunchpadEditFormView)
18from canonical.launchpad.webapp.publisher import canonical_url
16from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor19from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
20from lp.bugs.browser.bugrole import BugRoleMixin
1721
1822
19class BugSupervisorEditSchema(Interface):23class BugSupervisorEditSchema(Interface):
@@ -26,7 +30,7 @@
26 IHasBugSupervisor['bug_supervisor'], readonly=False)30 IHasBugSupervisor['bug_supervisor'], readonly=False)
2731
2832
29class BugSupervisorEditView(LaunchpadEditFormView):33class BugSupervisorEditView(BugRoleMixin, LaunchpadEditFormView):
30 """Browser view class for editing the bug supervisor."""34 """Browser view class for editing the bug supervisor."""
3135
32 schema = BugSupervisorEditSchema36 schema = BugSupervisorEditSchema
@@ -47,99 +51,23 @@
47 """See `LaunchpadFormView`"""51 """See `LaunchpadFormView`"""
48 return {BugSupervisorEditSchema: self.context}52 return {BugSupervisorEditSchema: self.context}
4953
54 @property
55 def next_url(self):
56 """See `LaunchpadFormView`."""
57 return canonical_url(self.context)
58
59 cancel_url = next_url
60
61 def validate(self, data):
62 """See `LaunchpadFormView`."""
63 self.validateBugSupervisor(data)
64
50 @action('Change', name='change')65 @action('Change', name='change')
51 def change_action(self, action, data):66 def change_action(self, action, data):
52 """Redirect to the target page with a success message."""67 """Redirect to the target page with a success message."""
53 target = self.context
54 bug_supervisor = data['bug_supervisor']68 bug_supervisor = data['bug_supervisor']
55 target.setBugSupervisor(bug_supervisor, self.user)69 self.changeBugSupervisor(bug_supervisor)
5670 if bug_supervisor is None:
57 if bug_supervisor is not None:
58 self.request.response.addNotification(structured(
59 'Successfully changed the bug supervisor to '
60 '<a href="%(supervisor_url)s">%(displayname)s</a>.'
61 '<br />'
62 '<a href="%(supervisor_url)s">%(displayname)s</a> '
63 'has also been '
64 'subscribed to bug notifications for %(targetname)s. '
65 '<br />'
66 'You can '
67 '<a href="%(targeturl)s/+subscribe">'
68 'change the subscriptions</a> for '
69 '%(targetname)s at any time.',
70 supervisor_url=canonical_url(bug_supervisor),
71 displayname=bug_supervisor.displayname,
72 targetname=self.context.displayname,
73 targeturl=canonical_url(self.context)))
74 else:
75 self.request.response.addNotification(71 self.request.response.addNotification(
76 "Successfully cleared the bug supervisor. "72 "Successfully cleared the bug supervisor. "
77 "You can set the bug supervisor again at any time.")73 "You can set the bug supervisor again at any time.")
78
79 self.request.response.redirect(canonical_url(target))
80
81 def validate(self, data):
82 """Validates the new bug supervisor.
83
84 The following values are valid as bug supervisors:
85 * None, indicating that the bug supervisor field for the target
86 should be cleared in change_action().
87 * A valid Person (email address or launchpad id).
88 * A valid Team of which the current user is an administrator.
89
90 If the bug supervisor entered does not meet any of the above
91 criteria then the submission will fail and the user will be notified
92 of the error.
93 """
94
95 # `data` will not have a bug_supervisor entry in cases where the
96 # bug_supervisor the user entered is valid according to the
97 # ValidPersonOrTeam vocabulary
98 # (i.e. is not a Person, Team or None).
99 if not data.has_key('bug_supervisor'):
100 self.setFieldError(
101 'bug_supervisor',
102 'You must choose a valid person or team to be the'
103 ' bug supervisor for %s.' %
104 self.context.displayname)
105
106 return
107
108 supervisor = data['bug_supervisor']
109
110 # Making a person the bug supervisor implies subscribing him
111 # to all bug mail. Ensure that the current user can indeed
112 # do this.
113 if (supervisor is not None and
114 not self.context.userCanAlterSubscription(supervisor, self.user)):
115 if supervisor.isTeam():
116 error = structured(
117 "You cannot set %(team)s as the bug supervisor for "
118 "%(target)s because you are not an administrator of that "
119 "team.<br />If you believe that %(team)s should be the "
120 "bug supervisor for %(target)s, please notify one of the "
121 "<a href=\"%(url)s\">%(team)s administrators</a>. See "
122 "<a href=\"https://help.launchpad.net/BugSupervisors\">"
123 "the help wiki</a> for information about setting a bug "
124 "supervisor.",
125 team=supervisor.displayname,
126 target=self.context.displayname,
127 url=(canonical_url(supervisor, rootsite='mainsite') +
128 '/+members'))
129 self.setFieldError('bug_supervisor', error)
130 else:
131 error = structured(
132 "You cannot set another person as the bug supervisor for "
133 "%(target)s.<br />See "
134 "<a href=\"https://help.launchpad.net/BugSupervisors\">"
135 "the help wiki</a> for information about setting a bug "
136 "supervisor.",
137 person=supervisor.displayname,
138 target=self.context.displayname)
139 self.setFieldError('bug_supervisor', error)
140
141 def cancel_url(self):
142 """See `LaunchpadFormView`."""
143 return canonical_url(self.context)
144
145
14674
=== 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-25 21:21:40 +0000
@@ -1,17 +1,22 @@
1# Copyright 2009 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Browser view classes for security contacts."""4"""Browser view classes for security contacts."""
55
6__metaclass__ = type6__metaclass__ = type
7__all__ = ["SecurityContactEditView"]7
88__all__ = [
9 "SecurityContactEditView",
10 ]
11
12from canonical.launchpad.webapp.launchpadform import (
13 action, LaunchpadFormView)
14from canonical.launchpad.webapp.publisher import canonical_url
9from lp.bugs.interfaces.securitycontact import IHasSecurityContact15from lp.bugs.interfaces.securitycontact import IHasSecurityContact
10from canonical.launchpad.webapp import (16from lp.bugs.browser.bugrole import BugRoleMixin
11 canonical_url, LaunchpadFormView, action)17
1218
1319class SecurityContactEditView(BugRoleMixin, LaunchpadFormView):
14class SecurityContactEditView(LaunchpadFormView):
15 """Browser view for editing the security contact.20 """Browser view for editing the security contact.
1621
17 self.context is assumed to implement IHasSecurityContact.22 self.context is assumed to implement IHasSecurityContact.
@@ -35,6 +40,10 @@
35 return {40 return {
36 'security_contact': self.context.security_contact}41 'security_contact': self.context.security_contact}
3742
43 def validate(self, data):
44 """See `LaunchpadFormView`."""
45 self.validateSecurityContact(data)
46
38 @action('Change', name='change')47 @action('Change', name='change')
39 def change_action(self, action, data):48 def change_action(self, action, data):
40 security_contact = data['security_contact']49 security_contact = data['security_contact']
@@ -43,25 +52,12 @@
4352
44 self.context.security_contact = security_contact53 self.context.security_contact = security_contact
45 if security_contact:54 if security_contact:
46 security_contact_display_value = None
47 if security_contact.preferredemail:
48 # The security contact was set to a new person or team.
49 security_contact_display_value = (
50 security_contact.preferredemail.email)
51 else:
52 # The security contact doesn't have a preferred email address,
53 # so it must be a team.
54 assert security_contact.isTeam(), (
55 "Expected security contact with no email address "
56 "to be a team.")
57 security_contact_display_value = security_contact.displayname
58
59 self.request.response.addNotification(55 self.request.response.addNotification(
60 "Successfully changed the security contact to %s" %56 "Successfully changed the security contact to %s." %
61 security_contact_display_value)57 security_contact.displayname)
62 else:58 else:
63 self.request.response.addNotification(59 self.request.response.addNotification(
64 "Successfully removed the security contact")60 "Successfully removed the security contact.")
6561
66 @property62 @property
67 def next_url(self):63 def next_url(self):
6864
=== 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-25 21:21:40 +0000
@@ -0,0 +1,167 @@
1# Copyright10 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version (see the file LICENSE).
3
4"""Unit tests for bug supervisor views."""
5
6__metaclass__ = type
7
8import unittest
9
10from zope.app.form.interfaces import ConversionError
11
12from lp.bugs.browser.bugsupervisor import BugSupervisorEditSchema
13from lp.testing import login, login_person, TestCaseWithFactory
14from lp.testing.views import create_initialized_view
15from canonical.testing import DatabaseFunctionalLayer
16
17
18class TestBugSupervisorEditView(TestCaseWithFactory):
19
20 layer = DatabaseFunctionalLayer
21
22 def setUp(self):
23 super(TestBugSupervisorEditView, self).setUp()
24 self.owner = self.factory.makePerson(
25 name='splat', displayname='<splat />')
26 self.product = self.factory.makeProduct(
27 name="boing", displayname='<boing />', owner=self.owner)
28 self.team = self.factory.makeTeam(name='thud', owner=self.owner)
29 login_person(self.owner)
30
31 def _makeForm(self, person):
32 if person is None:
33 name = ''
34 else:
35 name = person.name
36 return {
37 'field.bug_supervisor': name,
38 'field.actions.change': 'Change',
39 }
40
41 def test_view_attributes(self):
42 self.product.displayname = 'Boing'
43 view = create_initialized_view(
44 self.product, name='+bugsupervisor')
45 label = 'Edit bug supervisor for Boing'
46 self.assertEqual(label, view.label)
47 self.assertEqual(label, view.page_title)
48 fields = ['bug_supervisor']
49 self.assertEqual(fields, view.field_names)
50 adapter, context = view.adapters.popitem()
51 self.assertEqual(BugSupervisorEditSchema, adapter)
52 self.assertEqual(self.product, context)
53 self.assertEqual('http://launchpad.dev/boing', view.next_url)
54 self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
55
56 def test_owner_appoint_self_from_none(self):
57 # This also verifies that displaynames are escaped.
58 form = self._makeForm(self.owner)
59 view = create_initialized_view(
60 self.product, name='+bugsupervisor', form=form)
61 self.assertEqual([], view.errors)
62 self.assertEqual(self.product.bug_supervisor, self.owner)
63 notifications = view.request.response.notifications
64 self.assertEqual(1, len(notifications))
65 expected = (
66 'Successfully changed the bug supervisor to '
67 '<a href="http://launchpad.dev/~splat">&lt;splat /&gt;</a>.'
68 '<br /><a href="http://launchpad.dev/~splat">&lt;splat /&gt;</a> '
69 'has also been subscribed to bug notifications for '
70 '&lt;boing /&gt;.<br />You can '
71 '<a href="http://launchpad.dev/boing/+subscribe">change '
72 'the subscriptions</a> for &lt;boing /&gt; at any time.')
73 self.assertEqual(expected, notifications.pop().message)
74
75 def test_owner_appoint_self_from_another(self):
76 self.product.setBugSupervisor(self.team, self.owner)
77 form = self._makeForm(self.owner)
78 view = create_initialized_view(
79 self.product, name='+bugsupervisor', form=form)
80 self.assertEqual([], view.errors)
81 self.assertEqual(self.owner, self.product.bug_supervisor)
82
83 def test_owner_appoint_none(self):
84 self.product.setBugSupervisor(self.owner, self.owner)
85 form = self._makeForm(None)
86 view = create_initialized_view(
87 self.product, name='+bugsupervisor', form=form)
88 self.assertEqual([], view.errors)
89 self.assertEqual(self.product.bug_supervisor, None)
90 notifications = view.request.response.notifications
91 self.assertEqual(1, len(notifications))
92 expected = (
93 'Successfully cleared the bug supervisor. '
94 'You can set the bug supervisor again at any time.')
95 self.assertEqual(expected, notifications.pop().message)
96
97 def test_owner_appoint_his_team(self):
98 form = self._makeForm(self.team)
99 view = create_initialized_view(
100 self.product, name='+bugsupervisor', form=form)
101 self.assertEqual([], view.errors)
102 self.assertEqual(self.team, self.product.bug_supervisor)
103
104 def test_owner_cannot_appoint_another_team(self):
105 team = self.factory.makeTeam(name='smack', displayname='<smack />')
106 form = self._makeForm(team)
107 view = create_initialized_view(
108 self.product, name='+bugsupervisor', form=form)
109 self.assertEqual(1, len(view.errors))
110 expected = (
111 'You cannot set &lt;smack /&gt; as the bug supervisor for '
112 '&lt;boing /&gt; because you are not an administrator of that '
113 'team.<br />If you believe that &lt;smack /&gt; should be the '
114 'bug supervisor for &lt;boing /&gt;, notify one of the '
115 '<a href="http://launchpad.dev/~smack/+members">&lt;smack /&gt; '
116 'administrators</a>. See '
117 '<a href="https://help.launchpad.net/BugSupervisors">the '
118 'help wiki</a> for information about setting a bug supervisor.')
119 self.assertEqual(expected, view.errors.pop())
120
121 def test_owner_cannot_appoint_a_nonvalid_user(self):
122 # The vocabulary only accepts valid users.
123 form = self._makeForm(None)
124 form['field.bug_supervisor'] = 'fnord'
125 view = create_initialized_view(
126 self.product, name='+bugsupervisor', form=form)
127 self.assertEqual(2, len(view.errors))
128 expected = (
129 'You must choose a valid person or team to be the bug supervisor '
130 'for &lt;boing /&gt;.')
131 self.assertEqual(expected, view.errors.pop())
132 self.assertTrue(isinstance(view.errors.pop(), ConversionError))
133
134 def test_owner_cannot_appoint_another_user(self):
135 another_user = self.factory.makePerson()
136 form = self._makeForm(another_user)
137 view = create_initialized_view(
138 self.product, name='+bugsupervisor', form=form)
139 self.assertEqual(1, len(view.errors))
140 expected = (
141 'You cannot set another person as the bug supervisor for '
142 '&lt;boing /&gt;.<br />See '
143 '<a href="https://help.launchpad.net/BugSupervisors">the help '
144 'wiki</a> for information about setting a bug supervisor.')
145 self.assertEqual(expected, view.errors.pop())
146
147 def test_admin_appoint_another_user(self):
148 another_user = self.factory.makePerson()
149 login('admin@canonical.com')
150 form = self._makeForm(another_user)
151 view = create_initialized_view(
152 self.product, name='+bugsupervisor', form=form)
153 self.assertEqual([], view.errors)
154 self.assertEqual(another_user, self.product.bug_supervisor)
155
156 def test_admin_appoint_another_team(self):
157 another_team = self.factory.makeTeam()
158 login('admin@canonical.com')
159 form = self._makeForm(another_team)
160 view = create_initialized_view(
161 self.product, name='+bugsupervisor', form=form)
162 self.assertEqual([], view.errors)
163 self.assertEqual(another_team, self.product.bug_supervisor)
164
165
166def test_suite():
167 return unittest.TestLoader().loadTestsFromName(__name__)
0168
=== added file 'lib/lp/bugs/browser/tests/test_securitycontact.py'
--- lib/lp/bugs/browser/tests/test_securitycontact.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/browser/tests/test_securitycontact.py 2010-05-25 21:21:40 +0000
@@ -0,0 +1,154 @@
1# Copyright10 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version (see the file LICENSE).
3
4"""Unit tests for security contact views."""
5
6__metaclass__ = type
7
8import unittest
9
10from zope.app.form.interfaces import ConversionError
11
12from lp.testing import login, login_person, TestCaseWithFactory
13from lp.testing.views import create_initialized_view
14from canonical.testing import DatabaseFunctionalLayer
15
16
17class TestSecurityContactEditView(TestCaseWithFactory):
18
19 layer = DatabaseFunctionalLayer
20
21 def setUp(self):
22 super(TestSecurityContactEditView, self).setUp()
23 self.owner = self.factory.makePerson(
24 name='splat', displayname='<splat />')
25 self.product = self.factory.makeProduct(
26 name="boing", displayname='<boing />', owner=self.owner)
27 self.team = self.factory.makeTeam(name='thud', owner=self.owner)
28 login_person(self.owner)
29
30 def _makeForm(self, person):
31 if person is None:
32 name = ''
33 else:
34 name = person.name
35 return {
36 'field.security_contact': name,
37 'field.actions.change': 'Change',
38 }
39
40 def test_view_attributes(self):
41 self.product.displayname = 'Boing'
42 view = create_initialized_view(
43 self.product, name='+securitycontact')
44 label = 'Edit Boing security contact'
45 self.assertEqual(label, view.label)
46 self.assertEqual(label, view.page_title)
47 fields = ['security_contact']
48 self.assertEqual(fields, view.field_names)
49 self.assertEqual('http://launchpad.dev/boing', view.next_url)
50 self.assertEqual('http://launchpad.dev/boing', view.cancel_url)
51
52 def test_owner_appoint_self_from_none(self):
53 # This also verifies that displaynames are escaped.
54 form = self._makeForm(self.owner)
55 view = create_initialized_view(
56 self.product, name='+securitycontact', form=form)
57 self.assertEqual([], view.errors)
58 self.assertEqual(self.product.security_contact, self.owner)
59 notifications = view.request.response.notifications
60 self.assertEqual(1, len(notifications))
61 expected = (
62 'Successfully changed the security contact to &lt;splat /&gt;.')
63 self.assertEqual(expected, notifications.pop().message)
64
65 def test_owner_appoint_self_from_another(self):
66 self.product.security_contact = self.team
67 form = self._makeForm(self.owner)
68 view = create_initialized_view(
69 self.product, name='+securitycontact', form=form)
70 self.assertEqual([], view.errors)
71 self.assertEqual(self.owner, self.product.security_contact)
72
73 def test_owner_appoint_none(self):
74 self.product.security_contact = self.owner
75 form = self._makeForm(None)
76 view = create_initialized_view(
77 self.product, name='+securitycontact', form=form)
78 self.assertEqual([], view.errors)
79 self.assertEqual(self.product.security_contact, None)
80 notifications = view.request.response.notifications
81 self.assertEqual(1, len(notifications))
82 expected = ('Successfully removed the security contact.')
83 self.assertEqual(expected, notifications.pop().message)
84
85 def test_owner_appoint_his_team(self):
86 form = self._makeForm(self.team)
87 view = create_initialized_view(
88 self.product, name='+securitycontact', form=form)
89 self.assertEqual([], view.errors)
90 self.assertEqual(self.product.security_contact, self.team)
91 notifications = view.request.response.notifications
92 self.assertEqual(1, len(notifications))
93 expected = ('Successfully changed the security contact to Thud.')
94 self.assertEqual(expected, notifications.pop().message)
95
96 def test_owner_cannot_appoint_another_team(self):
97 team = self.factory.makeTeam(name='smack', displayname='<smack />')
98 form = self._makeForm(team)
99 view = create_initialized_view(
100 self.product, name='+securitycontact', form=form)
101 self.assertEqual(1, len(view.errors))
102 expected = (
103 'You cannot set &lt;smack /&gt; as the security contact for '
104 '&lt;boing /&gt; because you are not an administrator of that '
105 'team.<br />If you believe that &lt;smack /&gt; should be the '
106 'security contact for &lt;boing /&gt;, notify one of the '
107 '<a href="http://launchpad.dev/~smack/+members">&lt;smack /&gt; '
108 'administrators</a>.')
109 self.assertEqual(expected, view.errors.pop())
110
111 def test_owner_cannot_appoint_a_nonvalid_user(self):
112 form = self._makeForm(None)
113 form['field.security_contact'] = 'fnord'
114 view = create_initialized_view(
115 self.product, name='+securitycontact', form=form)
116 self.assertEqual(2, len(view.errors))
117 expected = (
118 'You must choose a valid person or team to be the '
119 'security contact for &lt;boing /&gt;.')
120 self.assertEqual(expected, view.errors.pop())
121 self.assertTrue(isinstance(view.errors.pop(), ConversionError))
122
123 def test_owner_cannot_appoint_another_user(self):
124 another_user = self.factory.makePerson()
125 form = self._makeForm(another_user)
126 view = create_initialized_view(
127 self.product, name='+securitycontact', form=form)
128 self.assertEqual(1, len(view.errors))
129 expected = (
130 'You cannot set another person as the security contact for '
131 '&lt;boing /&gt;.')
132 self.assertEqual(expected, view.errors.pop())
133
134 def test_admin_appoint_another_user(self):
135 another_user = self.factory.makePerson()
136 login('admin@canonical.com')
137 form = self._makeForm(another_user)
138 view = create_initialized_view(
139 self.product, name='+securitycontact', form=form)
140 self.assertEqual([], view.errors)
141 self.assertEqual(another_user, self.product.security_contact)
142
143 def test_admin_appoint_another_team(self):
144 another_team = self.factory.makeTeam()
145 login('admin@canonical.com')
146 form = self._makeForm(another_team)
147 view = create_initialized_view(
148 self.product, name='+securitycontact', form=form)
149 self.assertEqual([], view.errors)
150 self.assertEqual(another_team, self.product.security_contact)
151
152
153def test_suite():
154 return unittest.TestLoader().loadTestsFromName(__name__)
0155
=== modified file 'lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt'
--- lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/stories/bugs/xx-malone-security-contacts.txt 2010-05-25 21:21:40 +0000
@@ -6,17 +6,15 @@
6 >>> browser.open("http://localhost:9000/ubuntu/+securitycontact")6 >>> browser.open("http://localhost:9000/ubuntu/+securitycontact")
7 >>> browser.getControl("Security Contact").value = "name16"7 >>> browser.getControl("Security Contact").value = "name16"
8 >>> browser.getControl("Change").click()8 >>> browser.getControl("Change").click()
9 >>> print browser.contents9 >>> for message in get_feedback_messages(browser.contents):
10 <!DOCTYPE...10 ... print extract_text(message)
11 ...Successfully changed the security contact to foo.bar@canonical.com...11 Successfully changed the security contact to Foo Bar.
12 ...
1312
14Or on an upstream.13Or on an upstream.
1514
16 >>> browser.open("http://localhost:9000/firefox/+securitycontact")15 >>> browser.open("http://localhost:9000/firefox/+securitycontact")
17 >>> browser.getControl("Security Contact").value = "name21"16 >>> browser.getControl("Security Contact").value = "name21"
18 >>> browser.getControl("Change").click()17 >>> browser.getControl("Change").click()
19 >>> print browser.contents18 >>> for message in get_feedback_messages(browser.contents):
20 <!DOCTYPE...19 ... print extract_text(message)
21 ...Successfully changed the security contact to Hoary Gnome Team...20 Successfully changed the security contact to Hoary Gnome Team.
22 ...
2321
=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt'
--- lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt 2009-11-12 15:33:27 +0000
+++ lib/lp/bugs/stories/initial-bug-contacts/05-set-distribution-bugcontact.txt 2010-05-25 21:21:40 +0000
@@ -42,74 +42,11 @@
4242
43And then we're redirected to the distribution page.43And then we're redirected to the distribution page.
4444
45 >>> colin_browser.url45 >>> colin_browser.url
46 'http://launchpad.dev/ubuntu'46 'http://launchpad.dev/ubuntu'
4747
48 >>> for tag in find_tags_by_class(colin_browser.contents,48 >>> for message in get_feedback_messages(colin_browser.contents):
49 ... "informational message"):49 ... print extract_text(message)
50 ... print tag50 Successfully changed the bug supervisor to Colin Watson.
51 <div...Successfully changed the bug supervisor to ...Colin Watson...51 Colin Watson has also been subscribed to bug notifications for Ubuntu.
5252 You can change the subscriptions for Ubuntu at any time.
53Now that the bug supervisor is set, there's a link to him on the
54distribution's bug page page.
55
56 >>> browser.open('http://bugs.launchpad.dev/ubuntu')
57 >>> browser.getLink(url='/~kamion').url
58 'http://launchpad.dev/~kamion'
59
60When the bug supervisor doesn't have an email address, the displayname is
61shown instead in the feedback message. Let's appoint the Ubuntu Team as
62the bug supervisor. Since Colin is an administrator of this team, he can
63do it.
64
65 >>> colin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
66 >>> colin_browser.getControl("Bug Supervisor").value = 'ubuntu-team'
67 >>> colin_browser.getControl("Change").click()
68
69 >>> print extract_text(colin_browser.contents)
70 Ubuntu Linux in Launchpad
71 ...
72 Successfully changed the bug supervisor to Ubuntu Team.
73 ...
74
75While anybody with edit permissions can set the bug supervisor, only
76certain persons can be successfully selected. All users can set
77themselves self and teams they administer as the bug supervisor, as
78shown above. But ordinary users cannot appoint other persons and teams
79they do not administer.
80
81 >>> colin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
82 >>> colin_browser.getControl("Bug Supervisor").value = 'admins'
83 >>> colin_browser.getControl("Change").click()
84 >>> print extract_text(colin_browser.contents)
85 Edit bug supervisor for Ubuntu : Ubuntu
86 ...
87 You cannot set Launchpad Administrators as the bug supervisor for
88 Ubuntu because you are not an administrator of that team.
89 If you believe that Launchpad Administrators should be the bug
90 supervisor for Ubuntu, please notify one of the Launchpad
91 Administrators administrators.
92 ...
93
94But members of the Launchpad administration team can appoint anybody
95as bug supervisors, teams...
96
97 >>> admin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
98 >>> admin_browser.getControl("Bug Supervisor").value = 'ubuntu-translators'
99 >>> admin_browser.getControl("Change").click()
100 >>> print extract_text(admin_browser.contents)
101 Ubuntu Linux in Launchpad
102 ...
103 Successfully changed the bug supervisor to Ubuntu Translators.
104 ...
105
106...as well as persons.
107
108 >>> admin_browser.open("http://launchpad.dev/ubuntu/+bugsupervisor")
109 >>> admin_browser.getControl("Bug Supervisor").value = 'carlos'
110 >>> admin_browser.getControl("Change").click()
111 >>> print extract_text(admin_browser.contents)
112 Ubuntu Linux in Launchpad
113 ...
114 Successfully changed the bug supervisor to Carlos Perelló Marín.
115 ...
11653
=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt'
--- lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt 2010-03-31 20:25:33 +0000
+++ lib/lp/bugs/stories/initial-bug-contacts/10-set-upstream-bugcontact.txt 2010-05-25 21:21:40 +0000
@@ -38,39 +38,6 @@
38 Successfully changed the bug supervisor to Landscape Developers.38 Successfully changed the bug supervisor to Landscape Developers.
39 ...39 ...
4040
41If Sample Person tries to appoint another person, he'll get an error message.
42
43 >>> sample_browser.open("http://localhost:9000/firefox/+bugsupervisor")
44 >>> sample_browser.getControl(name="field.bug_supervisor").value = (
45 ... "robertc@robertcollins.net")
46 >>> sample_browser.getControl("Change").click()
47 >>> print sample_browser.url
48 http://localhost:9000/firefox/+bugsupervisor
49 >>> print extract_text(sample_browser.contents)
50 Edit bug supervisor for Mozilla Firefox : Mozilla Firefox
51 ...
52 You cannot set another person as the bug supervisor for Mozilla Firefox.
53 See the help wiki for information about setting a bug supervisor.
54 The person or team responsible for bug management.
55 ...
56
57If he tries to appoint a team which he does not administer, he'll get
58too an an error message.
59
60 >>> sample_browser.getControl(name="field.bug_supervisor").value = (
61 ... "guadamen")
62 >>> sample_browser.getControl("Change").click()
63 >>> print sample_browser.url
64 http://localhost:9000/firefox/+bugsupervisor
65 >>> print extract_text(sample_browser.contents)
66 Edit bug supervisor for Mozilla Firefox : Mozilla Firefox
67 ...
68 You cannot set GuadaMen as the bug supervisor for Mozilla Firefox
69 because you are not an administrator of that team.
70 If you believe that GuadaMen should be the bug supervisor for Mozilla
71 Firefox, please notify one of the GuadaMen administrators.
72 ...
73
74Launchpad administrators can appoint anybody.41Launchpad administrators can appoint anybody.
7542
76 >>> admin_browser = setupBrowser()43 >>> admin_browser = setupBrowser()
@@ -86,101 +53,3 @@
86 ...53 ...
87 Successfully changed the bug supervisor to Robert Collins.54 Successfully changed the bug supervisor to Robert Collins.
88 ...55 ...
89
90
91= Teams as Bug Supervisors =
92
93First, some setup. We create a new product owned by "Sample Person", using a
94separate browser instance to avoid breaking the other page tests.
95
96 >>> sample_browser.open('http://launchpad.dev/projects/+new')
97 >>> sample_browser.getControl('URL').value = 'testy'
98 >>> sample_browser.getControl('Name').value = 'Test Product'
99 >>> sample_browser.getControl('Title').value = (
100 ... 'A product for testing things')
101 >>> sample_browser.getControl('Summary').value = (
102 ... 'Testing team bug supervisors')
103 >>> sample_browser.getControl('Continue').click()
104
105 # XXX Add Description: field
106 >>> #sample_browser.getControl('Description').value = 'See bug 109652'
107 >>> sample_browser.getControl(name='field.licenses').value = ['GNU_GPL_V2']
108 >>> sample_browser.getControl(name='field.license_info').value = 'foo'
109 >>> sample_browser.getControl('Complete Registration').click()
110 >>> sample_browser.url
111 'http://launchpad.dev/testy'
112
113By default, a newly created project doesn't use Launchpad for bug
114tracking, and none of the bug lists or edit controls will be
115available.
116
117 >>> sample_browser.getLink('Bugs').click()
118 >>> uses_malone_p = find_tag_by_id(sample_browser.contents, 'no-malone')
119 >>> print extract_text(uses_malone_p)
120 A product for testing things does not use Launchpad for bug tracking.
121
122We can change the settings to use Launchpad for bug tracking.
123
124 >>> sample_browser.getLink(
125 ... 'Configure bug tracker').click()
126 >>> print sample_browser.title
127 Configure bug tracker : Bugs : Test Product
128 >>> sample_browser.getControl('In Launchpad').click()
129 >>> sample_browser.getControl('Change').click()
130
131Now, information about bug tracking (bug supervisor, security contact)
132and their edit controls are available on the page.
133
134 >>> bug_supervisor = find_tag_by_id(
135 ... sample_browser.contents, 'bug-supervisor')
136 >>> print extract_text(bug_supervisor)
137 Bug supervisor:
138 None set
139
140Sample Person is a member of "Warty Security Team". However, he should not be
141able to set Warty Security Team as a bug supervisor for Test Project as he's
142not an admin of that team.
143
144 >>> sample_browser.getLink('Change bug supervisor').click()
145 >>> sample_browser.url
146 'http://bugs.launchpad.dev/testy/+bugsupervisor'
147
148 >>> sample_browser.getControl('Bug Supervisor').value = 'name20'
149 >>> sample_browser.getControl('Change').click()
150 >>> 'Successfully changed the bug supervisor' not in sample_browser.contents
151 True
152
153 >>> print sample_browser.contents
154 <!DOCTYPE...
155 ...You cannot set Warty Security Team as the bug supervisor...
156 ...you are not an administrator of that team...
157
158In the warning, the user will be pointed to the Warty Security Team's
159membership page and advised to contact an administrator if she believes that
160the team should be a bug supervisor.
161
162 >>> sample_browser.getLink('Warty Security Team administrators').click()
163 >>> sample_browser.url
164 'http://launchpad.dev/~name20/+members'
165
166Launchpad admins can set a team as bug supervisor without any warnings,
167however, whether they're in the team or not.
168
169 >>> login('test@canonical.com')
170
171 >>> from zope.component import getUtility
172 >>> from canonical.launchpad.interfaces import (
173 ... IPersonSet)
174 >>> security_team = getUtility(IPersonSet).getByName('name20')
175 >>> foo_bar = getUtility(IPersonSet).getByName('name16')
176 >>> foo_bar.inTeam(security_team)
177 False
178
179 >>> logout()
180
181 >>> admin_browser.open('http://bugs.launchpad.dev/testy/+bugsupervisor')
182 >>> admin_browser.getControl('Bug Supervisor').value = 'name20'
183 >>> admin_browser.getControl('Change').click()
184 >>> 'Successfully changed the bug supervisor' in admin_browser.contents
185 True
186
18756
=== modified file 'lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt'
--- lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt 2009-11-12 12:01:22 +0000
+++ lib/lp/bugs/stories/initial-bug-contacts/25-file-distribution-bug.txt 2010-05-25 21:21:40 +0000
@@ -19,9 +19,9 @@
19 >>> print browser.url.replace(bug_id, "BUG-ID")19 >>> print browser.url.replace(bug_id, "BUG-ID")
20 http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/BUG-ID20 http://bugs.launchpad.dev/ubuntu/+source/mozilla-firefox/+bug/BUG-ID
2121
22We should have six subscribers now. The bug reporter (also a package22We should have three subscribers now. The bug reporter (also a package
23subscriber), mark, the distro bug supervisor, ubuntu-team, and carlos23subscriber), mark, the distro bug supervisor kamion, and foobar, who is
24and foobar, who are subscribed to the distribution.24subscribed to the distribution.
2525
26 >>> from zope.component import getUtility26 >>> from zope.component import getUtility
27 >>> from canonical.launchpad.interfaces import IBugSet27 >>> from canonical.launchpad.interfaces import IBugSet
@@ -36,8 +36,7 @@
3636
37 >>> bugset = getUtility(IBugSet)37 >>> bugset = getUtility(IBugSet)
38 >>> subscriber_names(bugset.get(bug_id))38 >>> subscriber_names(bugset.get(bug_id))
39 [u'carlos', u'kamion', u'mark', u'name16', u'ubuntu-team',39 [u'kamion', u'mark', u'name16']
40 u'ubuntu-translators']
4140
42 >>> logout()41 >>> logout()
4342