summaryrefslogtreecommitdiff
path: root/src/mailman/runners
diff options
context:
space:
mode:
authorBarry Warsaw2012-03-23 19:25:27 -0400
committerBarry Warsaw2012-03-23 19:25:27 -0400
commit25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc (patch)
treef51fe7931545e23058cdb65595c7caed9b43bb0e /src/mailman/runners
parentaa2d0ad067adfd2515ed3c256cd0bca296058479 (diff)
parente005e1b12fa0bd82d2e126df476b5505b440ce36 (diff)
downloadmailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.tar.gz
mailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.tar.zst
mailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.zip
Merge the 'owners' branch. Posting to a list's -owner address now works as
expected.
Diffstat (limited to 'src/mailman/runners')
-rw-r--r--src/mailman/runners/digest.py3
-rw-r--r--src/mailman/runners/docs/lmtp.rst2
-rw-r--r--src/mailman/runners/docs/outgoing.rst4
-rw-r--r--src/mailman/runners/incoming.py5
-rw-r--r--src/mailman/runners/pipeline.py5
-rw-r--r--src/mailman/runners/tests/test_incoming.py94
-rw-r--r--src/mailman/runners/tests/test_owner.py142
-rw-r--r--src/mailman/runners/tests/test_pipeline.py115
8 files changed, 363 insertions, 7 deletions
diff --git a/src/mailman/runners/digest.py b/src/mailman/runners/digest.py
index 513a20322..afc11f732 100644
--- a/src/mailman/runners/digest.py
+++ b/src/mailman/runners/digest.py
@@ -41,11 +41,10 @@ from email.utils import formatdate, getaddresses, make_msgid
from urllib2 import URLError
from mailman.config import config
-from mailman.core.errors import DiscardMessage
from mailman.core.i18n import _
from mailman.core.runner import Runner
+from mailman.handlers.decorate import decorate
from mailman.interfaces.member import DeliveryMode, DeliveryStatus
-from mailman.pipeline.decorate import decorate
from mailman.utilities.i18n import make
from mailman.utilities.mailbox import Mailbox
from mailman.utilities.string import oneline, wrap
diff --git a/src/mailman/runners/docs/lmtp.rst b/src/mailman/runners/docs/lmtp.rst
index 2b6c4b42b..3ce145907 100644
--- a/src/mailman/runners/docs/lmtp.rst
+++ b/src/mailman/runners/docs/lmtp.rst
@@ -306,7 +306,7 @@ Messages to the `-owner` address also go to the incoming processor.
1
>>> dump_msgdata(messages[0].msgdata)
_parsemsg : False
- envsender : changeme@example.com
+ envsender : noreply@example.com
listname : mylist@example.com
original_size: ...
subaddress : owner
diff --git a/src/mailman/runners/docs/outgoing.rst b/src/mailman/runners/docs/outgoing.rst
index b1ffb06a0..d8963ae00 100644
--- a/src/mailman/runners/docs/outgoing.rst
+++ b/src/mailman/runners/docs/outgoing.rst
@@ -33,7 +33,7 @@ move messages to the 'retry queue' for handling delivery failures.
Normally, messages would show up in the outgoing queue after the message has
been processed by the rule set and pipeline. But we can simulate that here by
injecting a message directly into the outgoing queue. First though, we must
-call the ``calculate-recipients`` handler so that the message metadata will be
+call the ``member-recipients`` handler so that the message metadata will be
populated with the list of addresses to deliver the message to.
::
@@ -47,7 +47,7 @@ populated with the list of addresses to deliver the message to.
... """)
>>> msgdata = {}
- >>> handler = config.handlers['calculate-recipients']
+ >>> handler = config.handlers['member-recipients']
>>> handler.process(mlist, msg, msgdata)
>>> outgoing_queue = config.switchboards['out']
diff --git a/src/mailman/runners/incoming.py b/src/mailman/runners/incoming.py
index 7072f9bcc..d8db926c7 100644
--- a/src/mailman/runners/incoming.py
+++ b/src/mailman/runners/incoming.py
@@ -61,6 +61,9 @@ class IncomingRunner(Runner):
pass
config.db.commit()
# Process the message through the mailing list's start chain.
- process(mlist, msg, msgdata, mlist.posting_chain)
+ start_chain = (mlist.owner_chain
+ if msgdata.get('to_owner', False)
+ else mlist.posting_chain)
+ process(mlist, msg, msgdata, start_chain)
# Do not keep this message queued.
return False
diff --git a/src/mailman/runners/pipeline.py b/src/mailman/runners/pipeline.py
index 8bee2c4cb..b031662b3 100644
--- a/src/mailman/runners/pipeline.py
+++ b/src/mailman/runners/pipeline.py
@@ -30,6 +30,9 @@ from mailman.core.runner import Runner
class PipelineRunner(Runner):
def _dispose(self, mlist, msg, msgdata):
# Process the message through the mailing list's pipeline.
- process(mlist, msg, msgdata, mlist.posting_pipeline)
+ pipeline = (mlist.owner_pipeline
+ if msgdata.get('to_owner', False)
+ else mlist.posting_pipeline)
+ process(mlist, msg, msgdata, pipeline)
# Do not keep this message queued.
return False
diff --git a/src/mailman/runners/tests/test_incoming.py b/src/mailman/runners/tests/test_incoming.py
new file mode 100644
index 000000000..5a0d82765
--- /dev/null
+++ b/src/mailman/runners/tests/test_incoming.py
@@ -0,0 +1,94 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the incoming queue runner."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestIncoming',
+ ]
+
+
+import unittest
+
+from mailman.app.lifecycle import create_list
+from mailman.chains.base import TerminalChainBase
+from mailman.config import config
+from mailman.runners.incoming import IncomingRunner
+from mailman.testing.helpers import (
+ get_queue_messages,
+ make_testable_runner,
+ specialized_message_from_string as mfs)
+from mailman.testing.layers import ConfigLayer
+
+
+
+class Chain(TerminalChainBase):
+ name = 'test'
+ description = 'a test chain'
+
+ def __init__(self, marker):
+ self._marker = marker
+
+ def _process(self, mlist, msg, msgdata):
+ msgdata['marker'] = self._marker
+ config.switchboards['out'].enqueue(msg, msgdata)
+
+
+
+class TestIncoming(unittest.TestCase):
+ """Test the incoming queue runner."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ self._mlist.posting_chain = 'test posting'
+ self._mlist.owner_chain = 'test owner'
+ config.chains['test posting'] = Chain('posting')
+ config.chains['test owner'] = Chain('owner')
+ self._in = make_testable_runner(IncomingRunner, 'in')
+ self._msg = mfs("""\
+From: anne@example.com
+To: test@example.com
+
+""")
+
+ def tearDown(self):
+ del config.chains['test posting']
+ del config.chains['test owner']
+
+ def test_posting(self):
+ # A message posted to the list goes through the posting chain.
+ msgdata = dict(listname='test@example.com')
+ config.switchboards['in'].enqueue(self._msg, msgdata)
+ self._in.run()
+ messages = get_queue_messages('out')
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(messages[0].msgdata.get('marker'), 'posting')
+
+ def test_owner(self):
+ # A message posted to the list goes through the posting chain.
+ msgdata = dict(listname='test@example.com',
+ to_owner=True)
+ config.switchboards['in'].enqueue(self._msg, msgdata)
+ self._in.run()
+ messages = get_queue_messages('out')
+ self.assertEqual(len(messages), 1)
+ self.assertEqual(messages[0].msgdata.get('marker'), 'owner')
diff --git a/src/mailman/runners/tests/test_owner.py b/src/mailman/runners/tests/test_owner.py
new file mode 100644
index 000000000..8264ef0d8
--- /dev/null
+++ b/src/mailman/runners/tests/test_owner.py
@@ -0,0 +1,142 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test posting to a mailing list's -owner address."""
+
+# XXX 2012-03-23 BAW: This is not necessarily the best place for this test.
+# We really need a better place to collect these sort of end-to-end posting
+# tests. They're not exactly integration tests, but they do touch lots of
+# parts of the system.
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestEmailToOwner',
+ ]
+
+
+import unittest
+
+from operator import itemgetter
+from zope.component import getUtility
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.interfaces.member import MemberRole
+from mailman.interfaces.usermanager import IUserManager
+from mailman.testing.helpers import (
+ TestableMaster,
+ get_lmtp_client,
+ make_testable_runner)
+from mailman.runners.incoming import IncomingRunner
+from mailman.runners.outgoing import OutgoingRunner
+from mailman.runners.pipeline import PipelineRunner
+from mailman.testing.layers import SMTPLayer
+
+
+
+class TestEmailToOwner(unittest.TestCase):
+ """Test emailing a mailing list's -owner address."""
+
+ layer = SMTPLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ # Add some owners, moderators, and members
+ manager = getUtility(IUserManager)
+ anne = manager.create_address('anne@example.com')
+ bart = manager.create_address('bart@example.com')
+ cris = manager.create_address('cris@example.com')
+ dave = manager.create_address('dave@example.com')
+ self._mlist.subscribe(anne, MemberRole.member)
+ self._mlist.subscribe(anne, MemberRole.owner)
+ self._mlist.subscribe(bart, MemberRole.moderator)
+ self._mlist.subscribe(bart, MemberRole.owner)
+ self._mlist.subscribe(cris, MemberRole.moderator)
+ self._mlist.subscribe(dave, MemberRole.member)
+ config.db.commit()
+ self._inq = make_testable_runner(IncomingRunner, 'in')
+ self._pipelineq = make_testable_runner(PipelineRunner, 'pipeline')
+ self._outq = make_testable_runner(OutgoingRunner, 'out')
+ # Python 2.7 has assertMultiLineEqual. Let this work without bounds.
+ self.maxDiff = None
+ self.eq = getattr(self, 'assertMultiLineEqual', self.assertEqual)
+
+ def test_owners_get_email(self):
+ # XXX 2012-03-23 BAW: We can't use a layer here because we need both
+ # the SMTPLayer and LMTPLayer and these are incompatible. There's no
+ # way to make zope.test* happy without causing errors or worse. Live
+ # with this hack until we can rip all that layer crap out and use
+ # something like testresources.
+ def wait():
+ get_lmtp_client(quiet=True)
+ lmtpd = TestableMaster(wait)
+ lmtpd.start()
+ # Post a message to the list's -owner address, and all the owners will
+ # get a copy of the message.
+ lmtp = get_lmtp_client(quiet=True)
+ lmtp.lhlo('remote.example.org')
+ lmtp.sendmail('zuzu@example.org', ['test-owner@example.com'], """\
+From: Zuzu Person <zuzu@example.org>
+To: test-owner@example.com
+Message-ID: <ant>
+
+Can you help me?
+""")
+ lmtpd.stop()
+ # There should now be one message sitting in the incoming queue.
+ # Check that, then process it. Don't use get_queue_messages() since
+ # that will empty the queue.
+ self.assertEqual(len(config.switchboards['in'].files), 1)
+ self._inq.run()
+ # There should now be one message sitting in the pipeline queue.
+ # Process that one too.
+ self.assertEqual(len(config.switchboards['pipeline'].files), 1)
+ self._pipelineq.run()
+ # The message has made its way to the outgoing queue. Again, check
+ # and process that one.
+ self.assertEqual(len(config.switchboards['out'].files), 1)
+ self._outq.run()
+ # The SMTP server has now received three messages, one for each of the
+ # owners and moderators. Of course, Bart is both an owner and a
+ # moderator, so he'll get only one copy of the message. Dave does not
+ # get a copy of the message.
+ messages = sorted(SMTPLayer.smtpd.messages, key=itemgetter('x-rcptto'))
+ self.assertEqual(len(messages), 3)
+ self.assertEqual(messages[0]['x-rcptto'], 'anne@example.com')
+ self.assertEqual(messages[1]['x-rcptto'], 'bart@example.com')
+ self.assertEqual(messages[2]['x-rcptto'], 'cris@example.com')
+ # And yet, all three messages are addressed to the -owner address.
+ for message in messages:
+ self.assertEqual(message['to'], 'test-owner@example.com')
+ # All three messages will have two X-MailFrom headers. One is added
+ # by the LMTP server accepting Zuzu's original message, and will
+ # contain her posting address, i.e. zuzu@example.com. The second one
+ # is added by the lazr.smtptest server that accepts Mailman's VERP'd
+ # message to the individual recipient. By verifying both, we prove
+ # that Zuzu sent the original message, and that Mailman is VERP'ing
+ # the copy to all the owners.
+ self.assertEqual(
+ messages[0].get_all('x-mailfrom'),
+ ['zuzu@example.org', 'test-bounces+anne=example.com@example.com'])
+ self.assertEqual(
+ messages[1].get_all('x-mailfrom'),
+ ['zuzu@example.org', 'test-bounces+bart=example.com@example.com'])
+ self.assertEqual(
+ messages[2].get_all('x-mailfrom'),
+ ['zuzu@example.org', 'test-bounces+cris=example.com@example.com'])
diff --git a/src/mailman/runners/tests/test_pipeline.py b/src/mailman/runners/tests/test_pipeline.py
new file mode 100644
index 000000000..8776bf844
--- /dev/null
+++ b/src/mailman/runners/tests/test_pipeline.py
@@ -0,0 +1,115 @@
+# Copyright (C) 2012 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test the pipeline runner."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'TestPipelineRunner',
+ ]
+
+
+import unittest
+
+from zope.interface import implements
+
+from mailman.app.lifecycle import create_list
+from mailman.config import config
+from mailman.interfaces.handler import IHandler
+from mailman.interfaces.pipeline import IPipeline
+from mailman.runners.pipeline import PipelineRunner
+from mailman.testing.helpers import (
+ make_testable_runner,
+ specialized_message_from_string as mfs)
+from mailman.testing.layers import ConfigLayer
+
+
+
+class MyTestHandler:
+ implements(IHandler)
+ name = 'test handler'
+ description = 'A test handler'
+
+ def __init__(self, marker, test):
+ self._marker = marker
+ self._test = test
+
+ def process(self, mlist, msg, msgdata):
+ self._test.mark(self._marker)
+
+
+class MyTestPipeline:
+ implements(IPipeline)
+ name = 'test'
+ description = 'a test pipeline'
+
+ def __init__(self, marker, test):
+ self._marker = marker
+ self._test = test
+
+ def __iter__(self):
+ yield MyTestHandler(self._marker, self._test)
+
+
+
+class TestPipelineRunner(unittest.TestCase):
+ """Test the pipeline runner."""
+
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('test@example.com')
+ self._mlist.posting_pipeline = 'test posting'
+ self._mlist.owner_pipeline = 'test owner'
+ config.pipelines['test posting'] = MyTestPipeline('posting', self)
+ config.pipelines['test owner'] = MyTestPipeline('owner', self)
+ self._pipeline = make_testable_runner(PipelineRunner, 'pipeline')
+ self._markers = []
+ self._msg = mfs("""\
+From: anne@example.com
+To: test@example.com
+
+""")
+
+ def tearDown(self):
+ del config.pipelines['test posting']
+ del config.pipelines['test owner']
+
+ def mark(self, marker):
+ # Record a marker seen by a handler.
+ self._markers.append(marker)
+
+ def test_posting(self):
+ # A message accepted for posting gets processed through the posting
+ # pipeline.
+ msgdata = dict(listname='test@example.com')
+ config.switchboards['pipeline'].enqueue(self._msg, msgdata)
+ self._pipeline.run()
+ self.assertEqual(len(self._markers), 1)
+ self.assertEqual(self._markers[0], 'posting')
+
+ def test_owner(self):
+ # A message accepted for posting to a list's owners gets processed
+ # through the owner pipeline.
+ msgdata = dict(listname='test@example.com',
+ to_owner=True)
+ config.switchboards['pipeline'].enqueue(self._msg, msgdata)
+ self._pipeline.run()
+ self.assertEqual(len(self._markers), 1)
+ self.assertEqual(self._markers[0], 'owner')