Example #1
0
 def test_processWithMergeDirectiveEmail(self):
     """process errors if merge@ address used."""
     message = self.factory.makeSignedMessage()
     file_alias = self.factory.makeLibraryFileAlias(
         content=message.as_string())
     # mail.incoming.handleMail also explicitly does this.
     switch_dbuser(config.processmail.dbuser)
     code_handler = CodeHandler()
     code_handler.process(message, '*****@*****.**', file_alias)
     notification = pop_notifications()[0]
     self.assertEqual(
         'Merge directive not supported.', notification['Subject'])
 def test_processWithMergeDirectiveEmail(self):
     """process errors if merge@ address used."""
     message = self.factory.makeSignedMessage()
     file_alias = self.factory.makeLibraryFileAlias(
         content=message.as_string())
     # mail.incoming.handleMail also explicitly does this.
     switch_dbuser(config.processmail.dbuser)
     code_handler = CodeHandler()
     code_handler.process(message, '*****@*****.**', file_alias)
     notification = pop_notifications()[0]
     self.assertEqual(
         'Merge directive not supported.', notification['Subject'])
Example #3
0
 def setUp(self):
     super(TestCodeHandler, self).setUp(user='******')
     self.code_handler = CodeHandler()
     self._old_policy = setSecurityPolicy(LaunchpadSecurityPolicy)
