# Copyright (C) 2012-2017 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 .
"""Test the archive runner."""
import os
import unittest
from email import message_from_file
from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.interfaces.archiver import IArchiver
from mailman.interfaces.mailinglist import IListArchiverSet
from mailman.runners.archive import ArchiveRunner
from mailman.testing.helpers import (
LogFileMark, configuration, get_queue_messages, make_testable_runner,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
from mailman.utilities.datetime import RFC822_DATE_FMT, factory, now
from zope.interface import implementer
@implementer(IArchiver)
class DummyArchiver:
name = 'dummy'
@staticmethod
def list_url(mlist):
return 'http://archive.example.com/'
@staticmethod
def permalink(mlist, msg):
filename = msg['message-id-hash']
return 'http://archive.example.com/' + filename
@staticmethod
def archive_message(mlist, msg):
filename = msg['message-id-hash']
path = os.path.join(config.MESSAGES_DIR, filename)
with open(path, 'w') as fp:
print(msg.as_string(), file=fp)
# Not technically allowed by the API, but good enough for the test.
return path
@implementer(IArchiver)
class BrokenArchiver:
"""An archiver that has some broken methods."""
name = 'broken'
def list_url(self, mlist):
raise RuntimeError('Cannot get list URL')
def permalink(self, mlist, msg):
raise RuntimeError('Cannot get permalink')
@staticmethod
def archive_message(mlist, message):
raise RuntimeError('Cannot archive message')
class TestArchiveRunner(unittest.TestCase):
"""Test the archive runner."""
layer = ConfigLayer
def setUp(self):
self._mlist = create_list('test@example.com')
self._now = now()
# Enable just the dummy archiver.
config.push('dummy', """
[archiver.dummy]
class: mailman.runners.tests.test_archiver.DummyArchiver
enable: no
[archiver.broken]
class: mailman.runners.tests.test_archiver.BrokenArchiver
enable: no
[archiver.prototype]
enable: no
[archiver.mhonarc]
enable: no
[archiver.mail_archive]
enable: no
""")
self.addCleanup(config.pop, 'dummy')
self._archiveq = config.switchboards['archive']
self._msg = mfs("""\
From: aperson@example.com
To: test@example.com
Subject: My first post
Message-ID:
Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
First post!
""")
self._runner = make_testable_runner(ArchiveRunner)
IListArchiverSet(self._mlist).get('dummy').is_enabled = True
@configuration('archiver.dummy', enable='yes')
def test_archive_runner(self):
# Ensure that the archive runner ends up archiving the message.
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id,
received_time=now())
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
@configuration('archiver.dummy', enable='yes')
def test_archive_runner_with_dated_message(self):
# Date headers don't throw off the archiver runner.
self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id,
received_time=now())
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
@configuration('archiver.dummy', enable='yes', clobber_date='never')
def test_clobber_date_never(self):
# Even if the Date header is insanely off from the received time of
# the message, if clobber_date is 'never', the header is not clobbered.
self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id,
received_time=now())
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
@configuration('archiver.dummy', enable='yes')
def test_clobber_dateless(self):
# A message with no Date header will always get clobbered.
self.assertEqual(self._msg['date'], None)
# Now, before enqueuing the message (well, really, calling 'now()'
# again), fast forward a few days.
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id,
received_time=now(strip_tzinfo=False))
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
@configuration('archiver.dummy', enable='yes', clobber_date='always')
def test_clobber_date_always(self):
# The date always gets clobbered with the current received time.
self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
# Now, before enqueuing the message (well, really, calling 'now()'
# again as will happen in the runner), fast forward a few days.
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id)
factory.fast_forward(days=4)
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Fri, 05 Aug 2005 07:49:23 +0000')
self.assertEqual(archived['x-original-date'],
'Mon, 01 Aug 2005 07:49:23 +0000')
@configuration('archiver.dummy',
enable='yes', clobber_date='maybe', clobber_skew='1d')
def test_clobber_date_maybe_when_insane(self):
# The date is clobbered if it's farther off from now than its skew
# period.
self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
# Now, before enqueuing the message (well, really, calling 'now()'
# again as will happen in the runner), fast forward a few days.
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id)
factory.fast_forward(days=4)
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Fri, 05 Aug 2005 07:49:23 +0000')
self.assertEqual(archived['x-original-date'],
'Mon, 01 Aug 2005 07:49:23 +0000')
@configuration('archiver.dummy',
enable='yes', clobber_date='maybe', clobber_skew='10d')
def test_clobber_date_maybe_when_sane(self):
# The date is not clobbered if it's nearer to now than its skew
# period.
self._msg['Date'] = now(strip_tzinfo=False).strftime(RFC822_DATE_FMT)
# Now, before enqueuing the message (well, really, calling 'now()'
# again as will happen in the runner), fast forward a few days.
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id)
factory.fast_forward(days=4)
self._runner.run()
# There should now be a copy of the message in the file system.
filename = os.path.join(
config.MESSAGES_DIR, '4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB')
with open(filename) as fp:
archived = message_from_file(fp)
self.assertEqual(archived['message-id'], '')
self.assertEqual(archived['date'], 'Mon, 01 Aug 2005 07:49:23 +0000')
self.assertEqual(archived['x-original-date'], None)
@configuration('archiver.dummy', enable='yes')
def test_disable_all_list_archivers(self):
# Let's disable all the archivers for the mailing list, but not the
# global archivers. No messages will get archived.
for archiver in IListArchiverSet(self._mlist).archivers:
archiver.is_enabled = False
config.db.store.commit()
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id)
self._runner.run()
self.assertEqual(os.listdir(config.MESSAGES_DIR), [])
@configuration('archiver.broken', enable='yes')
def test_broken_archiver(self):
# GL issue #208 - IArchive messages raise exceptions, breaking the
# rfc-2369 handler and shunting messages.
mark = LogFileMark('mailman.archiver')
self._archiveq.enqueue(
self._msg, {},
listid=self._mlist.list_id,
received_time=now())
IListArchiverSet(self._mlist).get('broken').is_enabled = True
self._runner.run()
# The archiver is broken, so there are no messages on the file system,
# but there is a log message and the message was not shunted.
log_messages = mark.read()
self.assertIn('Exception in "broken" archiver', log_messages)
self.assertIn('RuntimeError: Cannot archive message', log_messages)
get_queue_messages('shunt', expected_count=0)