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 """Tests for the Task runner."""
23 from datetime
import timedelta
24 from lazr
.config
import as_timedelta
25 from mailman
.app
.bounces
import PENDABLE_LIFETIME
26 from mailman
.app
.lifecycle
import create_list
27 from mailman
.app
.moderator
import hold_message
28 from mailman
.config
import config
29 from mailman
.database
.transaction
import dbconnection
30 from mailman
.interfaces
.cache
import ICacheManager
31 from mailman
.interfaces
.messages
import IMessageStore
32 from mailman
.interfaces
.pending
import IPendable
, IPendings
33 from mailman
.interfaces
.requests
import IListRequests
34 from mailman
.interfaces
.workflow
import IWorkflowStateManager
35 from mailman
.model
.bounce
import BounceEvent
36 from mailman
.model
.message
import Message
37 from mailman
.runners
.task
import TaskRunner
38 from mailman
.testing
.helpers
import (
41 specialized_message_from_string
as mfs
,
43 from mailman
.testing
.layers
import ConfigLayer
44 from mailman
.utilities
.datetime
import factory
45 from zope
.component
import getUtility
46 from zope
.interface
import implementer
49 @implementer(IPendable
)
50 class MyPendable(dict):
51 PEND_TYPE
= 'my pended'
54 class TestTask(unittest
.TestCase
):
55 """Test various aspects of the Task runner."""
60 self
._pendings
= getUtility(IPendings
)
61 self
._events
= BounceEvent
62 self
._wfmanager
= getUtility(IWorkflowStateManager
)
63 self
._cachemanager
= getUtility(ICacheManager
)
64 self
._messages
= getUtility(IMessageStore
)
65 self
._mlist
= create_list('ant@example.com')
68 From: anne@example.com
76 From: anne@example.com
82 self
._listrequests
= IListRequests(self
._mlist
)
83 self
._runner
= make_testable_runner(TaskRunner
)
84 self
._cachemanager
.add('cache1', 'xxx1', lifetime
=timedelta(days
=1))
85 self
._cachemanager
.add('cache2', 'xxx2', lifetime
=timedelta(days
=3))
86 pendable
= MyPendable(id=1)
87 self
._token
1 = self
._pendings
.add(pendable
, lifetime
=timedelta(days
=1))
88 self
._token
2 = self
._pendings
.add(pendable
, lifetime
=timedelta(days
=3))
89 self
._wfmanager
.save(self
._token
1)
90 self
._wfmanager
.save(self
._token
2)
91 self
._requestid
1 = hold_message(
92 self
._mlist
, self
._msg
1, reason
='Testing')
93 self
._requestid
2 = hold_message(
94 self
._mlist
, self
._msg
2, reason
='Testing')
96 def test_task_runner(self
):
97 # Test that the task runner deletes expired cache, pendings and
98 # associated workflows.
99 self
.assertEqual(self
._cachemanager
.get('cache1'), 'xxx1')
100 self
.assertEqual(self
._cachemanager
.get('cache2'), 'xxx2')
101 self
.assertEqual(self
._pendings
.count(), 4)
102 self
.assertEqual(self
._wfmanager
.count
, 2)
103 mark
= LogFileMark('mailman.task')
104 factory
.fast_forward(days
=2)
106 self
.assertIsNone(self
._cachemanager
.get('cache1'))
107 self
.assertEqual(self
._cachemanager
.get('cache2'), 'xxx2')
108 self
.assertEqual(self
._pendings
.count(), 3)
109 pended
= self
._pendings
.confirm(self
._token
2, expunge
=False)
110 self
.assertEqual(pended
['type'], 'my pended')
111 self
.assertEqual(list(self
._wfmanager
.get_all_tokens()),
114 self
.assertIn('Task runner evicted 1 expired pendings', log
)
115 self
.assertIn('Task runner deleted 1 orphaned workflows', log
)
116 self
.assertIn('Task runner deleted 0 orphaned requests', log
)
117 self
.assertIn('Task runner evicted expired cache entries', log
)
119 def test_task_runner_request(self
):
120 # Test that the task runner deletes orphaned requests.
121 self
.assertEqual(self
._pendings
.count(), 4)
122 self
.assertEqual(self
._listrequests
.count
, 2)
123 life
= as_timedelta(config
.mailman
.moderator_request_life
)
124 mark
= LogFileMark('mailman.task')
125 factory
.fast_forward(days
=life
.days
+1)
127 self
.assertEqual(self
._pendings
.count(), 0)
128 self
.assertEqual(self
._listrequests
.count
, 0)
130 self
.assertIn('Task runner deleted 2 orphaned requests', log
)
132 def test_task_runner_messages(self
):
133 # Test that the task runner deletes orphaned messages from the
135 # Initially, there are 2 messages in the store and 4 pendings.
136 self
.assertEqual(len(list(self
._messages
.messages
)), 2)
137 self
.assertEqual(self
._pendings
.count(), 4)
138 # Deleting the first request removes the pending but not the message.
139 self
._listrequests
.delete_request(self
._requestid
1)
140 self
.assertEqual(self
._pendings
.count(), 3)
141 self
.assertEqual(len(list(self
._messages
.messages
)), 2)
142 mark
= LogFileMark('mailman.task')
144 # Now there's only msg2.
145 self
.assertEqual(len(list(self
._messages
.messages
)), 1)
146 self
.assertIsNotNone(self
._messages
.get_message_by_id('<msg2>'))
148 self
.assertIn('Task runner deleted 1 orphaned messages', log
)
151 def test_task_runner_message_files(self
, store
):
152 # Test that task runner deletes message files with no entry in the
155 """ A helper to count the number of saved message files."""
157 base_dir
= config
.MESSAGES_DIR
158 for root
, dirs
, files
in os
.walk(base_dir
):
162 # Initally there are two entries in the message store and two files.
163 self
.assertEqual(len(list(self
._messages
.messages
)), 2)
164 self
.assertEqual(count_files(), 2)
165 # Delete one of the message store entries leaving one but still two
167 row
= store
.query(Message
).filter_by(message_id
='<msg1>').first()
169 self
.assertEqual(len(list(self
._messages
.messages
)), 1)
170 self
.assertEqual(count_files(), 2)
171 mark
= LogFileMark('mailman.task')
173 # Now there's only one file.
174 self
.assertEqual(len(list(self
._messages
.messages
)), 1)
175 self
.assertEqual(count_files(), 1)
177 self
.assertIn('Task runner deleted 1 orphaned messages', log
)
180 def test_task_runner_bounce_events_old_unprocessed(self
, store
):
181 # Test that the task runner deletes processed bounce events older than
182 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
183 # Set one old but unprocessed.
184 event
= self
._events
(self
._mlist
.list_id
,
188 event
.timestamp
-= as_timedelta(PENDABLE_LIFETIME
) + as_timedelta('1d')
190 mark
= LogFileMark('mailman.task')
193 self
.assertIn('Task runner evicted 0 expired bounce events', log
)
196 def test_task_runner_bounce_events_old_processed(self
, store
):
197 # Test that the task runner deletes processed bounce events older than
198 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
199 # Set one old and processed.
200 event
= self
._events
(self
._mlist
.list_id
,
204 event
.timestamp
-= as_timedelta(PENDABLE_LIFETIME
) + as_timedelta('1d')
205 event
.processed
= True
207 mark
= LogFileMark('mailman.task')
210 self
.assertIn('Task runner evicted 1 expired bounce events', log
)
213 def test_task_runner_bounce_events_processed(self
, store
):
214 # Test that the task runner deletes processed bounce events older than
215 # PENDABLE_LIFETIME, but not newer ones or unprocessed ones.
217 event
= self
._events
(self
._mlist
.list_id
,
221 event
.processed
= True
223 mark
= LogFileMark('mailman.task')
226 self
.assertIn('Task runner evicted 0 expired bounce events', log
)