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'], '*****@*****.**')
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")
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')
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)
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)
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)
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)
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)
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')
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(['*****@*****.**']))
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'], '*****@*****.**')
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)
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)
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')
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)
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)
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(['*****@*****.**']))
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)
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)
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()))
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)
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)
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()))
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)
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)
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>')
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)
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)
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)
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)
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))
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)
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)
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)
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))
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 à')
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'])
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)
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)
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)
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')
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)
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)
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)
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)