Example #4
0
class TestCodeHandler(TestCaseWithFactory):
    """Test the code email hander."""

    layer = ZopelessAppServerLayer

    def setUp(self):
        super(TestCodeHandler, self).setUp(user='******')
        self.code_handler = CodeHandler()
        self._old_policy = setSecurityPolicy(LaunchpadSecurityPolicy)

    def tearDown(self):
        setSecurityPolicy(self._old_policy)
        super(TestCodeHandler, self).tearDown()

    def test_get(self):
        handler = mail_handlers.get(config.launchpad.code_domain)
        self.assertIsInstance(handler, CodeHandler)

    def test_process(self):
        """Processing an email creates an appropriate CodeReviewComment."""
        mail = self.factory.makeSignedMessage('<my-id>')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(
            mail, email_addr, None), "Succeeded, but didn't return True")
        # if the message has not been created, this raises SQLObjectNotFound
        MessageSet().get('<my-id>')

    def test_process_packagebranch(self):
        """Processing an email related to a package branch works.."""
        mail = self.factory.makeSignedMessage('<my-id>')
        target_branch = self.factory.makePackageBranch()
        bmp = self.factory.makeBranchMergeProposal(
            target_branch=target_branch)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertIn(
            '<my-id>', [comment.message.rfc822msgid
                        for comment in bmp.all_comments])

    def test_process_git(self):
        """Processing an email related to a Git-based merge proposal works."""
        mail = self.factory.makeSignedMessage('<my-id>')
        bmp = self.factory.makeBranchMergeProposalForGit()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertIn(
            '<my-id>',
            [comment.message.rfc822msgid for comment in bmp.all_comments])

    def test_processBadAddress(self):
        """When a bad address is supplied, it returns False."""
        mail = self.factory.makeSignedMessage('<my-id>')
        switch_dbuser(config.processmail.dbuser)
        self.assertFalse(self.code_handler.process(mail,
            '*****@*****.**', None))

    def test_processNonExistantAddress(self):
        """When a non-existant address is supplied, it returns False."""
        mail = self.factory.makeSignedMessage('<my-id>')
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(mail,
            '*****@*****.**', None))
        notification = pop_notifications()[0]
        self.assertEqual('Submit Request Failure', notification['subject'])
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertIn(
            "There is no merge proposal at [email protected]\n",
            message.get_payload(decode=True))

    def test_processBadVote(self):
        """process handles bad votes properly."""
        mail = self.factory.makeSignedMessage(body=' vote badvalue')
        # Make sure that the correct user principal is there.
        login(mail['From'])
        bmp = self.factory.makeBranchMergeProposal()
        # Remove the notifications sent about the new proposal.
        pop_notifications()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(
            mail, email_addr, None), "Didn't return True")
        notification = pop_notifications()[0]
        self.assertEqual('Submit Request Failure', notification['subject'])
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertEqual(dedent("""\
        An error occurred while processing a mail you sent to Launchpad's email
        interface.

        Failing command:
            vote badvalue

        Error message:

        The 'review' command expects any of the following arguments:
        abstain, approve, disapprove, needs-fixing, needs-info, resubmit

        For example:

            review needs-fixing


        --\x20
        For more information about using Launchpad by email, see
        https://help.launchpad.net/EmailInterface
        or send an email to [email protected]"""),
                                message.get_payload(decode=True))
        self.assertEqual(mail['From'], notification['To'])

    def test_getReplyAddress(self):
        """getReplyAddress should return From or Reply-to address."""
        mail = self.factory.makeSignedMessage()
        switch_dbuser(config.processmail.dbuser)
        self.assertEqual(
            mail['From'], self.code_handler._getReplyAddress(mail))
        mail['Reply-to'] = self.factory.getUniqueEmailAddress()
        self.assertEqual(
            mail['Reply-to'], self.code_handler._getReplyAddress(mail))

    def test_process_for_imported_branch(self):
        """Make sure that the database user is able refer to import branches.

        Import branches have different permission checks than other branches.

        Permission to mark a merge proposal as approved checks launchpad.Edit
        of the target branch, or membership of the review team on the target
        branch.  For import branches launchpad.Edit also checks the registrant
        of the code import if there is one, and membership of vcs-imports.  So
        if someone is attempting to review something on an import branch, but
        they don't have launchpad.Edit but are a member of the review team,
        then a check against the code import is done.
        """
        mail = self.factory.makeSignedMessage(body=' merge approved')
        code_import = self.factory.makeCodeImport()
        bmp = self.factory.makeBranchMergeProposal(
            target_branch=code_import.branch)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        pop_notifications()
        self.code_handler.process(mail, email_addr, None)
        notification = pop_notifications()[0]
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertTrue(
            "You are not a reviewer for the branch" in
            message.get_payload(decode=True))

    def test_processVote(self):
        """Process respects the vote command."""
        mail = self.factory.makeSignedMessage(body=' vote Abstain EBAILIWICK')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('ebailiwick', bmp.all_comments[0].vote_tag)

    def test_processVoteColon(self):
        """Process respects the vote: command."""
        mail = self.factory.makeSignedMessage(
            body=' vote: Abstain EBAILIWICK')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('ebailiwick', bmp.all_comments[0].vote_tag)

    def test_processReview(self):
        """Process respects the review command."""
        mail = self.factory.makeSignedMessage(body=' review Abstain ROAR!')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('roar!', bmp.all_comments[0].vote_tag)

    def test_processReviewColon(self):
        """Process respects the review: command."""
        mail = self.factory.makeSignedMessage(body=' review: Abstain ROAR!')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('roar!', bmp.all_comments[0].vote_tag)

    def test_processWithExistingVote(self):
        """Process respects the vote command."""
        mail = self.factory.makeSignedMessage(body=' vote Abstain EBAILIWICK')
        sender = self.factory.makePerson()
        bmp = self.factory.makeBranchMergeProposal(reviewer=sender)
        email_addr = bmp.address
        [vote] = list(bmp.votes)
        self.assertEqual(sender, vote.reviewer)
        self.assertTrue(vote.comment is None)
        switch_dbuser(config.processmail.dbuser)
        # Login the sender as they are set as the message owner.
        login_person(sender)
        self.code_handler.process(mail, email_addr, None)
        comment = bmp.all_comments[0]
        self.assertEqual(CodeReviewVote.ABSTAIN, comment.vote)
        self.assertEqual('ebailiwick', comment.vote_tag)
        [vote] = list(bmp.votes)
        self.assertEqual(sender, vote.reviewer)
        self.assertEqual(comment, vote.comment)

    def test_processmail_generates_job(self):
        """Processing mail causes an email job to be created."""
        mail = self.factory.makeSignedMessage(
            body=' vote Abstain EBAILIWICK', subject='subject')
        bmp = self.factory.makeBranchMergeProposal()
        # Pop the notifications generated by the new proposal.
        pop_notifications()
        subscriber = self.factory.makePerson()
        bmp.source_branch.subscribe(
            subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
            CodeReviewNotificationLevel.FULL, subscriber)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        job = Store.of(bmp).find(
            BranchMergeProposalJob,
            BranchMergeProposalJob.branch_merge_proposal == bmp,
            BranchMergeProposalJob.job_type ==
            BranchMergeProposalJobType.CODE_REVIEW_COMMENT_EMAIL).one()
        self.assertIsNot(None, job)
        # Ensure the DB operations violate no constraints.
        Store.of(bmp).flush()

    def test_getBranchMergeProposal(self):
        """The correct BranchMergeProposal is returned for the address."""
        bmp = self.factory.makeBranchMergeProposal()
        switch_dbuser(config.processmail.dbuser)
        bmp2 = self.code_handler.getBranchMergeProposal(bmp.address)
        self.assertEqual(bmp, bmp2)

    def test_getBranchMergeProposalInvalid(self):
        """InvalidBranchMergeProposalAddress is raised if appropriate."""
        switch_dbuser(config.processmail.dbuser)
        self.assertRaises(InvalidBranchMergeProposalAddress,
                          self.code_handler.getBranchMergeProposal, '')
        self.assertRaises(InvalidBranchMergeProposalAddress,
                          self.code_handler.getBranchMergeProposal, 'mp+abc@')

    def test_processWithMergeDirectiveEmail(self):
        """process errors if merge@ address used."""
        message = self.factory.makeSignedMessage()
        file_alias = self.factory.makeLibraryFileAlias(
            content=message.as_string())
        # mail.incoming.handleMail also explicitly does this.
        switch_dbuser(config.processmail.dbuser)
        code_handler = CodeHandler()
        code_handler.process(message, '*****@*****.**', file_alias)
        notification = pop_notifications()[0]
        self.assertEqual(
            'Merge directive not supported.', notification['Subject'])

    def test_reviewer_with_diff(self):
        """Requesting a review with a diff works."""
        bmp = make_merge_proposal_without_reviewers(self.factory)
        self.factory.makePreviewDiff(merge_proposal=bmp)
        # To record the diff in the librarian.
        transaction.commit()
        eric = self.factory.makePerson(name="eric", email="*****@*****.**")
        mail = self.factory.makeSignedMessage(body=' reviewer eric')
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        [vote] = bmp.votes
        self.assertEqual(eric, vote.reviewer)

    def test_processMissingSubject(self):
        """If the subject is missing, the user is warned by email."""
        mail = self.factory.makeSignedMessage(
            body=' review abstain',
            subject='')
        bmp = self.factory.makeBranchMergeProposal()
        pop_notifications()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        [notification] = pop_notifications()

        self.assertEqual(
            notification['Subject'], 'Error Creating Merge Proposal')
        self.assertEqual(
            notification.get_payload(decode=True),
            'Your message did not contain a subject.  Launchpad code '
            'reviews require all\nemails to contain subject lines.  '
            'Please re-send your email including the\nsubject line.\n\n')
        self.assertEqual(notification['to'],
            mail['from'])
        self.assertEqual(0, bmp.all_comments.count())

    def test_notifies_modification(self):
        """Changes to the merge proposal itself trigger events."""
        mail = self.factory.makeSignedMessage(body=' merge approved')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        login_person(bmp.merge_target.owner)
        _, events = self.assertNotifies(
            [ObjectModifiedEvent, ObjectCreatedEvent], False,
            self.code_handler.process, mail, email_addr, None)
        self.assertEqual(bmp, events[0].object)
        self.assertEqual(
            BranchMergeProposalStatus.WORK_IN_PROGRESS,
            events[0].object_before_modification.queue_status)
        self.assertEqual(
            BranchMergeProposalStatus.CODE_APPROVED,
            events[0].object.queue_status)
 def setUp(self):
     super(TestCodeHandler, self).setUp(user='******')
     self.code_handler = CodeHandler()
     self._old_policy = setSecurityPolicy(LaunchpadSecurityPolicy)
