def test_mq_runner_can_load_plugins(self): queue_basedir = os.path.join(self.fs.root, 'mailqueue') create_maildir_directories(queue_basedir) inject_example_message(queue_basedir) mock_fn = mock.MagicMock(return_value=MQAction.DISCARD, spec={}) signal_map = {MQSignal.delivery_failed: mock_fn} fake_plugin = create_fake_plugin(signal_map) inject_plugin_into_working_set('testplugin', fake_plugin) config_path = create_ini('host.example', port=12345, fs=self.fs) cmd = ['mq-run', config_path, queue_basedir] mailer = DebugMailer(simulate_failed_sending=True) with mock.patch('schwarz.mailqueue.queue_runner.init_smtp_mailer', new=lambda s: mailer): rc = one_shot_queue_run_main(argv=cmd, return_rc_code=True) assert_equals(0, rc) assert_length(0, mailer.sent_mails) mock_fn.assert_called_once() assert_length( 0, find_messages(queue_basedir, log=l_(None)), message= 'plugin should have discarded the message after failed delivery')
def test_can_send_queued_message_to_multiple_recipients(self): mailer = DebugMailer() recipients = (b'*****@*****.**', b'*****@*****.**') inject_example_message(self.path_maildir, recipients=recipients) send_all_queued_messages(self.path_maildir, mailer) assert_is_empty(self.msg_files(folder='new')) sent_msg, = mailer.sent_mails _as_str = lambda values: tuple([v.decode('ascii') for v in values]) assert_equals(_as_str(recipients), sent_msg.to_addrs)
def test_can_handle_failures_and_update_metadata(self): mailer = DebugMailer(simulate_failed_sending=True) queue_date = DateTime(2020, 2, 4, hour=14, minute=32, tzinfo=UTC) inject_example_message(self.path_maildir, queue_date=queue_date) send_all_queued_messages(self.path_maildir, mailer) assert_is_empty(mailer.sent_mails) msg_file, = self.msg_files(folder='new') msg = MaildirBackedMsg(msg_file) assert_equals(queue_date, msg.queue_date) assert_equals(1, msg.retries) assert_not_none(msg.last_delivery_attempt) assert_almost_now(msg.last_delivery_attempt)
def test_can_send_message(self, with_msg_id): mailer = DebugMailer() msg_header = b'X-Header: somevalue\n' if with_msg_id: msg_id = '*****@*****.**' % uuid.uuid4() msg_header += b'Message-ID: <%s>\n' % msg_id.encode('ascii') msg_body = b'MsgBody\n' msg_bytes = msg_header + b'\n' + msg_body msg = inject_example_message( self.path_maildir, sender=b'*****@*****.**', recipient=b'*****@*****.**', msg_bytes=msg_bytes, ) assert_true(os.path.exists(msg.path)) with LogCapture() as lc: mh = MessageHandler([mailer], info_logger(lc)) was_sent = mh.send_message(msg) assert_trueish(was_sent) expected_log_msg = '%s => %s' % ('*****@*****.**', '*****@*****.**') if with_msg_id: expected_log_msg += ' <%s>' % msg_id assert_did_log_message(lc, expected_msg=expected_log_msg) assert_length(1, mailer.sent_mails) sent_msg, = mailer.sent_mails assert_equals('*****@*****.**', sent_msg.from_addr) assert_equals(('*****@*****.**', ), sent_msg.to_addrs) assert_equals(msg_nl(msg_bytes), sent_msg.msg_fp.read()) assert_false(os.path.exists(msg.path)) # ensure there are no left-overs/tmp files assert_length(0, self.list_all_files(self.path_maildir))
def test_can_move_stale_messages_back_to_new(self): mailer = DebugMailer() inject_example_message(self.path_maildir, target_folder='cur') send_all_queued_messages(self.path_maildir, mailer) assert_is_empty(mailer.sent_mails) assert_is_empty(self.msg_files(folder='new')) assert_length(1, self.msg_files(folder='cur')) dt_stale = DateTime.now() + TimeDelta(hours=1) # LogCapture: no logged warning about stale message on the command line with LogCapture(): with freeze_time(dt_stale): send_all_queued_messages(self.path_maildir, mailer) assert_is_empty(self.msg_files(folder='new')) assert_is_empty(self.msg_files(folder='cur')) assert_length(1, mailer.sent_mails)
def test_mq_runner_works_without_plugins(self): queue_basedir = os.path.join(self.fs.root, 'mailqueue') create_maildir_directories(queue_basedir) inject_example_message(queue_basedir) config_path = create_ini('host.example', port=12345, fs=self.fs) cmd = ['mq-run', config_path, queue_basedir] mailer = DebugMailer(simulate_failed_sending=True) with mock.patch('schwarz.mailqueue.queue_runner.init_smtp_mailer', new=lambda s: mailer): rc = one_shot_queue_run_main(argv=cmd, return_rc_code=True) assert_equals(0, rc) assert_length(0, mailer.sent_mails) assert_length( 1, find_messages(queue_basedir, log=l_(None)), message='message should have been queued for later delivery')
def test_can_handle_sending_failure(self): mailer = DebugMailer(simulate_failed_sending=True) msg = inject_example_message(self.path_maildir) assert_true(os.path.exists(msg.path)) was_sent = MessageHandler([mailer]).send_message(msg) assert_falseish(was_sent) assert_true(os.path.exists(msg.path)) # no left-overs (e.g. in "tmp" folder) other than the initial message file assert_length(1, self.list_all_files(self.path_maildir))
def test_can_handle_concurrent_sends(self): mailer = DebugMailer() msg = inject_example_message(self.path_maildir) locked_msg = lock_file(msg.path, timeout=0.1) send_all_queued_messages(self.path_maildir, mailer) assert_length(1, self.msg_files(folder='new')) assert_is_empty(mailer.sent_mails) locked_msg.close() send_all_queued_messages(self.path_maildir, mailer) assert_is_empty(self.msg_files(folder='new')) assert_length(1, mailer.sent_mails)
def test_can_handle_duplicate_file_in_cur_before_send(self): msg = inject_example_message(self.path_maildir) path_in_progress = msg.path.replace('new', 'cur') # this can happen on Unix/Posix because Python does not provide an # atomic "move without overwrite". Linux provides the "renameat2" # system call (with RENAME_NOREPLACE flag) but Python does not expose # that API. shutil.copy(msg.path, path_in_progress) mailer = DebugMailer() was_sent = MessageHandler([mailer]).send_message(msg) assert_none(was_sent) assert_length(0, mailer.sent_mails) assert_length(2, self.list_all_files(self.path_maildir))
def test_can_handle_vanished_file_after_successful_send(self): if IS_WINDOWS: self.skipTest('unable to unlink open file on Windows') msg = inject_example_message(self.path_maildir) path_in_progress = msg.path.replace('new', 'cur') def delete_on_send(*args): os.unlink(path_in_progress) return True mailer = DebugMailer(send_callback=delete_on_send) was_sent = MessageHandler([mailer]).send_message(msg) assert_true(was_sent) assert_length(1, mailer.sent_mails) assert_length(0, self.list_all_files(self.path_maildir))
def test_tries_to_lock_message_while_sending(self): mailer = DebugMailer() msg = inject_example_message(self.path_maildir) locked_msg = lock_file(msg.path, timeout=0.1) mh = MessageHandler([mailer]) was_sent = mh.send_message(msg) assert_none(was_sent) assert_length(1, self.msg_files(folder='new')) assert_is_empty(mailer.sent_mails) locked_msg.close() was_sent = mh.send_message(msg) assert_trueish(was_sent) assert_is_empty(self.msg_files(folder='new')) assert_length(1, mailer.sent_mails)
def test_can_handle_duplicate_file_in_new_after_failed_send(self): msg = inject_example_message(self.path_maildir) path_in_progress = msg.path.replace('new', 'cur') # again: can happen because Python provides not atomic "move without # overwrite" on Linux (see also "renameat2" system call) def duplicate_on_failed_send(*args): shutil.copy(path_in_progress, msg.path) return False mailer = DebugMailer(send_callback=duplicate_on_failed_send) was_sent = MessageHandler([mailer]).send_message(msg) assert_false(was_sent) assert_length(0, mailer.sent_mails) assert_length(2, self.list_all_files(self.path_maildir))
def test_plugin_can_access_number_of_failed_deliveries(self): registry = SignalRegistry() def discard_after_two_attempts(sender, msg, send_result): return MQAction.DISCARD if (msg.retries > 1) else None connect_signals({MQSignal.delivery_failed: discard_after_two_attempts}, registry.namespace) msg = inject_example_message(self.path_maildir) mailer = DebugMailer(simulate_failed_sending=True) mh = MessageHandler([mailer], plugins=registry) mh.send_message(msg) assert_length(1, find_messages(self.path_maildir, log=l_(None))) send_result = mh.send_message(msg) assert_falseish(send_result) assert_length(0, mailer.sent_mails) assert_length(0, find_messages(self.path_maildir, log=l_(None))) assert_true(send_result.discarded)