~launchpad-pqm/launchpad/devel

8687.15.34 by Karl Fogel
Add license header blocks to .py, .zcml, and .pt files that don't have it
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
3
"""Tests for the BaseMailer class."""
4
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
5
6
__metaclass__ = type
7
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
8
from smtplib import SMTPException
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
9
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
10
from lp.services.mail.basemailer import BaseMailer
11
from lp.services.mail.sendmail import MailController
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
12
from lp.testing import TestCaseWithFactory
14612.2.1 by William Grant
format-imports on lib/. So many imports.
13
from lp.testing.layers import LaunchpadZopelessLayer
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
14
from lp.testing.mail_helpers import pop_notifications
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
15
16
17
class FakeSubscription:
18
    """Stub for use with these tests."""
19
20
    mail_header = 'pete'
21
22
    def getReason(self):
23
        return "Because"
24
25
26
class BaseMailerSubclass(BaseMailer):
27
    """Subclass of BaseMailer to avoid getting the body template."""
28
12505.5.15 by Ian Booth
Complete tests
29
    def _getBody(self, email, recipient):
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
30
        return 'body'
31
32
33
class ToAddressesUpper(BaseMailerSubclass):
34
    """Subclass of BaseMailer providing an example getToAddresses."""
35
36
    def _getToAddresses(self, recipient, email):
37
        return email.upper()
38
39
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
40
class AttachmentMailer(BaseMailerSubclass):
41
    """Subclass the test mailer to add an attachment."""
42
43
    def _addAttachments(self, ctrl, email):
44
        ctrl.addAttachment('attachment1')
45
        ctrl.addAttachment('attachment2')
46
47
48
class RaisingMailController(MailController):
49
    """A mail controller that can raise errors."""
50
51
    def raiseOnSend(self):
52
        """Make send fail for the specified email address."""
53
        self.raise_on_send = True
54
55
    def send(self, bulk=True):
56
        if getattr(self, 'raise_on_send', False):
57
            raise SMTPException('boom')
58
        else:
59
            super(RaisingMailController, self).send(bulk)
60
61
62
class RaisingMailControllerFactory:
63
    """Pretends to be a class to make raising mail controllers."""
64
65
    def __init__(self, bad_email_addr, raise_count):
66
        self.bad_email_addr = bad_email_addr
67
        self.raise_count = raise_count
68
69
    def __call__(self, *args, **kwargs):
70
        ctrl = RaisingMailController(*args, **kwargs)
71
        if ((self.bad_email_addr in kwargs['envelope_to'])
72
            and self.raise_count):
73
            self.raise_count -= 1
74
            ctrl.raiseOnSend()
75
        return ctrl
76
77
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
78
class TestBaseMailer(TestCaseWithFactory):
79
80
    layer = LaunchpadZopelessLayer
81
8771.4.5 by Aaron Bentley
Rename real_to to envelope_to.
82
    def test_generateEmail_sets_envelope_to(self):
83
        """BaseMailer.generateEmail sets MailController.envelope_to.
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
84
85
        The only item in the list is the supplied email address.
86
        """
87
        fake_to = self.factory.makePerson(email='to@example.com',
88
            displayname='Example To')
89
        recipients = {fake_to: FakeSubscription()}
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
90
        mailer = BaseMailerSubclass(
91
            'subject', None, recipients, 'from@example.com')
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
92
        ctrl = mailer.generateEmail('to@example.com', fake_to)
8771.4.5 by Aaron Bentley
Rename real_to to envelope_to.
93
        self.assertEqual(['to@example.com'], ctrl.envelope_to)
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
94
        self.assertEqual(['Example To <to@example.com>'], ctrl.to_addrs)
95
96
    def test_generateEmail_uses_getToAddresses(self):
97
        """BaseMailer.generateEmail uses getToAddresses.
98
99
        We verify this by using a subclass that provides getToAddresses
100
        as a single-item list with the uppercased email address.
101
        """
102
        fake_to = self.factory.makePerson(email='to@example.com')
103
        recipients = {fake_to: FakeSubscription()}
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
104
        mailer = ToAddressesUpper(
105
            'subject', None, recipients, 'from@example.com')
