Example #1
0
    def test_no_detectable_bounce_addresses(self):
        # A bounce message was received, but no addresses could be detected.
        # A message will be logged in the bounce log though, and the message
        # can be forwarded to someone who can do something about it.
        self._mlist.forward_unrecognized_bounces_to = (
            UnrecognizedBounceDisposition.site_owner)
        bogus = message_from_string("""\
From: [email protected]
To: [email protected]
Message-Id: <third>

""")
        self._bounceq.enqueue(bogus, self._msgdata)
        mark = LogFileMark('mailman.bounce')
        self._runner.run()
        self.assertEqual(len(get_queue_messages('bounces')), 0)
        events = list(self._processor.events)
        self.assertEqual(len(events), 0)
        line = mark.readline()
        self.assertEqual(
            line[-51:-1],
            'Bounce message w/no discernable addresses: <third>')
        # Here's the forwarded message to the site owners.
        forwards = get_queue_messages('virgin')
        self.assertEqual(len(forwards), 1)
        self.assertEqual(forwards[0].msg['to'], '*****@*****.**')
Example #2
0
 def setUp(self):
     self._mlist = create_list("*****@*****.**")
     anne = getUtility(IUserManager).create_address("*****@*****.**", "Anne Person")
     self._token, token_owner, member = IRegistrar(self._mlist).register(anne)
     self._command = Confirm()
     # Clear the virgin queue.
     get_queue_messages("virgin")
Example #3
0
 def setUp(self):
     self._mlist = create_list('*****@*****.**')
     self._token = getUtility(IRegistrar).register(
         self._mlist, '*****@*****.**', 'Anne Person')
     self._command = Confirm()
     # Clear the virgin queue.
     get_queue_messages('virgin')
Example #4
0
    def test_verp_probe_bounce(self):
        # A VERP probe bounced.  The primary difference here is that the
        # registered bounce event will have a different context.  The
        # Message-Id will be different too, because of the way we're
        # simulating the probe bounce.
        #
        # Start be simulating a probe bounce.
        send_probe(self._member, self._msg)
        message = get_queue_messages('virgin')[0].msg
        bounce = message_from_string("""\
To: {0}
From: [email protected]
Message-Id: <second>

""".format(message['From']))
        self._bounceq.enqueue(bounce, self._msgdata)
        self._runner.run()
        self.assertEqual(len(get_queue_messages('bounces')), 0)
        events = list(self._processor.events)
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0].email, '*****@*****.**')
        self.assertEqual(events[0].list_id, 'test.example.com')
        self.assertEqual(events[0].message_id, '<second>')
        self.assertEqual(events[0].context, BounceContext.probe)
        self.assertEqual(events[0].processed, False)
Example #5
0
    def test_nonverp_detectable_fatal_bounce(self):
        # Here's a bounce that is not VERPd, but which has a bouncing address
        # that can be parsed from a known bounce format.  DSN is as good as
        # any, but we'll make the parsed address different for the fun of it.
        dsn = message_from_string(
            """\
From: [email protected]
To: [email protected]
Message-Id: <first>
Content-Type: multipart/report; report-type=delivery-status; boundary=AAA
MIME-Version: 1.0

--AAA
Content-Type: message/delivery-status

Action: fail
Original-Recipient: rfc822; [email protected]

--AAA--
"""
        )
        self._bounceq.enqueue(dsn, self._msgdata)
        self._runner.run()
        get_queue_messages("bounces", expected_count=0)
        events = list(self._processor.events)
        self.assertEqual(len(events), 1)
        self.assertEqual(events[0].email, "*****@*****.**")
        self.assertEqual(events[0].list_id, "test.example.com")
        self.assertEqual(events[0].message_id, "<first>")
        self.assertEqual(events[0].context, BounceContext.normal)
        self.assertEqual(events[0].processed, False)
Example #6
0
 def test_inject(self):
     # Injecting a message leaves the message in the queue.
     starting_messages = get_queue_messages('bad')
     self.assertEqual(len(starting_messages), 0)
     content, response = call_api('http://localhost:9001/3.0/queues/bad', {
         'list_id': 'test.example.com',
         'text': TEXT})
     self.assertEqual(response.status, 201)
     location = response['location']
     filebase = location.split('/')[-1]
     # The message is in the 'bad' queue.
     content, response = call_api('http://localhost:9001/3.0/queues/bad')
     files = content['files']
     self.assertEqual(len(files), 1)
     self.assertEqual(files[0], filebase)
     # Verify the files directly.
     files = list(config.switchboards['bad'].files)
     self.assertEqual(len(files), 1)
     self.assertEqual(files[0], filebase)
     # Verify the content.
     items = get_queue_messages('bad')
     self.assertEqual(len(items), 1)
     msg = items[0].msg
     # Remove some headers that get added by Mailman.
     del msg['date']
     self.assertEqual(msg['message-id-hash'],
                      'MS6QLWERIJLGCRF44J7USBFDELMNT2BW')
     del msg['message-id-hash']
     del msg['x-message-id-hash']
     self.assertMultiLineEqual(msg.as_string(), TEXT)
