The SubscriptionWorkflow class doesn't need to include the expiry date in its
[mailman.git] / src / mailman / app / tests / test_subscriptions.py
blob7bb635a166e796fe21e727f118e859e18a32a82a
1 # Copyright (C) 2011-2015 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 <http://www.gnu.org/licenses/>.
18 """Tests for the subscription service."""
20 __all__ = [
21 'TestJoin',
22 'TestSubscriptionWorkflow',
26 import uuid
27 import unittest
29 from mailman.app.lifecycle import create_list
30 from mailman.app.subscriptions import SubscriptionWorkflow
31 from mailman.interfaces.address import InvalidEmailAddressError
32 from mailman.interfaces.bans import IBanManager
33 from mailman.interfaces.member import (
34 MemberRole, MembershipIsBannedError, MissingPreferredAddressError)
35 from mailman.interfaces.pending import IPendings
36 from mailman.interfaces.subscriptions import (
37 MissingUserError, ISubscriptionService)
38 from mailman.testing.helpers import LogFileMark, get_queue_messages
39 from mailman.testing.layers import ConfigLayer
40 from mailman.interfaces.mailinglist import SubscriptionPolicy
41 from mailman.interfaces.usermanager import IUserManager
42 from mailman.interfaces.workflow import IWorkflowStateManager
43 from mailman.utilities.datetime import now
44 from unittest.mock import patch
45 from zope.component import getUtility
49 class TestJoin(unittest.TestCase):
50 layer = ConfigLayer
52 def setUp(self):
53 self._mlist = create_list('test@example.com')
54 self._service = getUtility(ISubscriptionService)
56 def test_join_user_with_bogus_id(self):
57 # When `subscriber` is a missing user id, an exception is raised.
58 with self.assertRaises(MissingUserError) as cm:
59 self._service.join('test.example.com', uuid.UUID(int=99))
60 self.assertEqual(cm.exception.user_id, uuid.UUID(int=99))
62 def test_join_user_with_invalid_email_address(self):
63 # When `subscriber` is a string that is not an email address, an
64 # exception is raised.
65 with self.assertRaises(InvalidEmailAddressError) as cm:
66 self._service.join('test.example.com', 'bogus')
67 self.assertEqual(cm.exception.email, 'bogus')
69 def test_missing_preferred_address(self):
70 # A user cannot join a mailing list if they have no preferred address.
71 anne = self._service.join(
72 'test.example.com', 'anne@example.com', 'Anne Person')
73 # Try to join Anne as a user with a different role. Her user has no
74 # preferred address, so this will fail.
75 self.assertRaises(MissingPreferredAddressError,
76 self._service.join,
77 'test.example.com', anne.user.user_id,
78 role=MemberRole.owner)
82 class TestSubscriptionWorkflow(unittest.TestCase):
83 layer = ConfigLayer
84 maxDiff = None
86 def setUp(self):
87 self._mlist = create_list('test@example.com')
88 self._mlist.admin_immed_notify = False
89 self._anne = 'anne@example.com'
90 self._user_manager = getUtility(IUserManager)
92 def test_user_or_address_required(self):
93 # The `subscriber` attribute must be a user or address.
94 workflow = SubscriptionWorkflow(self._mlist)
95 self.assertRaises(AssertionError, list, workflow)
97 def test_sanity_checks_address(self):
98 # Ensure that the sanity check phase, when given an IAddress, ends up
99 # with a linked user.
100 anne = self._user_manager.create_address(self._anne)
101 workflow = SubscriptionWorkflow(self._mlist, anne)
102 self.assertIsNotNone(workflow.address)
103 self.assertIsNone(workflow.user)
104 workflow.run_thru('sanity_checks')
105 self.assertIsNotNone(workflow.address)
106 self.assertIsNotNone(workflow.user)
107 self.assertEqual(list(workflow.user.addresses)[0].email, self._anne)
109 def test_sanity_checks_user_with_preferred_address(self):
110 # Ensure that the sanity check phase, when given an IUser with a
111 # preferred address, ends up with an address.
112 anne = self._user_manager.make_user(self._anne)
113 address = list(anne.addresses)[0]
114 address.verified_on = now()
115 anne.preferred_address = address
116 workflow = SubscriptionWorkflow(self._mlist, anne)
117 # The constructor sets workflow.address because the user has a
118 # preferred address.
119 self.assertEqual(workflow.address, address)
120 self.assertEqual(workflow.user, anne)
121 workflow.run_thru('sanity_checks')
122 self.assertEqual(workflow.address, address)
123 self.assertEqual(workflow.user, anne)
125 def test_sanity_checks_user_without_preferred_address(self):
126 # Ensure that the sanity check phase, when given a user without a
127 # preferred address, but with at least one linked address, gets an
128 # address.
129 anne = self._user_manager.make_user(self._anne)
130 workflow = SubscriptionWorkflow(self._mlist, anne)
131 self.assertIsNone(workflow.address)
132 self.assertEqual(workflow.user, anne)
133 workflow.run_thru('sanity_checks')
134 self.assertIsNotNone(workflow.address)
135 self.assertEqual(workflow.user, anne)
137 def test_sanity_checks_user_with_multiple_linked_addresses(self):
138 # Ensure that the santiy check phase, when given a user without a
139 # preferred address, but with multiple linked addresses, gets of of
140 # those addresses (exactly which one is undefined).
141 anne = self._user_manager.make_user(self._anne)
142 anne.link(self._user_manager.create_address('anne@example.net'))
143 anne.link(self._user_manager.create_address('anne@example.org'))
144 workflow = SubscriptionWorkflow(self._mlist, anne)
145 self.assertIsNone(workflow.address)
146 self.assertEqual(workflow.user, anne)
147 workflow.run_thru('sanity_checks')
148 self.assertIn(workflow.address.email, ['anne@example.com',
149 'anne@example.net',
150 'anne@example.org'])
151 self.assertEqual(workflow.user, anne)
153 def test_sanity_checks_user_without_addresses(self):
154 # It is an error to try to subscribe a user with no linked addresses.
155 user = self._user_manager.create_user()
156 workflow = SubscriptionWorkflow(self._mlist, user)
157 self.assertRaises(AssertionError, workflow.run_thru, 'sanity_checks')
159 def test_sanity_checks_globally_banned_address(self):
160 # An exception is raised if the address is globally banned.
161 anne = self._user_manager.create_address(self._anne)
162 IBanManager(None).ban(self._anne)
163 workflow = SubscriptionWorkflow(self._mlist, anne)
164 self.assertRaises(MembershipIsBannedError, list, workflow)
166 def test_sanity_checks_banned_address(self):
167 # An exception is raised if the address is banned by the mailing list.
168 anne = self._user_manager.create_address(self._anne)
169 IBanManager(self._mlist).ban(self._anne)
170 workflow = SubscriptionWorkflow(self._mlist, anne)
171 self.assertRaises(MembershipIsBannedError, list, workflow)
173 def test_verification_checks_with_verified_address(self):
174 # When the address is already verified, we skip straight to the
175 # confirmation checks.
176 anne = self._user_manager.create_address(self._anne)
177 anne.verified_on = now()
178 workflow = SubscriptionWorkflow(self._mlist, anne)
179 workflow.run_thru('verification_checks')
180 with patch.object(workflow, '_step_confirmation_checks') as step:
181 next(workflow)
182 step.assert_called_once_with()
184 def test_verification_checks_with_pre_verified_address(self):
185 # When the address is not yet verified, but the pre-verified flag is
186 # passed to the workflow, we skip to the confirmation checks.
187 anne = self._user_manager.create_address(self._anne)
188 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
189 workflow.run_thru('verification_checks')
190 with patch.object(workflow, '_step_confirmation_checks') as step:
191 next(workflow)
192 step.assert_called_once_with()
193 # And now the address is verified.
194 self.assertIsNotNone(anne.verified_on)
196 def test_verification_checks_confirmation_needed(self):
197 # The address is neither verified, nor is the pre-verified flag set.
198 # A confirmation message must be sent to the user which will also
199 # verify their address.
200 anne = self._user_manager.create_address(self._anne)
201 workflow = SubscriptionWorkflow(self._mlist, anne)
202 workflow.run_thru('verification_checks')
203 with patch.object(workflow, '_step_send_confirmation') as step:
204 next(workflow)
205 step.assert_called_once_with()
206 # The address still hasn't been verified.
207 self.assertIsNone(anne.verified_on)
209 def test_confirmation_checks_open_list(self):
210 # A subscription to an open list does not need to be confirmed or
211 # moderated.
212 self._mlist.subscription_policy = SubscriptionPolicy.open
213 anne = self._user_manager.create_address(self._anne)
214 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
215 workflow.run_thru('confirmation_checks')
216 with patch.object(workflow, '_step_do_subscription') as step:
217 next(workflow)
218 step.assert_called_once_with()
220 def test_confirmation_checks_no_user_confirmation_needed(self):
221 # A subscription to a list which does not need user confirmation skips
222 # to the moderation checks.
223 self._mlist.subscription_policy = SubscriptionPolicy.moderate
224 anne = self._user_manager.create_address(self._anne)
225 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
226 workflow.run_thru('confirmation_checks')
227 with patch.object(workflow, '_step_moderation_checks') as step:
228 next(workflow)
229 step.assert_called_once_with()
231 def test_confirmation_checks_confirm_pre_confirmed(self):
232 # The subscription policy requires user confirmation, but their
233 # subscription is pre-confirmed.
234 self._mlist.subscription_policy = SubscriptionPolicy.confirm
235 anne = self._user_manager.create_address(self._anne)
236 workflow = SubscriptionWorkflow(self._mlist, anne,
237 pre_verified=True,
238 pre_confirmed=True)
239 workflow.run_thru('confirmation_checks')
240 with patch.object(workflow, '_step_moderation_checks') as step:
241 next(workflow)
242 step.assert_called_once_with()
244 def test_confirmation_checks_confirm_and_moderate_pre_confirmed(self):
245 # The subscription policy requires user confirmation and moderation,
246 # but their subscription is pre-confirmed.
247 self._mlist.subscription_policy = \
248 SubscriptionPolicy.confirm_then_moderate
249 anne = self._user_manager.create_address(self._anne)
250 workflow = SubscriptionWorkflow(self._mlist, anne,
251 pre_verified=True,
252 pre_confirmed=True)
253 workflow.run_thru('confirmation_checks')
254 with patch.object(workflow, '_step_moderation_checks') as step:
255 next(workflow)
256 step.assert_called_once_with()
258 def test_confirmation_checks_confirmation_needed(self):
259 # The subscription policy requires confirmation and the subscription
260 # is not pre-confirmed.
261 self._mlist.subscription_policy = SubscriptionPolicy.confirm
262 anne = self._user_manager.create_address(self._anne)
263 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
264 workflow.run_thru('confirmation_checks')
265 with patch.object(workflow, '_step_send_confirmation') as step:
266 next(workflow)
267 step.assert_called_once_with()
269 def test_confirmation_checks_moderate_confirmation_needed(self):
270 # The subscription policy requires confirmation and moderation, and the
271 # subscription is not pre-confirmed.
272 self._mlist.subscription_policy = \
273 SubscriptionPolicy.confirm_then_moderate
274 anne = self._user_manager.create_address(self._anne)
275 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
276 workflow.run_thru('confirmation_checks')
277 with patch.object(workflow, '_step_send_confirmation') as step:
278 next(workflow)
279 step.assert_called_once_with()
281 def test_moderation_checks_pre_approved(self):
282 # The subscription is pre-approved by the moderator.
283 self._mlist.subscription_policy = SubscriptionPolicy.moderate
284 anne = self._user_manager.create_address(self._anne)
285 workflow = SubscriptionWorkflow(self._mlist, anne,
286 pre_verified=True,
287 pre_approved=True)
288 workflow.run_thru('moderation_checks')
289 with patch.object(workflow, '_step_do_subscription') as step:
290 next(workflow)
291 step.assert_called_once_with()
293 def test_moderation_checks_approval_required(self):
294 # The moderator must approve the subscription.
295 self._mlist.subscription_policy = SubscriptionPolicy.moderate
296 anne = self._user_manager.create_address(self._anne)
297 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
298 workflow.run_thru('moderation_checks')
299 with patch.object(workflow, '_step_get_moderator_approval') as step:
300 next(workflow)
301 step.assert_called_once_with()
303 def test_do_subscription(self):
304 # An open subscription policy plus a pre-verified address means the
305 # user gets subscribed to the mailing list without any further
306 # confirmations or approvals.
307 self._mlist.subscription_policy = SubscriptionPolicy.open
308 anne = self._user_manager.create_address(self._anne)
309 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
310 # Consume the entire state machine.
311 list(workflow)
312 # Anne is now a member of the mailing list.
313 member = self._mlist.regular_members.get_member(self._anne)
314 self.assertEqual(member.address, anne)
316 def test_do_subscription_pre_approved(self):
317 # An moderation-requiring subscription policy plus a pre-verified and
318 # pre-approved address means the user gets subscribed to the mailing
319 # list without any further confirmations or approvals.
320 self._mlist.subscription_policy = SubscriptionPolicy.moderate
321 anne = self._user_manager.create_address(self._anne)
322 workflow = SubscriptionWorkflow(self._mlist, anne,
323 pre_verified=True,
324 pre_approved=True)
325 # Consume the entire state machine.
326 list(workflow)
327 # Anne is now a member of the mailing list.
328 member = self._mlist.regular_members.get_member(self._anne)
329 self.assertEqual(member.address, anne)
331 def test_do_subscription_pre_approved_pre_confirmed(self):
332 # An moderation-requiring subscription policy plus a pre-verified and
333 # pre-approved address means the user gets subscribed to the mailing
334 # list without any further confirmations or approvals.
335 self._mlist.subscription_policy = \
336 SubscriptionPolicy.confirm_then_moderate
337 anne = self._user_manager.create_address(self._anne)
338 workflow = SubscriptionWorkflow(self._mlist, anne,
339 pre_verified=True,
340 pre_confirmed=True,
341 pre_approved=True)
342 # Consume the entire state machine.
343 list(workflow)
344 # Anne is now a member of the mailing list.
345 member = self._mlist.regular_members.get_member(self._anne)
346 self.assertEqual(member.address, anne)
348 def test_do_subscription_cleanups(self):
349 # Once the user is subscribed, the token, and its associated pending
350 # database record will be removed from the database.
351 self._mlist.subscription_policy = SubscriptionPolicy.open
352 anne = self._user_manager.create_address(self._anne)
353 workflow = SubscriptionWorkflow(self._mlist, anne,
354 pre_verified=True,
355 pre_confirmed=True,
356 pre_approved=True)
357 # Cache the token.
358 token = workflow.token
359 # Consume the entire state machine.
360 list(workflow)
361 # Anne is now a member of the mailing list.
362 member = self._mlist.regular_members.get_member(self._anne)
363 self.assertEqual(member.address, anne)
364 # The workflow is done, so it has no token.
365 self.assertIsNone(workflow.token)
366 # The pendable associated with the token has been evicted.
367 self.assertIsNone(getUtility(IPendings).confirm(token, expunge=False))
368 # There is no saved workflow associated with the token.
369 new_workflow = SubscriptionWorkflow(self._mlist)
370 new_workflow.token = token
371 new_workflow.restore()
372 self.assertIsNone(new_workflow.which)
374 def test_moderator_approves(self):
375 # The workflow runs until moderator approval is required, at which
376 # point the workflow is saved. Once the moderator approves, the
377 # workflow resumes and the user is subscribed.
378 self._mlist.subscription_policy = SubscriptionPolicy.moderate
379 anne = self._user_manager.create_address(self._anne)
380 workflow = SubscriptionWorkflow(self._mlist, anne,
381 pre_verified=True,
382 pre_confirmed=True)
383 # Consume the entire state machine.
384 list(workflow)
385 # The user is not currently subscribed to the mailing list.
386 member = self._mlist.regular_members.get_member(self._anne)
387 self.assertIsNone(member)
388 # Create a new workflow with the previous workflow's save token, and
389 # restore its state. This models an approved subscription and should
390 # result in the user getting subscribed.
391 approved_workflow = SubscriptionWorkflow(self._mlist)
392 approved_workflow.token = workflow.token
393 approved_workflow.restore()
394 list(approved_workflow)
395 # Now the user is subscribed to the mailing list.
396 member = self._mlist.regular_members.get_member(self._anne)
397 self.assertEqual(member.address, anne)
399 def test_get_moderator_approval_log_on_hold(self):
400 # When the subscription is held for moderator approval, a message is
401 # logged.
402 mark = LogFileMark('mailman.subscribe')
403 self._mlist.subscription_policy = SubscriptionPolicy.moderate
404 anne = self._user_manager.create_address(self._anne)
405 workflow = SubscriptionWorkflow(self._mlist, anne,
406 pre_verified=True,
407 pre_confirmed=True)
408 # Consume the entire state machine.
409 list(workflow)
410 line = mark.readline()
411 self.assertEqual(
412 line[29:-1],
413 'test@example.com: held subscription request from anne@example.com'
416 def test_get_moderator_approval_notifies_moderators(self):
417 # When the subscription is held for moderator approval, and the list
418 # is so configured, a notification is sent to the list moderators.
419 self._mlist.admin_immed_notify = True
420 self._mlist.subscription_policy = SubscriptionPolicy.moderate
421 anne = self._user_manager.create_address(self._anne)
422 workflow = SubscriptionWorkflow(self._mlist, anne,
423 pre_verified=True,
424 pre_confirmed=True)
425 # Consume the entire state machine.
426 list(workflow)
427 items = get_queue_messages('virgin')
428 self.assertEqual(len(items), 1)
429 message = items[0].msg
430 self.assertEqual(message['From'], 'test-owner@example.com')
431 self.assertEqual(message['To'], 'test-owner@example.com')
432 self.assertEqual(
433 message['Subject'],
434 'New subscription request to Test from anne@example.com')
435 self.assertEqual(message.get_payload(), """\
436 Your authorization is required for a mailing list subscription request
437 approval:
439 For: anne@example.com
440 List: test@example.com""")
442 def test_get_moderator_approval_no_notifications(self):
443 # When the subscription is held for moderator approval, and the list
444 # is so configured, a notification is sent to the list moderators.
445 self._mlist.admin_immed_notify = False
446 self._mlist.subscription_policy = SubscriptionPolicy.moderate
447 anne = self._user_manager.create_address(self._anne)
448 workflow = SubscriptionWorkflow(self._mlist, anne,
449 pre_verified=True,
450 pre_confirmed=True)
451 # Consume the entire state machine.
452 list(workflow)
453 items = get_queue_messages('virgin')
454 self.assertEqual(len(items), 0)
456 def test_send_confirmation(self):
457 # A confirmation message gets sent when the address is not verified.
458 anne = self._user_manager.create_address(self._anne)
459 self.assertIsNone(anne.verified_on)
460 # Run the workflow to model the confirmation step.
461 workflow = SubscriptionWorkflow(self._mlist, anne)
462 list(workflow)
463 items = get_queue_messages('virgin')
464 self.assertEqual(len(items), 1)
465 message = items[0].msg
466 token = workflow.token
467 self.assertEqual(message['Subject'], 'confirm {}'.format(token))
468 self.assertEqual(
469 message['From'], 'test-confirm+{}@example.com'.format(token))
471 def test_send_confirmation_pre_confirmed(self):
472 # A confirmation message gets sent when the address is not verified
473 # but the subscription is pre-confirmed.
474 anne = self._user_manager.create_address(self._anne)
475 self.assertIsNone(anne.verified_on)
476 # Run the workflow to model the confirmation step.
477 workflow = SubscriptionWorkflow(self._mlist, anne, pre_confirmed=True)
478 list(workflow)
479 items = get_queue_messages('virgin')
480 self.assertEqual(len(items), 1)
481 message = items[0].msg
482 token = workflow.token
483 self.assertEqual(
484 message['Subject'], 'confirm {}'.format(workflow.token))
485 self.assertEqual(
486 message['From'], 'test-confirm+{}@example.com'.format(token))
488 def test_send_confirmation_pre_verified(self):
489 # A confirmation message gets sent even when the address is verified
490 # when the subscription must be confirmed.
491 self._mlist.subscription_policy = SubscriptionPolicy.confirm
492 anne = self._user_manager.create_address(self._anne)
493 self.assertIsNone(anne.verified_on)
494 # Run the workflow to model the confirmation step.
495 workflow = SubscriptionWorkflow(self._mlist, anne, pre_verified=True)
496 list(workflow)
497 items = get_queue_messages('virgin')
498 self.assertEqual(len(items), 1)
499 message = items[0].msg
500 token = workflow.token
501 self.assertEqual(
502 message['Subject'], 'confirm {}'.format(workflow.token))
503 self.assertEqual(
504 message['From'], 'test-confirm+{}@example.com'.format(token))
506 def test_do_confirm_verify_address(self):
507 # The address is not yet verified, nor are we pre-verifying. A
508 # confirmation message will be sent. When the user confirms their
509 # subscription request, the address will end up being verified.
510 anne = self._user_manager.create_address(self._anne)
511 self.assertIsNone(anne.verified_on)
512 # Run the workflow to model the confirmation step.
513 workflow = SubscriptionWorkflow(self._mlist, anne)
514 list(workflow)
515 # The address is still not verified.
516 self.assertIsNone(anne.verified_on)
517 confirm_workflow = SubscriptionWorkflow(self._mlist)
518 confirm_workflow.token = workflow.token
519 confirm_workflow.restore()
520 confirm_workflow.run_thru('do_confirm_verify')
521 # The address is now verified.
522 self.assertIsNotNone(anne.verified_on)
524 def test_do_confirmation_subscribes_user(self):
525 # Subscriptions to the mailing list must be confirmed. Once that's
526 # done, the user's address (which is not initially verified) gets
527 # subscribed to the mailing list.
528 self._mlist.subscription_policy = SubscriptionPolicy.confirm
529 anne = self._user_manager.create_address(self._anne)
530 self.assertIsNone(anne.verified_on)
531 workflow = SubscriptionWorkflow(self._mlist, anne)
532 list(workflow)
533 self.assertIsNone(self._mlist.regular_members.get_member(self._anne))
534 confirm_workflow = SubscriptionWorkflow(self._mlist)
535 confirm_workflow.token = workflow.token
536 confirm_workflow.restore()
537 list(confirm_workflow)
538 self.assertIsNotNone(anne.verified_on)
539 self.assertEqual(
540 self._mlist.regular_members.get_member(self._anne).address, anne)