launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04988
[Merge] lp:~sinzui/launchpad/private-bug-5 into lp:launchpad
Curtis Hovey has proposed merging lp:~sinzui/launchpad/private-bug-5 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~sinzui/launchpad/private-bug-5/+merge/75614
Bugs created via email for private-bugs-default projects are not private.
Launchpad bug: https://bugs.launchpad.net/bugs/797697
Pre-implementation: lifeless, jcsackett
MaloneHandler.process() processes email commands as discreet operations:
BugEmailCommand and AffectsEmailCommand are separate operations which do not
support default-private-bugs. The discreet approach assumed the order of
changes is not important.
BugSet.createBug() takes a holistic approach to where all the parameters are
examined and reconciled before the bug is created using a specific order
of calls.
--------------------------------------------------------------------
RULES
* When a new bug is being created, MaloneHandler.process() should be
setting CreateBugParams. When all the information is gathered it will
call BugSet.createBug()
* Launchpad web and API code require the target to create the bug,
the Affects command will create a bug from the BugParams.
* Some message processing rules in BugEmailCommand need to move to
AffectsEmailCommand.
* Most bug email commands expect an IBug:
* Update the methods to check for bug or bug_params
* When there is a bug, continue as normal.
* When there are bug_params, update them and return early.
QA
* Send an email to a project with private bugs by default that you
are the bug supervisor for:
affects my-project
tags testing
status triaged
importance high
* Verify the bug was created and it that is private.
* Send an email to a project with private bugs by default that you
are the security contact for:
affects my-project
security true
* Verify the bug was created and that it is security related and private.
* Send an email to a project with private bugs by default that you
are the security contact for:
affects my-project
private false
* Verify the bug was created and that it is public.
LINT
lib/lp/bugs/interfaces/bug.py
lib/lp/bugs/mail/commands.py
lib/lp/bugs/mail/handler.py
lib/lp/bugs/mail/tests/test_commands.py
lib/lp/bugs/model/bug.py
lib/lp/bugs/tests/test_bug.py
lib/lp/registry/adapters.py
lib/lp/registry/configure.zcml
lib/lp/registry/tests/test_adapters.py
TEST
./bin/test -vv lp.bugs.mail.tests.test_commands \
./bin/test -vv lp.bugs.mail.tests.test_handler \
./bin/test -vv -t bug-emailinterface lp.bugs.mail.tests
IMPLEMENTATION
Updated the most all the bug commands and the first bugtask command to
work with bug_params. The Duplicate and Unsubscribe commands do nothing
since these commands are really for existing bugs. I updated malone handler
loop to reconcile the defects reported by the bug-emailinterface.txt test.
lib/lp/bugs/mail/commands.py
lib/lp/bugs/mail/handler.py
lib/lp/bugs/mail/tests/test_commands.py
Updated the signature of CreateBug() to return the event if the callsite needs
to manage it...because it can be aborted. Added support for CVEs.
lib/lp/bugs/interfaces/bug.py
lib/lp/bugs/model/bug.py
lib/lp/bugs/tests/test_bug.py
Added or registered adapters to get product and dsp.
lib/lp/registry/adapters.py
lib/lp/registry/configure.zcml
lib/lp/registry/tests/test_adapters.py
--
https://code.launchpad.net/~sinzui/launchpad/private-bug-5/+merge/75614
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/private-bug-5 into lp:launchpad.
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2011-09-11 12:50:33 +0000
+++ lib/lp/bugs/interfaces/bug.py 2011-09-15 19:33:27 +0000
@@ -101,7 +101,7 @@
status=None, datecreated=None, security_related=False,
private=False, subscribers=(),
tags=None, subscribe_owner=True, filed_by=None,
- importance=None, milestone=None, assignee=None):
+ importance=None, milestone=None, assignee=None, cve=None):
self.owner = owner
self.title = title
self.comment = comment
@@ -121,6 +121,7 @@
self.importance = importance
self.milestone = milestone
self.assignee = assignee
+ self.cve = cve
def setBugTarget(self, product=None, distribution=None,
sourcepackagename=None):
@@ -1143,10 +1144,13 @@
the given bug tracker and remote bug id.
"""
- def createBug(bug_params):
+ def createBug(bug_params, notify_event=True):
"""Create a bug and return it.
- :bug_params: A CreateBugParams object.
+ :param bug_params: A CreateBugParams object.
+ :param notify_event: notify subscribers of the bug creation event.
+ :return: the new bug, or a tuple of bug, event when notify_event
+ is false.
Things to note when using this factory:
=== modified file 'lib/lp/bugs/mail/commands.py'
--- lib/lp/bugs/mail/commands.py 2011-09-11 12:50:33 +0000
+++ lib/lp/bugs/mail/commands.py 2011-09-15 19:33:27 +0000
@@ -57,6 +57,7 @@
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.registry.interfaces.sourcepackage import ISourcePackage
+from lp.registry.interfaces.sourcepackagename import ISourcePackageName
from lp.services.mail.commands import (
EditEmailCommand,
EmailCommand,
@@ -127,7 +128,7 @@
params = CreateBugParams(
msg=message, title=message.title,
owner=getUtility(ILaunchBag).user)
- return getUtility(IBugSet).createBugWithoutTarget(params)
+ return params, None
else:
try:
bugid = int(bugid)
@@ -181,6 +182,13 @@
error_templates=error_templates),
stop_processing=True)
+ if isinstance(context, CreateBugParams):
+ if context.security_related:
+ # BugSet.createBug() requires new security bugs to be private.
+ private = True
+ context.private = private
+ return context, current_event
+
# Snapshot.
edited_fields = set()
if IObjectModifiedEvent.providedBy(current_event):
@@ -230,6 +238,13 @@
error_templates=error_templates),
stop_processing=True)
+ if isinstance(context, CreateBugParams):
+ context.security_related = security_related
+ if security_related:
+ # BugSet.createBug() requires new security bugs to be private.
+ context.private = True
+ return context, current_event
+
# Take a snapshot.
edited = False
edited_fields = set()
@@ -287,6 +302,13 @@
'subscribe-too-many-arguments.txt',
error_templates=error_templates))
+ if isinstance(bug, CreateBugParams):
+ if len(bug.subscribers) == 0:
+ bug.subscribers = [person]
+ else:
+ bug.subscribers.append(person)
+ return bug, current_event
+
if bug.isSubscribed(person):
# but we still need to find the subscription
for bugsubscription in bug.subscriptions:
@@ -308,6 +330,11 @@
def execute(self, bug, current_event):
"""See IEmailCommand."""
+ if isinstance(bug, CreateBugParams):
+ # Return the input because there is not yet a bug to
+ # unsubscribe too.
+ return bug, current_event
+
string_args = list(self.string_args)
if len(string_args) == 1:
person = get_person_or_team(string_args.pop())
@@ -359,6 +386,10 @@
'summary-too-many-arguments.txt',
error_templates=error_templates))
+ if isinstance(bug, CreateBugParams):
+ bug.title = self.string_args[0]
+ return bug, current_event
+
return EditEmailCommand.execute(self, bug, current_event)
def convertArguments(self, context):
@@ -375,6 +406,10 @@
def execute(self, context, current_event):
"""See IEmailCommand."""
+ if isinstance(context, CreateBugParams):
+ # No one intentially reports a duplicate bug. Bug email commands
+ # support CreateBugParams, so in this case, just return.
+ return context, current_event
self._ensureNumberOfArguments()
[bug_id] = self.string_args
@@ -421,6 +456,10 @@
if cve is None:
raise EmailProcessingError(
'Launchpad can\'t find the CVE "%s".' % cve_sequence)
+ if isinstance(bug, CreateBugParams):
+ bug.cve = cve
+ return bug, current_event
+
bug.linkCVE(cve, getUtility(ILaunchBag).user)
return bug, current_event
@@ -528,7 +567,7 @@
assert rest, "This is the fallback for unexpected path components."
raise BugTargetNotFound("Unexpected path components: %s" % rest)
- def execute(self, bug):
+ def execute(self, bug, bug_event):
"""See IEmailCommand."""
if bug is None:
raise EmailProcessingError(
@@ -551,6 +590,20 @@
except BugTargetNotFound, error:
raise EmailProcessingError(unicode(error), stop_processing=True)
event = None
+
+ if isinstance(bug, CreateBugParams):
+ # Enough information has been gathered to create a new bug.
+ kwargs = {
+ 'product': IProduct(bug_target, None),
+ 'distribution': IDistribution(bug_target, None),
+ 'sourcepackagename': ISourcePackageName(bug_target, None),
+ }
+ bug.setBugTarget(**kwargs)
+ bug, bug_event = getUtility(IBugSet).createBug(
+ bug, notify_event=False)
+ event = ObjectCreatedEvent(bug.bugtasks[0])
+ # Continue because the bug_target may be a subordinate bugtask.
+
bugtask = bug.getBugTask(bug_target)
if (bugtask is None and
IDistributionSourcePackage.providedBy(bug_target)):
@@ -568,7 +621,7 @@
bugtask = self._create_bug_task(bug, bug_target)
event = ObjectCreatedEvent(bugtask)
- return bugtask, event
+ return bugtask, event, bug_event
def _targetBug(self, user, bug, series, sourcepackagename=None):
"""Try to target the bug the given distroseries.
@@ -796,16 +849,10 @@
"""See `IEmailCommand`."""
# Tags are always lowercase.
string_args = [arg.lower() for arg in self.string_args]
- # Bug.tags returns a Zope List, which does not support Python list
- # operations so we need to convert it.
- tags = list(bug.tags)
-
- # XXX: DaveMurphy 2007-07-11: in the following loop we process each
- # tag in turn. Each tag that is either invalid or unassigned will
- # result in a mail to the submitter. This may result in several mails
- # for a single command. This will need to be addressed if that becomes
- # a problem.
-
+ if bug.tags is None:
+ tags = []
+ else:
+ tags = list(bug.tags)
for arg in string_args:
# Are we adding or removing a tag?
if arg.startswith('-'):
@@ -832,13 +879,6 @@
tag=tag))
else:
tags.append(arg)
-
- # Duplicates are dealt with when the tags are stored in the DB (which
- # incidentally uses a set to achieve this). Since the code already
- # exists we don't duplicate it here.
-
- # Bug.tags expects to be given a Python list, so there is no need to
- # convert it back.
bug.tags = tags
return bug, current_event
=== modified file 'lib/lp/bugs/mail/handler.py'
--- lib/lp/bugs/mail/handler.py 2011-08-29 20:04:54 +0000
+++ lib/lp/bugs/mail/handler.py 2011-09-15 19:33:27 +0000
@@ -30,6 +30,7 @@
IBugAttachmentSet,
)
from lp.bugs.interfaces.bugmessage import IBugMessageSet
+from lp.bugs.interfaces.bug import CreateBugParams
from lp.bugs.mail.commands import BugEmailCommands
from lp.services.mail.helpers import (
ensure_not_weakly_authenticated,
@@ -273,18 +274,20 @@
message = self.appendBugComment(
bug, signed_msg, filealias)
add_comment_to_bug = False
- else:
+ self.processAttachments(bug, message, signed_msg)
+ elif IBugTaskEmailCommand.providedBy(command):
+ self.notify_bugtask_event(bugtask_event, bug_event)
+ bugtask, bugtask_event, bug_event = command.execute(
+ bug, bug_event)
+ if isinstance(bug, CreateBugParams):
+ bug = bugtask.bug
message = bug.initial_message
- self.processAttachments(bug, message, signed_msg)
- elif IBugTaskEmailCommand.providedBy(command):
- self.notify_bugtask_event(bugtask_event, bug_event)
- bugtask, bugtask_event = command.execute(
- bug)
+ self.processAttachments(bug, message, signed_msg)
elif IBugEditEmailCommand.providedBy(command):
bug, bug_event = command.execute(bug, bug_event)
elif IBugTaskEditEmailCommand.providedBy(command):
if bugtask is None:
- if len(bug.bugtasks) == 0:
+ if isinstance(bug, CreateBugParams):
self.handleNoAffectsTarget()
bugtask = guess_bugtask(
bug, getUtility(ILaunchBag).user)
@@ -306,6 +309,9 @@
'\n'.join(str(error) for error, command
in processing_errors),
[command for error, command in processing_errors])
+ if isinstance(bug, CreateBugParams):
+ # A new bug without any commands was sent.
+ self.handleNoAffectsTarget()
self.notify_bug_event(bug_event)
self.notify_bugtask_event(bugtask_event, bug_event)
=== modified file 'lib/lp/bugs/mail/tests/test_commands.py'
--- lib/lp/bugs/mail/tests/test_commands.py 2011-08-16 18:39:51 +0000
+++ lib/lp/bugs/mail/tests/test_commands.py 2011-09-15 19:33:27 +0000
@@ -1,11 +1,32 @@
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-from canonical.testing.layers import DatabaseFunctionalLayer
+from lazr.lifecycle.interfaces import (
+ IObjectCreatedEvent,
+ IObjectModifiedEvent,
+ )
+
+from canonical.testing.layers import (
+ DatabaseFunctionalLayer,
+ LaunchpadFunctionalLayer,
+ )
+from lp.bugs.interfaces.bug import CreateBugParams
from lp.bugs.mail.commands import (
AffectsEmailCommand,
- )
-from lp.services.mail.interfaces import BugTargetNotFound
+ BugEmailCommand,
+ CVEEmailCommand,
+ DuplicateEmailCommand,
+ PrivateEmailCommand,
+ SecurityEmailCommand,
+ SubscribeEmailCommand,
+ SummaryEmailCommand,
+ TagEmailCommand,
+ UnsubscribeEmailCommand,
+ )
+from lp.services.mail.interfaces import (
+ BugTargetNotFound,
+ EmailProcessingError,
+ )
from lp.testing import (
login_celebrity,
login_person,
@@ -143,3 +164,429 @@
self.assertRaisesWithContent(
BugTargetNotFound, message,
AffectsEmailCommand.getBugTarget, 'fnord/pting/snarf/thrup')
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ product = self.factory.makeProduct(name='fnord')
+ login_person(bug.bugtasks[0].target.owner)
+ command = AffectsEmailCommand('affects', ['fnord'])
+ bugtask, bugtask_event, bug_event = command.execute(bug, None)
+ self.assertEqual(bug, bugtask.bug)
+ self.assertEqual(product, bugtask.target)
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertEqual(None, bug_event)
+
+ def test_execute_bug_params_product(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ product = self.factory.makeProduct(name='fnord')
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord')
+ command = AffectsEmailCommand('affects', ['fnord'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=user)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(product, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertEqual('borked\n affects fnord', bugtask.bug.description)
+ self.assertEqual(user, bugtask.bug.owner)
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+ def test_execute_bug_params_productseries(self):
+ product = self.factory.makeProduct(name='fnord')
+ login_person(product.owner)
+ series = self.factory.makeProductSeries(name='pting', product=product)
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord/pting')
+ command = AffectsEmailCommand('affects', ['fnord/pting'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=product.owner)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(series, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertEqual(2, len(bugtask.bug.bugtasks))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+ def test_execute_bug_params_distribution(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ distribution = self.factory.makeDistribution(name='fnord')
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord')
+ command = AffectsEmailCommand('affects', ['fnord'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=user)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(distribution, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+ def test_execute_bug_params_dsp(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ distribution = self.factory.makeDistribution(name='fnord')
+ series = self.factory.makeDistroSeries(
+ name='pting', distribution=distribution)
+ package = self.factory.makeSourcePackage(
+ sourcepackagename='snarf', distroseries=series, publish=True)
+ dsp = distribution.getSourcePackage(package.name)
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord/snarf')
+ command = AffectsEmailCommand('affects', ['fnord/snarf'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=user)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(dsp, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+ def test_execute_bug_params_distroseries(self):
+ distribution = self.factory.makeDistribution(name='fnord')
+ login_person(distribution.owner)
+ series = self.factory.makeDistroSeries(
+ name='pting', distribution=distribution)
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord/pting')
+ command = AffectsEmailCommand('affects', ['fnord/pting'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=distribution.owner)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(series, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertEqual(2, len(bugtask.bug.bugtasks))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+ def test_execute_bug_params_distroseries_sourcepackage(self):
+ distribution = self.factory.makeDistribution(name='fnord')
+ login_person(distribution.owner)
+ series = self.factory.makeDistroSeries(
+ name='pting', distribution=distribution)
+ package = self.factory.makeSourcePackage(
+ sourcepackagename='snarf', distroseries=series, publish=True)
+ message = self.factory.makeMessage(
+ subject='bug title', content='borked\n affects fnord/pting/snarf')
+ command = AffectsEmailCommand('affects', ['fnord/pting/snarf'])
+ bug_params = CreateBugParams(
+ title='bug title', msg=message, owner=distribution.owner)
+ bugtask, bugtask_event, bug_event = command.execute(bug_params, None)
+ self.assertEqual(package, bugtask.target)
+ self.assertEqual('bug title', bugtask.bug.title)
+ self.assertEqual(2, len(bugtask.bug.bugtasks))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bugtask_event))
+ self.assertTrue(IObjectCreatedEvent.providedBy(bug_event))
+
+
+class BugEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = LaunchpadFunctionalLayer
+
+ def test_execute_bug_id(self):
+ bug = self.factory.makeBug()
+ command = BugEmailCommand('bug', [str(bug.id)])
+ self.assertEqual((bug, None), command.execute(None, None))
+
+ def test_execute_bug_id_wrong_type(self):
+ command = BugEmailCommand('bug', ['nickname'])
+ error = self.assertRaises(
+ EmailProcessingError, command.execute, None, None)
+ message = str(error).split('\n')
+ self.assertEqual(
+ "The 'bug' command expects either 'new' or a bug id.", message[0])
+
+ def test_execute_bug_id_not_found(self):
+ command = BugEmailCommand('bug', ['9999999'])
+ error = self.assertRaises(
+ EmailProcessingError, command.execute, None, None)
+ message = str(error).split('\n')
+ self.assertEqual(
+ "There is no such bug in Launchpad: 9999999", message[0])
+
+ def test_execute_bug_id_new(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ message = self.factory.makeSignedMessage(
+ body='borked\n affects fnord',
+ subject='title borked',
+ to_address='new@xxxxxxxxxxxxxxxxxx')
+ filealias = self.factory.makeLibraryFileAlias()
+ command = BugEmailCommand('bug', ['new'])
+ params, event = command.execute(message, filealias)
+ self.assertEqual(None, event)
+ self.assertEqual(user, params.owner)
+ self.assertEqual('title borked', params.title)
+ self.assertEqual(message['Message-Id'], params.msg.rfc822msgid)
+
+
+class PrivateEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ command = PrivateEmailCommand('private', ['yes'])
+ exec_bug, event = command.execute(bug, None)
+ self.assertEqual(bug, exec_bug)
+ self.assertEqual(True, bug.private)
+ self.assertTrue(IObjectModifiedEvent.providedBy(event))
+
+ def test_execute_bug_params(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = PrivateEmailCommand('private', ['yes'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(True, bug_params.private)
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params_with_security(self):
+ # BugSet.createBug() requires new security bugs to be private.
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(
+ title='bug title', owner=user, security_related='yes')
+ command = PrivateEmailCommand('private', ['no'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(True, bug_params.private)
+ self.assertEqual(dummy_event, event)
+
+
+class SecurityEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ command = SecurityEmailCommand('security', ['yes'])
+ exec_bug, event = command.execute(bug, None)
+ self.assertEqual(bug, exec_bug)
+ self.assertEqual(True, bug.security_related)
+ self.assertTrue(IObjectModifiedEvent.providedBy(event))
+
+ def test_execute_bug_params(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = SecurityEmailCommand('security', ['yes'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(True, bug_params.security_related)
+ self.assertEqual(True, bug_params.private)
+ self.assertEqual(dummy_event, event)
+
+
+class SubscribeEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug_with_user_name(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ subscriber = self.factory.makePerson()
+ command = SubscribeEmailCommand('subscribe', [subscriber.name])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertContentEqual(
+ [bug.owner, subscriber], bug.getDirectSubscribers())
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_without_user_name(self):
+ bug = self.factory.makeBug()
+ target_owner = bug.bugtasks[0].target.owner
+ login_person(target_owner)
+ command = SubscribeEmailCommand('subscribe', [])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertContentEqual(
+ [bug.owner, target_owner], bug.getDirectSubscribers())
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params_one_subscriber(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ subscriber = self.factory.makePerson()
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = SubscribeEmailCommand('subscribe', [subscriber.name])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertContentEqual([subscriber], bug_params.subscribers)
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params_many_subscriber(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ subscriber_1 = self.factory.makePerson()
+ subscriber_2 = self.factory.makePerson()
+ bug_params = CreateBugParams(
+ title='bug title', owner=user, subscribers=[subscriber_1])
+ command = SubscribeEmailCommand('subscribe', [subscriber_2.name])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertContentEqual(
+ [subscriber_1, subscriber_2], bug_params.subscribers)
+ self.assertEqual(dummy_event, event)
+
+
+class UnsubscribeEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug_with_user_name(self):
+ bug = self.factory.makeBug()
+ target_owner = bug.bugtasks[0].target.owner
+ login_person(target_owner)
+ bug.subscribe(target_owner, target_owner)
+ command = UnsubscribeEmailCommand('unsubscribe', [target_owner.name])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertContentEqual(
+ [bug.owner], bug.getDirectSubscribers())
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_without_user_name(self):
+ bug = self.factory.makeBug()
+ target_owner = bug.bugtasks[0].target.owner
+ login_person(target_owner)
+ bug.subscribe(target_owner, target_owner)
+ command = UnsubscribeEmailCommand('unsubscribe', [])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertContentEqual(
+ [bug.owner], bug.getDirectSubscribers())
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params(self):
+ # Unsubscribe does nothing because the is not yet a bug.
+ # Any value can be used for the user name.
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = UnsubscribeEmailCommand('unsubscribe', ['non-existent'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(dummy_event, event)
+
+
+class SummaryEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ command = SummaryEmailCommand('summary', ['new title'])
+ exec_bug, event = command.execute(bug, None)
+ self.assertEqual(bug, exec_bug)
+ self.assertEqual('new title', bug.title)
+ self.assertTrue(IObjectModifiedEvent.providedBy(event))
+
+ def test_execute_bug_params(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = SummaryEmailCommand('summary', ['new title'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual('new title', bug_params.title)
+ self.assertEqual(dummy_event, event)
+
+
+class DuplicateEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ master_bug = self.factory.makeBug()
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ command = DuplicateEmailCommand('duplicate', [str(master_bug.id)])
+ exec_bug, event = command.execute(bug, None)
+ self.assertEqual(master_bug, exec_bug)
+ self.assertEqual(master_bug, bug.duplicateof)
+ self.assertTrue(IObjectModifiedEvent.providedBy(event))
+
+ def test_execute_bug_params(self):
+ # duplicate does nothing because the is not yet a bug.
+ # Any value can be used for the bug is.
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = DuplicateEmailCommand('duplicate', ['non-existent'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(dummy_event, event)
+
+
+class CVEEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ cve = self.factory.makeCVE('1999-1717')
+ command = CVEEmailCommand('cve', ['1999-1717'])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertEqual([cve], [cve_link.cve for cve_link in bug.cve_links])
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ cve = self.factory.makeCVE('1999-1717')
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = CVEEmailCommand('cve', ['1999-1717'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertEqual(cve, params.cve)
+ self.assertEqual(dummy_event, event)
+
+
+class TagEmailCommandTestCase(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_execute_bug(self):
+ bug = self.factory.makeBug()
+ login_person(bug.bugtasks[0].target.owner)
+ bug.tags = ['form']
+ command = TagEmailCommand('tag', ['ui', 'trivial'])
+ dummy_event = object()
+ exec_bug, event = command.execute(bug, dummy_event)
+ self.assertEqual(bug, exec_bug)
+ self.assertContentEqual(['form', 'ui', 'trivial'], bug.tags)
+ self.assertEqual(dummy_event, event)
+
+ def test_execute_bug_params(self):
+ user = self.factory.makePerson()
+ login_person(user)
+ bug_params = CreateBugParams(title='bug title', owner=user)
+ command = TagEmailCommand('tag', ['ui', 'trivial'])
+ dummy_event = object()
+ params, event = command.execute(bug_params, dummy_event)
+ self.assertEqual(bug_params, params)
+ self.assertContentEqual(['ui', 'trivial'], bug_params.tags)
+ self.assertEqual(dummy_event, event)
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2011-09-13 22:43:21 +0000
+++ lib/lp/bugs/model/bug.py 2011-09-15 19:33:27 +0000
@@ -228,7 +228,7 @@
"distribution", "sourcepackagename",
"product", "status", "subscribers", "tags",
"subscribe_owner", "filed_by", "importance",
- "milestone", "assignee"])
+ "milestone", "assignee", "cve"])
class BugTag(SQLBase):
@@ -2606,7 +2606,7 @@
orderBy=['datecreated'])
return bug
- def createBug(self, bug_params):
+ def createBug(self, bug_params, notify_event=True):
"""See `IBugSet`."""
# Make a copy of the parameter object, because we might modify some
# of its attribute values below.
@@ -2668,11 +2668,14 @@
bug_task.transitionToMilestone(params.milestone, params.owner)
# Tell everyone.
- notify(event)
+ if notify_event:
+ notify(event)
# Calculate the bug's initial heat.
bug.updateHeat()
+ if not notify_event:
+ return bug, event
return bug
def createBugWithoutTarget(self, bug_params):
@@ -2736,6 +2739,9 @@
# Mark the bug reporter as affected by that bug.
bug.markUserAffected(bug.owner)
+ if params.cve is not None:
+ bug.linkCVE(params.cve, params.owner)
+
# Populate the creation event.
if params.filed_by is None:
event = ObjectCreatedEvent(bug, user=params.owner)
=== modified file 'lib/lp/bugs/tests/test_bug.py'
--- lib/lp/bugs/tests/test_bug.py 2011-05-24 08:58:38 +0000
+++ lib/lp/bugs/tests/test_bug.py 2011-09-15 19:33:27 +0000
@@ -287,3 +287,14 @@
self.assertRaises(
UserCannotEditBugTaskMilestone,
getUtility(IBugSet).createBug, params)
+
+ def test_createBugWithoutTarget_cve(self):
+ cve = self.factory.makeCVE('1999-1717')
+ target = self.factory.makeProduct()
+ person = self.factory.makePerson()
+ with person_logged_in(person):
+ params = CreateBugParams(
+ owner=person, title="A bug", comment="bad thing.", cve=cve)
+ params.setBugTarget(product=target)
+ bug = getUtility(IBugSet).createBug(params)
+ self.assertEqual([cve], [cve_link.cve for cve_link in bug.cve_links])
=== modified file 'lib/lp/registry/adapters.py'
--- lib/lp/registry/adapters.py 2011-08-19 15:29:12 +0000
+++ lib/lp/registry/adapters.py 2011-09-15 19:33:27 +0000
@@ -130,3 +130,8 @@
# Used for traversal from distro to +pubconf.
config = getUtility(IPublisherConfigSet).getByDistribution(distro)
return config
+
+
+def package_to_sourcepackagename(package):
+ """Adapts a package to its `ISourcePackageName`."""
+ return package.sourcepackagename
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2011-09-10 00:16:56 +0000
+++ lib/lp/registry/configure.zcml 2011-09-15 19:33:27 +0000
@@ -554,6 +554,11 @@
factory="lp.registry.adapters.sourcepackage_to_distribution"
permission="zope.Public"/>
<adapter
+ provides="lp.registry.interfaces.sourcepackagename.ISourcePackageName"
+ for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
+ factory="lp.registry.adapters.package_to_sourcepackagename"
+ permission="zope.Public"/>
+ <adapter
factory="lp.registry.browser.distributionsourcepackage.DistributionSourcePackageBreadcrumb"/>
<!-- CommercialSubscription -->
@@ -1478,6 +1483,11 @@
factory="lp.registry.adapters.productseries_to_product"
permission="zope.Public"/>
<adapter
+ provides="lp.registry.interfaces.product.IProduct"
+ for="lp.registry.interfaces.productseries.IProductSeries"
+ factory="lp.registry.adapters.productseries_to_product"
+ permission="zope.Public"/>
+ <adapter
provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
for="lp.registry.interfaces.productseries.IProductSeries"
factory="lp.registry.browser.productseries.ProductSeriesBreadcrumb"
@@ -1670,6 +1680,11 @@
<adapter
factory="lp.registry.browser.sourcepackage.SourcePackageBreadcrumb"/>
<adapter
+ provides="lp.registry.interfaces.sourcepackagename.ISourcePackageName"
+ for="lp.registry.interfaces.sourcepackage.ISourcePackage"
+ factory="lp.registry.adapters.package_to_sourcepackagename"
+ permission="zope.Public"/>
+ <adapter
provides="lp.app.interfaces.launchpad.IServiceUsage"
for="lp.registry.interfaces.sourcepackage.ISourcePackage"
factory="lp.registry.adapters.sourcepackage_to_distribution"
=== modified file 'lib/lp/registry/tests/test_adapters.py'
--- lib/lp/registry/tests/test_adapters.py 2011-07-25 19:21:49 +0000
+++ lib/lp/registry/tests/test_adapters.py 2011-09-15 19:33:27 +0000
@@ -8,11 +8,13 @@
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.registry.adapters import (
distroseries_to_distribution,
+ package_to_sourcepackagename,
productseries_to_product,
sourcepackage_to_distribution,
)
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.product import IProduct
+from lp.registry.interfaces.sourcepackagename import ISourcePackageName
from lp.testing import TestCaseWithFactory
@@ -37,6 +39,22 @@
self.assertEqual(
package.distroseries.distribution, IDistribution(package))
+ def test_sourcepackage_to_sourcepackagename(self):
+ # A sourcepackagename can be retrieved source package.
+ package = self.factory.makeSourcePackage()
+ spn = package_to_sourcepackagename(package)
+ self.assertTrue(ISourcePackageName.providedBy(spn))
+ self.assertEqual(
+ package.sourcepackagename, ISourcePackageName(package))
+
+ def test_distributionsourcepackage_to_sourcepackagename(self):
+ # A sourcepackagename can be retrieved distribution source package.
+ package = self.factory.makeDistributionSourcePackage()
+ spn = package_to_sourcepackagename(package)
+ self.assertTrue(ISourcePackageName.providedBy(spn))
+ self.assertEqual(
+ package.sourcepackagename, ISourcePackageName(package))
+
def test_distroseries_to_distribution(self):
# distroseries_to_distribution() returns an IDistribution given an
# IDistroSeries.
@@ -54,3 +72,4 @@
product = productseries_to_product(product_series)
self.assertTrue(IProduct.providedBy(product))
self.assertEqual(product_series.product, product)
+ self.assertEqual(product, IProduct(product_series))