Example #7
0
    def test_send_digest_to_one_missing_and_one_existing_list(self):
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 1

""")
        self._handler.process(self._mlist, msg, {})
        del msg['subject']
        msg['subject'] = 'message 2'
        self._handler.process(self._mlist, msg, {})
        # There are no digests already being sent, but the ant mailing list
        # does have a digest mbox collecting messages.
        get_queue_messages('digest', expected_count=0)
        mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
        self.assertGreater(os.path.getsize(mailbox_path), 0)
        args = FakeArgs()
        args.send = True
        args.lists.extend(('ant.example.com', 'bee.example.com'))
        stderr = StringIO()
        with patch('mailman.commands.cli_digests.sys.stderr', stderr):
            self._command.process(args)
        self._runner.run()
        # The warning was printed to stderr.
        self.assertEqual(stderr.getvalue(),
                         'No such list found: bee.example.com\n')
        # But ant's digest was still prepared.
        self.assertFalse(os.path.exists(mailbox_path))
        items = get_queue_messages('virgin', expected_count=1)
        digest_contents = str(items[0].msg)
        self.assertIn('Subject: message 1', digest_contents)
        self.assertIn('Subject: message 2', digest_contents)
Example #8
0
    def test_send_one_digest_to_missing_fqdn_listname(self):
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 1

""")
        self._handler.process(self._mlist, msg, {})
        del msg['subject']
        msg['subject'] = 'message 2'
        self._handler.process(self._mlist, msg, {})
        # There are no digests already being sent, but the ant mailing list
        # does have a digest mbox collecting messages.
        get_queue_messages('digest', expected_count=0)
        mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
        self.assertGreater(os.path.getsize(mailbox_path), 0)
        args = FakeArgs()
        args.send = True
        args.lists.append('*****@*****.**')
        stderr = StringIO()
        with patch('mailman.commands.cli_digests.sys.stderr', stderr):
            self._command.process(args)
        self._runner.run()
        # The warning was printed to stderr.
        self.assertEqual(stderr.getvalue(),
                         'No such list found: [email protected]\n')
        # And no digest was prepared.
        self.assertGreater(os.path.getsize(mailbox_path), 0)
        get_queue_messages('virgin', expected_count=0)
Example #9
0
    def test_welcome_message_after_confirmation(self):
        # Confirmations with a welcome message set.
        self._mlist.send_welcome_message = True
        self._mlist.welcome_message_uri = 'mailman:///welcome.txt'
        # 'confirm' in the Subject and in the To header should not try to
        # confirm the token twice.
        #
        # Clear out the virgin queue so that the test below only sees the
        # reply to the confirmation message.
        get_queue_messages('virgin')
        subject = 'Re: confirm {0}'.format(self._token)
        to = 'test-confirm+{0}@example.com'.format(self._token)
        msg = mfs("""\
From: Anne Person <*****@*****.**>

""")
        msg['Subject'] = subject
        msg['To'] = to
        self._commandq.enqueue(msg, dict(listid='test.example.com',
                                         subaddress='confirm'))
        self._runner.run()
        # Now there's a email command notification and a welcome message.  All
        # we care about for this test is the welcome message.
        messages = get_queue_messages('virgin', sort_on='subject')
        self.assertEqual(len(messages), 2)
        message = messages[1].msg
        self.assertEqual(str(message['subject']),
                         'Welcome to the "Test" mailing list')
Example #10
0
    def test_confirm_with_no_command_in_utf8_body(self):
        # Clear out the virgin queue so that the test below only sees the
        # reply to the confirmation message.
        get_queue_messages('virgin')
        subject = 'Re: confirm {0}'.format(self._token)
        to = 'test-confirm+{0}@example.com'.format(self._token)
        msg = mfs("""\
From: Anne Person <*****@*****.**>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

Franziskanerstra=C3=9Fe
""")
        msg['Subject'] = subject
        msg['To'] = to
        self._commandq.enqueue(msg, dict(listid='test.example.com'))
        self._runner.run()
        # Anne is now a confirmed member so her user record and email address
        # should exist in the database.
        manager = getUtility(IUserManager)
        user = manager.get_user('*****@*****.**')
        address = list(user.addresses)[0]
        self.assertEqual(address.email, '*****@*****.**')
        self.assertEqual(address.verified_on, datetime(2005, 8, 1, 7, 49, 23))
        address = manager.get_address('*****@*****.**')
        self.assertEqual(address.email, '*****@*****.**')
        messages = get_queue_messages('virgin')
        self.assertEqual(len(messages), 1)
        self.assertEqual(messages[0].msgdata['recipients'],
                         set(['*****@*****.**']))