class TestCodeHandler(TestCaseWithFactory):
    """Test the code email hander."""

    layer = ZopelessAppServerLayer

    def setUp(self):
        super(TestCodeHandler, self).setUp(user='******')
        self.code_handler = CodeHandler()
        self._old_policy = setSecurityPolicy(LaunchpadSecurityPolicy)

    def tearDown(self):
        setSecurityPolicy(self._old_policy)
        super(TestCodeHandler, self).tearDown()

    def test_get(self):
        handler = mail_handlers.get(config.launchpad.code_domain)
        self.assertIsInstance(handler, CodeHandler)

    def test_process(self):
        """Processing an email creates an appropriate CodeReviewComment."""
        mail = self.factory.makeSignedMessage('<my-id>')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(
            mail, email_addr, None), "Succeeded, but didn't return True")
        # if the message has not been created, this raises SQLObjectNotFound
        MessageSet().get('<my-id>')

    def test_process_packagebranch(self):
        """Processing an email related to a package branch works.."""
        mail = self.factory.makeSignedMessage('<my-id>')
        target_branch = self.factory.makePackageBranch()
        bmp = self.factory.makeBranchMergeProposal(
            target_branch=target_branch)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertIn(
            '<my-id>', [comment.message.rfc822msgid
                        for comment in bmp.all_comments])

    def test_processBadAddress(self):
        """When a bad address is supplied, it returns False."""
        mail = self.factory.makeSignedMessage('<my-id>')
        switch_dbuser(config.processmail.dbuser)
        self.assertFalse(self.code_handler.process(mail,
            '*****@*****.**', None))

    def test_processNonExistantAddress(self):
        """When a non-existant address is supplied, it returns False."""
        mail = self.factory.makeSignedMessage('<my-id>')
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(mail,
            '*****@*****.**', None))
        notification = pop_notifications()[0]
        self.assertEqual('Submit Request Failure', notification['subject'])
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertIn(
            "There is no merge proposal at [email protected]\n",
            message.get_payload(decode=True))

    def test_processBadVote(self):
        """process handles bad votes properly."""
        mail = self.factory.makeSignedMessage(body=' vote badvalue')
        # Make sure that the correct user principal is there.
        login(mail['From'])
        bmp = self.factory.makeBranchMergeProposal()
        # Remove the notifications sent about the new proposal.
        pop_notifications()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.assertTrue(self.code_handler.process(
            mail, email_addr, None), "Didn't return True")
        notification = pop_notifications()[0]
        self.assertEqual('Submit Request Failure', notification['subject'])
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertEqual(dedent("""\
        An error occurred while processing a mail you sent to Launchpad's email
        interface.

        Failing command:
            vote badvalue

        Error message:

        The 'review' command expects any of the following arguments:
        abstain, approve, disapprove, needs-fixing, needs-info, resubmit

        For example:

            review needs-fixing


        --\x20
        For more information about using Launchpad by e-mail, see
        https://help.launchpad.net/EmailInterface
        or send an email to [email protected]"""),
                                message.get_payload(decode=True))
        self.assertEqual(mail['From'], notification['To'])

    def test_getReplyAddress(self):
        """getReplyAddress should return From or Reply-to address."""
        mail = self.factory.makeSignedMessage()
        switch_dbuser(config.processmail.dbuser)
        self.assertEqual(
            mail['From'], self.code_handler._getReplyAddress(mail))
        mail['Reply-to'] = self.factory.getUniqueEmailAddress()
        self.assertEqual(
            mail['Reply-to'], self.code_handler._getReplyAddress(mail))

    def test_process_for_imported_branch(self):
        """Make sure that the database user is able refer to import branches.

        Import branches have different permission checks than other branches.

        Permission to mark a merge proposal as approved checks launchpad.Edit
        of the target branch, or membership of the review team on the target
        branch.  For import branches launchpad.Edit also checks the registrant
        of the code import if there is one, and membership of vcs-imports.  So
        if someone is attempting to review something on an import branch, but
        they don't have launchpad.Edit but are a member of the review team,
        then a check against the code import is done.
        """
        mail = self.factory.makeSignedMessage(body=' merge approved')
        code_import = self.factory.makeCodeImport()
        bmp = self.factory.makeBranchMergeProposal(
            target_branch=code_import.branch)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        pop_notifications()
        self.code_handler.process(mail, email_addr, None)
        notification = pop_notifications()[0]
        # The returned message is a multipart message, the first part is
        # the message, and the second is the original message.
        message, original = notification.get_payload()
        self.assertTrue(
            "You are not a reviewer for the branch" in
            message.get_payload(decode=True))

    def test_processVote(self):
        """Process respects the vote command."""
        mail = self.factory.makeSignedMessage(body=' vote Abstain EBAILIWICK')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('ebailiwick', bmp.all_comments[0].vote_tag)

    def test_processVoteColon(self):
        """Process respects the vote: command."""
        mail = self.factory.makeSignedMessage(
            body=' vote: Abstain EBAILIWICK')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('ebailiwick', bmp.all_comments[0].vote_tag)

    def test_processReview(self):
        """Process respects the review command."""
        mail = self.factory.makeSignedMessage(body=' review Abstain ROAR!')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('roar!', bmp.all_comments[0].vote_tag)

    def test_processReviewColon(self):
        """Process respects the review: command."""
        mail = self.factory.makeSignedMessage(body=' review: Abstain ROAR!')
        bmp = self.factory.makeBranchMergeProposal()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        self.assertEqual(CodeReviewVote.ABSTAIN, bmp.all_comments[0].vote)
        self.assertEqual('roar!', bmp.all_comments[0].vote_tag)

    def test_processWithExistingVote(self):
        """Process respects the vote command."""
        mail = self.factory.makeSignedMessage(body=' vote Abstain EBAILIWICK')
        sender = self.factory.makePerson()
        bmp = self.factory.makeBranchMergeProposal(reviewer=sender)
        email_addr = bmp.address
        [vote] = list(bmp.votes)
        self.assertEqual(sender, vote.reviewer)
        self.assertTrue(vote.comment is None)
        switch_dbuser(config.processmail.dbuser)
        # Login the sender as they are set as the message owner.
        login_person(sender)
        self.code_handler.process(mail, email_addr, None)
        comment = bmp.all_comments[0]
        self.assertEqual(CodeReviewVote.ABSTAIN, comment.vote)
        self.assertEqual('ebailiwick', comment.vote_tag)
        [vote] = list(bmp.votes)
        self.assertEqual(sender, vote.reviewer)
        self.assertEqual(comment, vote.comment)

    def test_processmail_generates_job(self):
        """Processing mail causes an email job to be created."""
        mail = self.factory.makeSignedMessage(
            body=' vote Abstain EBAILIWICK', subject='subject')
        bmp = self.factory.makeBranchMergeProposal()
        # Pop the notifications generated by the new proposal.
        pop_notifications()
        subscriber = self.factory.makePerson()
        bmp.source_branch.subscribe(
            subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
            CodeReviewNotificationLevel.FULL, subscriber)
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        job = Store.of(bmp).find(
            BranchMergeProposalJob,
            BranchMergeProposalJob.branch_merge_proposal == bmp,
            BranchMergeProposalJob.job_type ==
            BranchMergeProposalJobType.CODE_REVIEW_COMMENT_EMAIL).one()
        self.assertIsNot(None, job)
        # Ensure the DB operations violate no constraints.
        Store.of(bmp).flush()

    def test_getBranchMergeProposal(self):
        """The correct BranchMergeProposal is returned for the address."""
        bmp = self.factory.makeBranchMergeProposal()
        switch_dbuser(config.processmail.dbuser)
        bmp2 = self.code_handler.getBranchMergeProposal(bmp.address)
        self.assertEqual(bmp, bmp2)

    def test_getBranchMergeProposalInvalid(self):
        """InvalidBranchMergeProposalAddress is raised if appropriate."""
        switch_dbuser(config.processmail.dbuser)
        self.assertRaises(InvalidBranchMergeProposalAddress,
                          self.code_handler.getBranchMergeProposal, '')
        self.assertRaises(InvalidBranchMergeProposalAddress,
                          self.code_handler.getBranchMergeProposal, 'mp+abc@')

    def test_processWithMergeDirectiveEmail(self):
        """process errors if merge@ address used."""
        message = self.factory.makeSignedMessage()
        file_alias = self.factory.makeLibraryFileAlias(
            content=message.as_string())
        # mail.incoming.handleMail also explicitly does this.
        switch_dbuser(config.processmail.dbuser)
        code_handler = CodeHandler()
        code_handler.process(message, '*****@*****.**', file_alias)
        notification = pop_notifications()[0]
        self.assertEqual(
            'Merge directive not supported.', notification['Subject'])

    def test_reviewer_with_diff(self):
        """Requesting a review with a diff works."""
        bmp = make_merge_proposal_without_reviewers(self.factory)
        preview_diff = self.factory.makePreviewDiff(merge_proposal=bmp)
        # To record the diff in the librarian.
        transaction.commit()
        eric = self.factory.makePerson(name="eric", email="*****@*****.**")
        mail = self.factory.makeSignedMessage(body=' reviewer eric')
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        [vote] = bmp.votes
        self.assertEqual(eric, vote.reviewer)

    def test_processMissingSubject(self):
        """If the subject is missing, the user is warned by email."""
        mail = self.factory.makeSignedMessage(
            body=' review abstain',
            subject='')
        bmp = self.factory.makeBranchMergeProposal()
        pop_notifications()
        email_addr = bmp.address
        switch_dbuser(config.processmail.dbuser)
        self.code_handler.process(mail, email_addr, None)
        [notification] = pop_notifications()

        self.assertEqual(
            notification['Subject'], 'Error Creating Merge Proposal')
        self.assertEqual(
            notification.get_payload(decode=True),
            'Your message did not contain a subject.  Launchpad code '
            'reviews require all\nemails to contain subject lines.  '
            'Please re-send your email including the\nsubject line.\n\n')
        self.assertEqual(notification['to'],
            mail['from'])
        self.assertEqual(0, bmp.all_comments.count())