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)
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
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."""
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 (
35 specialized_message_from_string
as mfs
,
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."""
56 self
._mlist
= create_list('test@example.com')
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.
69 From: anne@example.com
74 config
.switchboards
['in'].enqueue(msg
, listid
='test.example.com')
75 with
event_subscribers(self
._got
_event
):
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
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')
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?
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.