Example #11
0
 def test_digest_messages(self):
     # In LP: #1130697, the digest runner creates MIME digests using the
     # stdlib MIMEMutlipart class, however this class does not have the
     # extended attributes we require (e.g. .sender).  The fix is to use a
     # subclass of MIMEMultipart and our own Message subclass; this adds
     # back the required attributes.  (LP: #1130696)
     #
     # Start by creating the raw ingredients for the digests.  This also
     # runs the digest runner, thus producing the digest messages into the
     # virgin queue.
     make_digest_messages(self._mlist)
     # Run the virgin queue processor, which runs the cook-headers and
     # to-outgoing handlers.  This should produce no error.
     error_log = LogFileMark('mailman.error')
     runner = make_testable_runner(VirginRunner, 'virgin')
     runner.run()
     error_text = error_log.read()
     self.assertEqual(len(error_text), 0, error_text)
     self.assertEqual(len(get_queue_messages('shunt')), 0)
     messages = get_queue_messages('out')
     self.assertEqual(len(messages), 2)
     # Which one is the MIME digest?
     mime_digest = None
     for bag in messages:
         if bag.msg.get_content_type() == 'multipart/mixed':
             assert mime_digest is None, 'Found two MIME digests'
             mime_digest = bag.msg
     # The cook-headers handler ran.
     self.assertIn('x-mailman-version', mime_digest)
     self.assertEqual(mime_digest['precedence'], 'list')
     # The list's -request address is the original sender.
     self.assertEqual(bag.msgdata['original_sender'],
                      '*****@*****.**')
Example #12
0
 def test_dispose_bounce(self):
     self._mlist.filter_action = FilterAction.reject
     with self.assertRaises(RejectMessage) as cm:
         mime_delete.dispose(self._mlist, self._msg, {}, 'rejecting')
     self.assertEqual(cm.exception.message, 'rejecting')
     # There should be no messages in the 'bad' queue.
     get_queue_messages('bad', expected_count=0)
Example #13
0
 def test_probe_sends_one_message(self):
     # send_probe() places one message in the virgin queue.
     items = get_queue_messages('virgin')
     self.assertEqual(len(items), 0)
     send_probe(self._member, self._msg)
     items = get_queue_messages('virgin')
     self.assertEqual(len(items), 1)
Example #14
0
 def test_reject(self):
     # POST to the request to reject it.  This leaves a bounce message in
     # the virgin queue.
     with transaction():
         token, token_owner, member = self._registrar.register(self._anne)
     # Anne's subscription request got held.
     self.assertIsNone(member)
     # Clear out the virgin queue, which currently contains the
     # confirmation message sent to Anne.
     get_queue_messages('virgin')
     url = 'http://*****:*****@example.com/requests/{}'
     content, response = call_api(url.format(token), dict(
         action='reject',
         ))
     self.assertEqual(response.status, 204)
     # Anne is not a member.
     self.assertIsNone(self._mlist.members.get_member('*****@*****.**'))
     # The request URL no longer exists.
     with self.assertRaises(HTTPError) as cm:
         call_api(url.format(token), dict(
             action='reject',
             ))
     self.assertEqual(cm.exception.code, 404)
     # And the rejection message to Anne is now in the virgin queue.
     items = get_queue_messages('virgin')
     self.assertEqual(len(items), 1)
     message = items[0].msg
     self.assertEqual(message['From'], '*****@*****.**')
     self.assertEqual(message['To'], '*****@*****.**')
     self.assertEqual(message['Subject'],
                      'Request to mailing list "Ant" rejected')
Example #15
0
    def test_send_one_digest_by_fqdn_listname(self):
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 1

""")
        self._handler.process(self._mlist, msg, {})
        del msg['subject']
        msg['subject'] = 'message 2'
        self._handler.process(self._mlist, msg, {})
        # There are no digests already being sent, but the ant mailing list
        # does have a digest mbox collecting messages.
        get_queue_messages('digest', expected_count=0)
        mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
        self.assertGreater(os.path.getsize(mailbox_path), 0)
        args = FakeArgs()
        args.send = True
        args.lists.append('*****@*****.**')
        self._command.process(args)
        self._runner.run()
        # Now, there's no digest mbox and there's a plaintext digest in the
        # outgoing queue.
        self.assertFalse(os.path.exists(mailbox_path))
        items = get_queue_messages('virgin', expected_count=1)
        digest_contents = str(items[0].msg)
        self.assertIn('Subject: message 1', digest_contents)
        self.assertIn('Subject: message 2', digest_contents)
Example #16
0
    def test_nonverp_detectable_nonfatal_bounce(self):
        # Here's a bounce that is not VERPd, but which has a bouncing address
        # that can be parsed from a known bounce format.  The bounce is
        # non-fatal so no bounce event is registered.
        dsn = message_from_string(
            """\