8771.4.3 by Aaron Bentley
BaseMailer sets real_email and honours _getToAddresses.
106
        ctrl = mailer.generateEmail('to@example.com', fake_to)
107
        self.assertEqual(['TO@EXAMPLE.COM'], ctrl.to_addrs)
108
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
109
    def test_generateEmail_adds_attachments(self):
110
        # BaseMailer.generateEmail calls _addAttachments.
111
        fake_to = self.factory.makePerson(email='to@example.com')
112
        recipients = {fake_to: FakeSubscription()}
113
        mailer = AttachmentMailer(
114
            'subject', None, recipients, 'from@example.com')
115
        ctrl = mailer.generateEmail('to@example.com', fake_to)
116
        self.assertEqual(2, len(ctrl.attachments))
117
118
    def test_generateEmail_force_no_attachments(self):
9320.1.2 by Tim Penhey
Fix two typos in the tests.
119
        # If BaseMailer.generateEmail is called with
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
120
        # force_no_attachments=True then attachments are not added.
121
        fake_to = self.factory.makePerson(email='to@example.com')
122
        recipients = {fake_to: FakeSubscription()}
123
        mailer = AttachmentMailer(
124
            'subject', None, recipients, 'from@example.com')
125
        ctrl = mailer.generateEmail(
126
            'to@example.com', fake_to, force_no_attachments=True)
127
        self.assertEqual(1, len(ctrl.attachments))
128
        attachment = ctrl.attachments[0]
129
        self.assertEqual(
130
            'Excessively large attachments removed.',
131
            attachment.get_payload())
132
        self.assertEqual('text/plain', attachment['Content-Type'])
133
        self.assertEqual('inline', attachment['Content-Disposition'])
134
135
    def test_sendall_single_failure_doesnt_kill_all(self):
9320.1.2 by Tim Penhey
Fix two typos in the tests.
136
        # A failure to send to a particular email address doesn't stop sending
9320.1.1 by Tim Penhey
Don't allow SMTPExceptions to propogate from BaseMailer.sendAll().
137
        # to others.
138
        recipients = {
139
            self.factory.makePerson(name='good', email='good@example.com'):
140
                FakeSubscription(),
141
            self.factory.makePerson(name='bad', email='bad@example.com'):
142
                FakeSubscription()}
143
        controller_factory = RaisingMailControllerFactory(
144
            'bad@example.com', 2)
145
        mailer = BaseMailerSubclass(
146
            'subject', None, recipients, 'from@example.com',
147
            mail_controller_class=controller_factory)
148
        mailer.sendAll()
149
        # One email is still sent.
150
        notifications = pop_notifications()
151
        self.assertEqual(1, len(notifications))
152
        self.assertEqual('Good <good@example.com>', notifications[0]['To'])
153
154
    def test_sendall_first_failure_strips_attachments(self):
155
        # If sending an email fails, we try again without the (almost
156
        # certainly) large attachment.
157
        recipients = {
158
            self.factory.makePerson(name='good', email='good@example.com'):
159
                FakeSubscription(),
160
            self.factory.makePerson(name='bad', email='bad@example.com'):
161
                FakeSubscription()}
162
        # Only raise the first time for bob.
163
        controller_factory = RaisingMailControllerFactory(
164
            'bad@example.com', 1)
165
        mailer = AttachmentMailer(
166
            'subject', None, recipients, 'from@example.com',
167
            mail_controller_class=controller_factory)
168
        mailer.sendAll()
169
        # Both emails are sent.
170
        notifications = pop_notifications()
171
        self.assertEqual(2, len(notifications))
172
        bad, good = notifications
173
        # The good email as the expected attachments.
174
        good_parts = good.get_payload()
175
        self.assertEqual(3, len(good_parts))
176
        self.assertEqual(
177
            'attachment1', good_parts[1].get_payload(decode=True))
178
        self.assertEqual(
179
            'attachment2', good_parts[2].get_payload(decode=True))
180
        # The bad email has the normal attachments stripped off and replaced
181
        # with the text.
182
        bad_parts = bad.get_payload()
183
        self.assertEqual(2, len(bad_parts))
184
        self.assertEqual(
185
            'Excessively large attachments removed.',
186
            bad_parts[1].get_payload(decode=True))