The task runner now deletes old processed bounce events.
[mailman.git] / src / mailman / runners / tests / test_task.py
blob31fa6e2abbd56aa8a3e6ce301aeb7884b130a6b1
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 """Tests for the Task runner."""
20 import unittest
22 from datetime import timedelta
23 from lazr.config import as_timedelta
24 from mailman.app.bounces import PENDABLE_LIFETIME
25 from mailman.app.lifecycle import create_list
26 from mailman.app.moderator import hold_message
27 from mailman.config import config
28 from mailman.database.transaction import dbconnection
29 from mailman.interfaces.cache import ICacheManager
30 from mailman.interfaces.messages import IMessageStore
31 from mailman.interfaces.pending import IPendable, IPendings
32 from mailman.interfaces.requests import IListRequests
33 from mailman.interfaces.workflow import IWorkflowStateManager
34 from mailman.model.bounce import BounceEvent
35 from mailman.runners.task import TaskRunner
36 from mailman.testing.helpers import (
37 LogFileMark,
38 make_testable_runner,
39 specialized_message_from_string as mfs,
41 from mailman.testing.layers import ConfigLayer
42 from mailman.utilities.datetime import factory
43 from zope.component import getUtility
44 from zope.interface import implementer
47 @implementer(IPendable)
48 class MyPendable(dict):
49 PEND_TYPE = 'my pended'
52 class TestTask(unittest.TestCase):
53 """Test various aspects of the Task runner."""
55 layer = ConfigLayer
57 def setUp(self):
58 self._pendings = getUtility(IPendings)
59 self._events = BounceEvent
60 self._wfmanager = getUtility(IWorkflowStateManager)
61 self._cachemanager = getUtility(ICacheManager)
62 self._messages = getUtility(IMessageStore)
63 self._mlist = create_list('ant@example.com')
64 self._msg1 = mfs("""\
65 To: ant@example.com
66 From: anne@example.com
67 Subject: A message
68 Message-ID: <msg1>
70 first message
71 """)
72 self._msg2 = mfs("""\
73 To: ant@example.com
74 From: anne@example.com
75 Subject: A message
76 Message-ID: <msg2>
78 second message
79 """)
80 self._listrequests = IListRequests(self._mlist)
81 self._runner = make_testable_runner(TaskRunner)
82 self._cachemanager.add('cache1', 'xxx1', lifetime=timedelta(days=1))
83 self._cachemanager.add('cache2', 'xxx2', lifetime=timedelta(days=3))
84 pendable = MyPendable(id=1)
85 self._token1 = self._pendings.add(pendable, lifetime=timedelta(days=1))
86 self._token2 = self._pendings.add(pendable, lifetime=timedelta(days=3))
87 self._wfmanager.save(self._token1)
88 self._wfmanager.save(self._token2)
89 self._requestid1 = hold_message(
90 self._mlist, self._msg1, reason='Testing')
91 self._requestid2 = hold_message(
92 self._mlist, self._msg2, reason='Testing')
94 def test_task_runner(self):
95 # Test that the task runner deletes expired cache, pendings and
96 # associated workflows.
97 self.assertEqual(self._cachemanager.get('cache1'), 'xxx1')
98 self.assertEqual(self._cachemanager.get('cache2'), 'xxx2')
99 self.assertEqual(self._pendings.count(), 4)
100 self.assertEqual(self._wfmanager.count, 2)
101 mark = LogFileMark('mailman.task')
102 factory.fast_forward(days=2)
103 self._runner.run()
104 self.assertIsNone(self._cachemanager.get('cache1'))
105 self.assertEqual(self._cachemanager.get('cache2'), 'xxx2')
106 self.assertEqual(self._pendings.count(), 3)
107 pended = self._pendings.confirm(self._token2, expunge=False)
108 self.assertEqual(pended['type'], 'my pended')
109 self.assertEqual(list(self._wfmanager.get_all_tokens()),
110 [self._token2])
111 log = mark.read()
112 self.assertIn('Task runner evicted 1 expired pendings', log)
113 self.assertIn('Task runner deleted 1 orphaned workflows', log)
114 self.assertIn('Task runner deleted 0 orphaned requests', log)
115 self.assertIn('Task runner evicted expired cache entries', log)
117 def test_task_runner_request(self):
118 # Test that the task runner deletes orphaned requests.
119 self.assertEqual(self._pendings.count(), 4)
120 self.assertEqual(self._listrequests.count, 2)
121 life = as_timedelta(config.mailman.moderator_request_life)
122 mark = LogFileMark('mailman.task')
123 factory.fast_forward(days=life.days+1)
124 self._runner.run()
125 self.assertEqual(self._pendings.count(), 0)
126 self.assertEqual(self._listrequests.count, 0)
127 log = mark.read()
128 self.assertIn('Task runner deleted 2 orphaned requests', log)
130 def test_task_runner_messages(self):
131 # Test that the task runner deletes orphaned messages from the
132 # message store.
133 # Initially, there are 2 messages in the store and 4 pendings.
134 self.assertEqual(len(list(self._messages.messages)), 2)
135 self.assertEqual(self._pendings.count(), 4)
136 # Deleting the first request removes the pending but not the message.
137 self._listrequests.delete_request(self._requestid1)
138 self.assertEqual(self._pendings.count(), 3)
139 self.assertEqual(len(list(self._messages.messages)), 2)
140 mark = LogFileMark('mailman.task')
141 self._runner.run()
142 # Now there's only msg2.
143 self.assertEqual(len(list(self._messages.messages)), 1)
144 self.assertIsNotNone(self._messages.get_message_by_id('<msg2>'))
145 log = mark.read()
146 self.assertIn('Task runner deleted 1 orphaned messages', log)
148 @dbconnection
149 def test_task_runner_bounce_events_old_unprocessed(self, store):
150 # Test that the task runner deletes processed bounce events older than
151 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
152 # Set one old but unprocessed.
153 event = self._events(self._mlist.list_id,
154 'anne@example.com',
155 self._msg1,
157 event.timestamp -= as_timedelta(PENDABLE_LIFETIME) + as_timedelta('1d')
158 store.add(event)
159 mark = LogFileMark('mailman.task')
160 self._runner.run()
161 log = mark.read()
162 self.assertIn('Task runner evicted 0 expired bounce events', log)
164 @dbconnection
165 def test_task_runner_bounce_events_old_processed(self, store):
166 # Test that the task runner deletes processed bounce events older than
167 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
168 # Set one old and processed.
169 event = self._events(self._mlist.list_id,
170 'anne@example.com',
171 self._msg1,
173 event.timestamp -= as_timedelta(PENDABLE_LIFETIME) + as_timedelta('1d')
174 event.processed = True
175 store.add(event)
176 mark = LogFileMark('mailman.task')
177 self._runner.run()
178 log = mark.read()
179 self.assertIn('Task runner evicted 1 expired bounce events', log)
181 @dbconnection
182 def test_task_runner_bounce_events_processed(self, store):
183 # Test that the task runner deletes processed bounce events older than
184 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
185 # Set one processed.
186 event = self._events(self._mlist.list_id,
187 'anne@example.com',
188 self._msg1,
190 event.processed = True
191 store.add(event)
192 mark = LogFileMark('mailman.task')
193 self._runner.run()
194 log = mark.read()
195 self.assertIn('Task runner evicted 0 expired bounce events', log)