From: [email protected]
To: [email protected]
Message-Id: <first>
Content-Type: multipart/report; report-type=delivery-status; boundary=AAA
MIME-Version: 1.0

--AAA
Content-Type: message/delivery-status

Action: delayed
Original-Recipient: rfc822; [email protected]

--AAA--
"""
        )
        self._bounceq.enqueue(dsn, self._msgdata)
        self._runner.run()
        get_queue_messages("bounces", expected_count=0)
        events = list(self._processor.events)
        self.assertEqual(len(events), 0)
Example #17
0
    def test_confirm_with_utf8_body(self):
        # Clear out the virgin queue so that the test below only sees the
        # reply to the confirmation message.
        get_queue_messages('virgin')
        subject = 'Re: confirm {0}'.format(self._token)
        to = 'test-confirm+{0}@example.com'.format(self._token)
        msg = mfs("""\
From: Anne Person <*****@*****.**>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

* test-confirm+90bf6ef335d92cfbbe540a5c9ebfecb107a48e48@example.com <=
test-confirm+90bf6ef335d92cfbbe540a5c9ebfecb107a48e48@example.com>:
> Email Address Registration Confirmation
>=20
> Hello, this is the GNU Mailman server at example.com.
>=20
> We have received a registration request for the email address
>=20
>     [email protected]
>=20
> Before you can start using GNU Mailman at this site, you must first con=
firm
> that this is your email address.  You can do this by replying to this m=
essage,
> keeping the Subject header intact.  Or you can visit this web page
>=20
>     http://example.com/confirm/90bf6ef335d92cfbbe540a5c9ebfecb107a48e48
>=20
> If you do not wish to register this email address simply disregard this
> message.  If you think you are being maliciously subscribed to the list=
, or
> have any other questions, you may contact
>=20
>     [email protected]

--=20

