Fix a typo in rest/membership.rst.
[mailman.git] / src / mailman / core / tests / test_runner.py
blobaf22c6d7f47297ea00ab85123c7ce9eb4dd4e896
1 # Copyright (C) 2012-2023 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <https://www.gnu.org/licenses/>.
18 """Test some Runner base class behavior."""
20 import unittest
22 from mailman.app.lifecycle import create_list
23 from mailman.config import config
24 from mailman.core.runner import Runner
25 from mailman.interfaces.member import DeliveryMode
26 from mailman.interfaces.runner import RunnerCrashEvent
27 from mailman.runners.virgin import VirginRunner
28 from mailman.testing.helpers import (
29 configuration,
30 event_subscribers,
31 get_queue_messages,
32 LogFileMark,
33 make_digest_messages,
34 make_testable_runner,
35 specialized_message_from_string as mfs,
36 subscribe,
38 from mailman.testing.layers import ConfigLayer
41 class CrashingRunner(Runner):
42 def _dispose(self, mlist, msg, msgdata):
43 raise RuntimeError('borked')
46 class NonQueueRunner(Runner):
47 is_queue_runner = False
50 class TestRunner(unittest.TestCase):
51 """Test the Runner base class behavior."""
53 layer = ConfigLayer
55 def setUp(self):
56 self._mlist = create_list('test@example.com')
57 self._events = []
59 def _got_event(self, event):
60 self._events.append(event)
62 @configuration('runner.crashing',
63 **{'class': 'mailman.core.tests.CrashingRunner'})
64 def test_crash_event(self):
65 runner = make_testable_runner(CrashingRunner, 'in')
66 # When an exception occurs in Runner._process_one_file(), a zope.event
67 # gets triggered containing the exception object.
68 msg = mfs("""\
69 From: anne@example.com
70 To: test@example.com
71 Message-ID: <ant>
73 """)
74 config.switchboards['in'].enqueue(msg, listid='test.example.com')
75 with event_subscribers(self._got_event):
76 runner.run()
77 # We should now have exactly one event, which will contain the
78 # exception, plus additional metadata containing the mailing list,
79 # message, and metadata.
80 self.assertEqual(len(self._events), 1)
81 event = self._events[0]
82 self.assertIsInstance(event, RunnerCrashEvent)
83 self.assertEqual(event.mailing_list, self._mlist)
84 self.assertEqual(event.message['message-id'], '<ant>')
85 self.assertEqual(event.metadata['listid'], 'test.example.com')
86 self.assertIsInstance(event.error, RuntimeError)
87 self.assertEqual(str(event.error), 'borked')
88 self.assertIsInstance(event.runner, CrashingRunner)
89 # The message should also have ended up in the shunt queue.
90 items = get_queue_messages('shunt', expected_count=1)
91 self.assertEqual(items[0].msg['message-id'], '<ant>')
93 def test_digest_messages(self):
94 # In LP: #1130697, the digest runner creates MIME digests using the
95 # stdlib MIMEMutlipart class, however this class does not have the
96 # extended attributes we require (e.g. .sender). The fix is to use a
97 # subclass of MIMEMultipart and our own Message subclass; this adds
98 # back the required attributes. (LP: #1130696)
99 self._mlist.send_welcome_message = False
100 # Subscribe some users receiving digests.
101 anne = subscribe(self._mlist, 'Anne')
102 anne.preferences.delivery_mode = DeliveryMode.mime_digests
103 bart = subscribe(self._mlist, 'Bart')
104 bart.preferences.delivery_mode = DeliveryMode.plaintext_digests
105 # Start by creating the raw ingredients for the digests. This also
106 # runs the digest runner, thus producing the digest messages into the
107 # virgin queue.
108 make_digest_messages(self._mlist)
109 # Run the virgin queue processor, which runs the cook-headers and
110 # to-outgoing handlers. This should produce no error.
111 error_log = LogFileMark('mailman.error')
112 runner = make_testable_runner(VirginRunner, 'virgin')
113 runner.run()
114 error_text = error_log.read()
115 self.assertEqual(len(error_text), 0, error_text)
116 get_queue_messages('shunt', expected_count=0)
117 items = get_queue_messages('out', expected_count=2)
118 # Which one is the MIME digest?
119 mime_digest = None
120 for item in items:
121 if item.msg.get_content_type() == 'multipart/mixed':
122 assert mime_digest is None, 'Found two MIME digests'
123 mime_digest = item.msg
124 # The cook-headers handler ran.
125 self.assertIn('x-mailman-version', mime_digest)
126 self.assertEqual(mime_digest['precedence'], 'list')
127 # The list's -request address is the original sender.
128 self.assertEqual(item.msgdata['original_sender'],
129 'test-request@example.com')
131 @configuration('runner.nonqueue',
132 **{'class': 'mailman.core.tests.NonQueueRunner'})
133 def test_non_queue_runner(self):
134 # Test that a runner with no queue can run _one_iteration.
135 runner = make_testable_runner(NonQueueRunner)
136 # This will throw AttributeError on failure.
137 runner.run()