Franziskanerstra=C3=9Fe
""")
        msg['Subject'] = subject
        msg['To'] = to
        self._commandq.enqueue(msg, dict(listid='test.example.com'))
        self._runner.run()
        # Anne is now a confirmed member so her user record and email address
        # should exist in the database.
        manager = getUtility(IUserManager)
        user = manager.get_user('*****@*****.**')
        address = list(user.addresses)[0]
        self.assertEqual(address.email, '*****@*****.**')
        self.assertEqual(address.verified_on, datetime(2005, 8, 1, 7, 49, 23))
        address = manager.get_address('*****@*****.**')
        self.assertEqual(address.email, '*****@*****.**')
        messages = get_queue_messages('virgin')
        self.assertEqual(len(messages), 1)
        self.assertEqual(messages[0].msgdata['recipients'],
                         set(['*****@*****.**']))
Example #18
0
 def test_does_no_processing(self):
     # If the mailing list does no bounce processing, the messages are
     # simply discarded.
     self._mlist.process_bounces = False
     self._bounceq.enqueue(self._msg, self._msgdata)
     self._runner.run()
     get_queue_messages("bounces", expected_count=0)
     self.assertEqual(len(list(self._processor.events)), 0)
Example #19
0
 def test_no_welcome_message(self):
     # When configured not to send a welcome message, none is sent.
     self._mlist.send_welcome_message = False
     status = self._command.process(
         self._mlist, Message(), {}, (self._token,), Results())
     self.assertEqual(status, ContinueProcessing.yes)
     # There will be no messages in the queue.
     get_queue_messages('virgin', expected_count=0)
Example #20
0
 def test_inject_message_to_queue(self):
     # Explicitly use a different queue.
     inject_message(self.mlist, self.msg, switchboard='virgin')
     get_queue_messages('in', expected_count=0)
     items = get_queue_messages('virgin', expected_count=1)
     self.assertMultiLineEqual(items[0].msg.as_string(),
                               self.msg.as_string())
     self.assertEqual(items[0].msgdata['listid'], 'test.example.com')
     self.assertEqual(items[0].msgdata['original_size'],
                      len(self.msg.as_string()))
Example #21
0
 def test_dispose_non_preservable(self):
     # Two actions can happen here, depending on a site-wide setting.  If
     # the site owner has indicated that filtered messages cannot be
     # preserved, then this is the same as discarding them.
     self._mlist.filter_action = FilterAction.preserve
     with self.assertRaises(DiscardMessage) as cm:
         mime_delete.dispose(self._mlist, self._msg, {}, 'not preserved')
     self.assertEqual(cm.exception.message, 'not preserved')
     # There should be no messages in the 'bad' queue.
     get_queue_messages('bad', expected_count=0)
Example #22
0
 def test_send_no_digest_ready(self):
     # If no messages have been sent through the mailing list, no digest
     # can be sent.
     mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
     self.assertFalse(os.path.exists(mailbox_path))
     args = FakeArgs()
     args.send = True
     args.lists.append('ant.example.com')
     self._command.process(args)
     self._runner.run()
     get_queue_messages('virgin', expected_count=0)
Example #23
0
 def test_inject_message_to_queue(self):
     # Explicitly use a different queue.
     inject_message(self.mlist, self.msg, switchboard='virgin')
     items = get_queue_messages('in')
     self.assertEqual(len(items), 0)
     items = get_queue_messages('virgin')
     self.assertEqual(len(items), 1)
     self.eq(items[0].msg.as_string(), self.msg.as_string())
     self.assertEqual(items[0].msgdata['listname'], '*****@*****.**')
     self.assertEqual(items[0].msgdata['original_size'],
                      len(self.msg.as_string()))
Example #24
0
 def test_dont_send_digest_under_threshold(self):
     # Put a few messages in the digest.
     self._to_digest(3)
     # Set the size threshold high enough to not trigger a send.
     self._mlist.digest_size_threshold = 100
     maybe_send_digest_now(self._mlist)
     self._runner.run()
     # A digest is still being collected, but none have been sent.
     get_queue_messages('digest', expected_count=0)
     self.assertGreater(os.path.getsize(self._mailbox_path), 0)
     self.assertLess(os.path.getsize(self._mailbox_path), 100 * 1024.0)
     get_queue_messages('virgin', expected_count=0)
Example #25
0
 def test_get_moderator_approval_no_notifications(self):
     # When the subscription is held for moderator approval, and the list
     # is so configured, a notification is sent to the list moderators.
     self._mlist.admin_immed_notify = False
     self._mlist.subscription_policy = SubscriptionPolicy.moderate
     anne = self._user_manager.create_address(self._anne)
     workflow = SubscriptionWorkflow(self._mlist, anne,
                                     pre_verified=True,
                                     pre_confirmed=True)
     # Consume the entire state machine.
     list(workflow)
     get_queue_messages('virgin', expected_count=0)
Example #26
0
 def test_maybe_forward_discard(self):
     # When forward_unrecognized_bounces_to is set to discard, no bounce
     # messages are forwarded.
     self._mlist.forward_unrecognized_bounces_to = (
         UnrecognizedBounceDisposition.discard)
     # The only artifact of this call is a log file entry.
     mark = LogFileMark('mailman.bounce')
     maybe_forward(self._mlist, self._msg)
     get_queue_messages('virgin', expected_count=0)
     line = mark.readline()
     self.assertEqual(
         line[-40:-1],
         'Discarding unrecognized bounce: <first>')
Example #27
0
 def test_verp_detection(self):
     # When we get a VERPd bounce, and we're doing processing, a bounce
     # event will be registered.
     self._bounceq.enqueue(self._msg, self._msgdata)
     self._runner.run()
     get_queue_messages("bounces", expected_count=0)
     events = list(self._processor.events)
     self.assertEqual(len(events), 1)
     self.assertEqual(events[0].email, "*****@*****.**")
     self.assertEqual(events[0].list_id, "test.example.com")
     self.assertEqual(events[0].message_id, "<first>")
     self.assertEqual(events[0].context, BounceContext.normal)
     self.assertEqual(events[0].processed, False)
Example #28
0
 def test_send_digest_over_threshold(self):
     # Put a few messages in the digest.
     self._to_digest(3)
     # Set the size threshold low enough to trigger a send.
     self._mlist.digest_size_threshold = 0.1
     maybe_send_digest_now(self._mlist)
     self._runner.run()
     # There are no digests in flight now, and a single digest message has
     # been sent.
     get_queue_messages('digest', expected_count=0)
     self.assertFalse(os.path.exists(self._mailbox_path))
     items = get_queue_messages('virgin', expected_count=1)
     digest_contents = str(items[0].msg)
     self.assertIn('Subject: message 1', digest_contents)
     self.assertIn('Subject: message 2', digest_contents)
Example #29
0
    def test_mailing_list_with_subaddress_command(self):
        # Like above, but we can still send a command to the mailing list.
        with transaction():
            create_list('*****@*****.**')
        self._lmtp.sendmail('*****@*****.**',
                            ['*****@*****.**'], """\
From: [email protected]
To: [email protected]
Message-ID: <ant>
Subject: This will be recognized as a join command.

""")
        # The message is in the command queue but not the incoming queue.
        get_queue_messages('in', expected_count=0)
        get_queue_messages('command', expected_count=1)
Example #30
0
    def test_mailing_list_with_subaddress(self):
        # A mailing list with a subaddress in its name should be recognized as
        # the mailing list, not as a command.
        with transaction():
            create_list('*****@*****.**')
        self._lmtp.sendmail('*****@*****.**', ['*****@*****.**'], """\
From: [email protected]
To: [email protected]
Message-ID: <ant>
Subject: This should not be recognized as a join command

""")
        # The message is in the incoming queue but not the command queue.
        get_queue_messages('in', expected_count=1)
        get_queue_messages('command', expected_count=0)
Example #31
0
    def test_mime_digest_format(self):
        # Make sure that the format of the MIME digest is as expected.
        self._mlist.digest_size_threshold = 0.6
        self._mlist.volume = 1
        self._mlist.next_digest_number = 1
        self._mlist.send_welcome_message = False
        # Subscribe some users receiving digests.
        anne = subscribe(self._mlist, 'Anne')
        anne.preferences.delivery_mode = DeliveryMode.mime_digests
        bart = subscribe(self._mlist, 'Bart')
        bart.preferences.delivery_mode = DeliveryMode.plaintext_digests
        # Fill the digest.
        process = config.handlers['to-digest'].process
        size = 0
        for i in range(1, 5):
            text = Template("""\
From: [email protected]
To: [email protected]
Subject: Test message $i
List-Post: <*****@*****.**>

Here is message $i
""").substitute(i=i)
            msg = message_from_string(text)
            process(self._mlist, msg, {})
            size += len(text)
            if size >= self._mlist.digest_size_threshold * 1024:
                break
        # Run the digest runner to create the MIME and RFC 1153 digests.
        runner = make_testable_runner(DigestRunner)
        runner.run()
        items = get_queue_messages('virgin', expected_count=2)
        # Find the MIME one.
        mime_digest = None
        for item in items:
            if item.msg.is_multipart():
                assert mime_digest is None, 'We got two MIME digests'
                mime_digest = item.msg
        fp = StringIO()
        # Verify the structure is what we expect.
        structure(mime_digest, fp)
        self.assertMultiLineEqual(
            fp.getvalue(), """\
multipart/mixed
    text/plain
    text/plain
    multipart/digest
        message/rfc822
            text/plain
        message/rfc822
            text/plain
        message/rfc822
            text/plain
        message/rfc822
            text/plain
    text/plain
""")
        # Verify the Message: headers
        for i in range(1, 5):
            body = mime_digest.get_payload(2).get_payload(i - 1).get_payload(0)
            self.assertEqual(body['message'], str(i))
Example #32
0
 def test_no_welcome_message_to_owners(self):
     # Welcome messages go only to mailing list members, not to owners.
     subscribe(self._mlist, 'Anne', MemberRole.owner, '*****@*****.**')
     # There is no welcome message in the virgin queue.
     messages = get_queue_messages('virgin')
     self.assertEqual(len(messages), 0)
Example #33
0
    def test_send_digests_for_all_lists(self):
        # Populate ant's digest.
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 1

""")
        self._handler.process(self._mlist, msg, {})
        del msg['subject']
        msg['subject'] = 'message 2'
        self._handler.process(self._mlist, msg, {})
        # Create the second list.
        bee = create_list('*****@*****.**')
        bee.digests_enabled = True
        bee.digest_size_threshold = 100000
        bee.send_welcome_message = False
        member = subscribe(bee, 'Bart')
        member.preferences.delivery_mode = DeliveryMode.plaintext_digests
        # Populate bee's digest.
        msg = mfs("""\
To: [email protected]
From: [email protected]
Subject: message 3

""")
        self._handler.process(bee, msg, {})
        del msg['subject']
        msg['subject'] = 'message 4'
        self._handler.process(bee, msg, {})
        # There are no digests for either list already being sent, but the
        # mailing lists do have a digest mbox collecting messages.
        ant_mailbox_path = os.path.join(self._mlist.data_path, 'digest.mmdf')
        self.assertGreater(os.path.getsize(ant_mailbox_path), 0)
        # Check bee's digest.
        bee_mailbox_path = os.path.join(bee.data_path, 'digest.mmdf')
        self.assertGreater(os.path.getsize(bee_mailbox_path), 0)
        # Both.
        get_queue_messages('digest', expected_count=0)
        # Process all mailing list digests by not setting any arguments.
        args = FakeArgs()
        args.send = True
        self._command.process(args)
        self._runner.run()
        # Now, neither list has a digest mbox and but there are plaintext
        # digest in the outgoing queue for both.
        self.assertFalse(os.path.exists(ant_mailbox_path))
        self.assertFalse(os.path.exists(bee_mailbox_path))
        items = get_queue_messages('virgin', expected_count=2)
        # Figure out which digest is going to ant and which to bee.
        if items[0].msg['to'] == '*****@*****.**':
            ant = items[0].msg
            bee = items[1].msg
        else:
            assert items[0].msg['to'] == '*****@*****.**'
            ant = items[1].msg
            bee = items[0].msg
        # Check ant's digest.
        digest_contents = str(ant)
        self.assertIn('Subject: message 1', digest_contents)
        self.assertIn('Subject: message 2', digest_contents)
        # Check bee's digest.
        digest_contents = str(bee)
        self.assertIn('Subject: message 3', digest_contents)
        self.assertIn('Subject: message 4', digest_contents)
Example #34
0
 def test_message_put_in_outgoing_queue(self):
     self._retryq.enqueue(self._msg, self._msgdata)
     self._runner.run()
     self.assertEqual(len(get_queue_messages('out')), 1)
Example #35
0
    def test_confirm_then_moderate_workflow(self):
        # Issue #114 describes a problem when confirming the moderation email.
        self._mlist.subscription_policy = (
            SubscriptionPolicy.confirm_then_moderate)
        bart = getUtility(IUserManager).create_address('*****@*****.**',
                                                       'Bart Person')
        # Clear any previously queued confirmation messages.
        get_queue_messages('virgin')
        self._token, token_owner, member = ISubscriptionManager(
            self._mlist).register(bart)
        # There should now be one email message in the virgin queue, i.e. the
        # confirmation message sent to Bart.
        items = get_queue_messages('virgin', expected_count=1)
        msg = items[0].msg
        # Confirmations come first, so this one goes to the subscriber.
        self.assertEqual(msg['to'], '*****@*****.**')
        confirm, token = str(msg['subject']).split()
        self.assertEqual(confirm, 'confirm')
        self.assertEqual(token, self._token)
        # Craft a confirmation response with the expected tokens.
        user_response = Message()
        user_response['From'] = '*****@*****.**'
        user_response['To'] = 'test-confirm+{}@example.com'.format(token)
        user_response['Subject'] = 'Re: confirm {}'.format(token)
        user_response.set_payload('')
        # Process the message through the command runner.
        config.switchboards['command'].enqueue(user_response,
                                               listid='test.example.com')
        make_testable_runner(CommandRunner, 'command').run()
        # There are now two messages in the virgin queue.  One is going to the
        # subscriber containing the results of their confirmation message, and
        # the other is to the moderators informing them that they need to
        # handle the moderation queue.
        items = get_queue_messages('virgin', expected_count=2)
        if items[0].msg['to'] == '*****@*****.**':
            results = items[0].msg
            moderator_msg = items[1].msg
        else:
            results = items[1].msg
            moderator_msg = items[0].msg
        # Check the moderator message first.
        self.assertEqual(moderator_msg['to'], '*****@*****.**')
        self.assertEqual(
            moderator_msg['subject'],
            'New subscription request to Test from [email protected]')
        lines = moderator_msg.get_payload().splitlines()
        self.assertEqual(lines[-2].strip(),
                         'For:  Bart Person <*****@*****.**>')
        self.assertEqual(lines[-1].strip(), 'List: [email protected]')
        # Now check the results message.
        self.assertEqual(str(results['subject']),
                         'The results of your email commands')
        self.assertMultiLineEqual(
            results.get_payload(), """\
The results of your email command are provided below.

- Original message details:
From: [email protected]
Subject: Re: confirm {}
Date: n/a
Message-ID: n/a

- Results:
Confirmed

- Done.
""".format(token))
Example #36
0
    def test_multilingual_digest(self):
        # When messages come in with a content-type character set different
        # than that of the list's preferred language, recipients will get an
        # internationalized digest.
        #
        # Subscribe some users receiving digests.
        anne = subscribe(self._mlist, 'Anne')
        anne.preferences.delivery_mode = DeliveryMode.mime_digests
        bart = subscribe(self._mlist, 'Bart')
        bart.preferences.delivery_mode = DeliveryMode.plaintext_digests
        msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: =?iso-2022-jp?b?GyRCMGxIVhsoQg==?=
MIME-Version: 1.0
Content-Type: text/plain; charset=iso-2022-jp
Content-Transfer-Encoding: 7bit

\x1b$B0lHV\x1b(B
""")
        self._process(self._mlist, msg, {})
        self._runner.run()
        # There are two digests in the virgin queue; one is the MIME digest
        # and the other is the RFC 1153 digest.
        items = get_queue_messages('virgin', expected_count=2)
        if items[0].msg.is_multipart():
            mime, rfc1153 = items[0].msg, items[1].msg
        else:
            rfc1153, mime = items[0].msg, items[1].msg
        # The MIME version contains a mix of French and Japanese.  The digest
        # chrome added by Mailman is in French.
        self.assertEqual(
            mime['subject'].encode(),
            '=?iso-8859-1?q?Groupe_Test=2C_Vol_1=2C_Parution_1?=')
        self.assertEqual(str(mime['subject']),
                         'Groupe Test, Vol 1, Parution 1')
        # The first subpart contains the iso-8859-1 masthead.
        masthead = mime.get_payload(0).get_payload(
            decode=True).decode('iso-8859-1')
        self.assertMultiLineEqual(masthead.splitlines()[0],
                                  'Envoyez vos messages pour la liste Test à')
        # The second subpart contains the utf-8 table of contents.
        self.assertEqual(
            mime.get_payload(1)['content-description'],
            "Today's Topics (1 messages)")
        toc = mime.get_payload(1).get_payload(decode=True).decode('utf-8')
        self.assertMultiLineEqual(toc.splitlines()[0], 'Thèmes du jour :')
        # The third subpart is a multipart/digest part and its first subpart
        # contains the posted message in Japanese.
        self.assertEqual(
            mime.get_payload(2).get_content_type(), 'multipart/digest')
        self.assertEqual(
            mime.get_payload(2).get_payload(0).get_content_type(),
            'message/rfc822')
        post = mime.get_payload(2).get_payload(0).get_payload(0)
        self.assertEqual(post['subject'], '=?iso-2022-jp?b?GyRCMGxIVhsoQg==?=')
        # Compare the bytes so that this module doesn't contain string
        # literals in multiple incompatible character sets.
        self.assertEqual(post.get_payload(decode=True), b'\x1b$B0lHV\x1b(B\n')
        # The RFC 1153 digest will have the same subject, but its payload will
        # be recast into utf-8.
        self.assertEqual(str(rfc1153['subject']),
                         'Groupe Test, Vol 1, Parution 1')
        self.assertEqual(rfc1153.get_charset(), 'utf-8')
        lines = rfc1153.get_payload(decode=True).decode('utf-8').splitlines()
        self.assertEqual(lines[0], 'Envoyez vos messages pour la liste Test à')
Example #37
0
 def test_no_precedence_header(self):
     # Probe messages should not have a Precedence header (LP: #808821).
     send_probe(self._member, self._msg)
     items = get_queue_messages('virgin', expected_count=1)
     self.assertIsNone(items[0].msg['precedence'])
Example #38
0
 def test_probe_sends_one_message(self):
     # send_probe() places one message in the virgin queue.  We start out
     # with no messages in the queue.
     get_queue_messages('virgin', expected_count=0)
     send_probe(self._member, self._msg)
     get_queue_messages('virgin', expected_count=1)
Example #39
0
 def test_inject_message_with_recipients(self):
     # Explicit recipients end up in the metadata.
     recipients = ['*****@*****.**', '*****@*****.**']
     inject_message(self.mlist, self.msg, recipients)
     items = get_queue_messages('in', expected_count=1)
     self.assertEqual(items[0].msgdata['recipients'], recipients)
Example #40
0
 def test_no_sender(self):
     # The message won't be bounced if it has no discernible sender.
     del self._msg['from']
     bounce_message(self._mlist, self._msg)
     # Nothing in the virgin queue means nothing's been bounced.
     get_queue_messages('virgin', expected_count=0)
Example #41
0
 def test_inject_message_with_keywords(self):
     # Keyword arguments are copied into the metadata.
     inject_message(self.mlist, self.msg, foo='yes', bar='no')
     items = get_queue_messages('in', expected_count=1)
     self.assertEqual(items[0].msgdata['foo'], 'yes')
     self.assertEqual(items[0].msgdata['bar'], 'no')
Example #42
0
 def test_no_precedence_header(self):
     # Probe messages should not have a Precedence header (LP: #808821).
     send_probe(self._member, self._msg)
     message = get_queue_messages('virgin')[0].msg
     self.assertEqual(message['precedence'], None)
Example #43
0
 def test_message_put_in_outgoing_queue(self):
     self._retryq.enqueue(self._msg, self._msgdata)
     self._runner.run()
     get_queue_messages('out', expected_count=1)
Example #44
0
 def test_no_welcome_message_to_moderators(self):
     # Welcome messages go only to mailing list members, not to moderators.
     subscribe(self._mlist, 'Anne', MemberRole.moderator,
               '*****@*****.**')
     # There is no welcome message in the virgin queue.
     get_queue_messages('virgin', expected_count=0)
Example #45
0
 def test_no_reason(self):
     # There may be no moderation reasons.
     process_chain(self._mlist, self._msg, {}, start_chain='reject')
     bounces = get_queue_messages('virgin', expected_count=1)
     payload = bounces[0].msg.get_payload(0).as_string()
     self.assertIn('No bounce details are available', payload)