1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2017-2023 by the Free Software Foundation, Inc.
4 # This file is part of Postorius.
6 # Postorius is free software: you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free
8 # Software Foundation, either version 3 of the License, or (at your option)
11 # Postorius is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
16 # You should have received a copy of the GNU General Public License along with
17 # Postorius. If not, see <http://www.gnu.org/licenses/>.
21 from django
.test
import TestCase
, override_settings
23 from postorius
.forms
.list_forms
import (
25 ChangeSubscriptionForm
,
28 ListAnonymousSubscribe
,
29 ListAutomaticResponsesForm
,
38 MessageAcceptanceForm
,
40 from postorius
.forms
.system
import AddBanForm
41 from postorius
.tests
.utils
import create_mock_list
44 class TestListSubscribe(TestCase
):
45 def test_required_fields_only(self
):
46 user_emails
= ['bob@example.com', 'anne@example.com']
48 user_emails
, None, None, dict(subscriber
='bob@example.com')
50 self
.assertTrue(form
.is_valid())
52 def test_email_is_only_from_choices(self
):
53 user_emails
= ['bob@example.com', 'anne@example.com']
58 dict(subscriber
='alice@example.com', display_name
='Alice'),
60 self
.assertFalse(form
.is_valid())
61 self
.assertTrue('subscriber' in form
.errors
)
63 'Select a valid choice.' in form
.errors
['subscriber'][0]
66 def test_subscribe_works(self
):
67 user_emails
= ['someone@example.com']
72 {'subscriber': 'someone@example.com', 'display_name': 'Someone'},
74 self
.assertTrue(form
.is_valid())
76 def test_subscribe_fails(self
):
77 user_emails
= ['someone@example.com']
82 {'subscriber': 'notaemail', 'display_name': 'Someone'},
84 self
.assertFalse(form
.is_valid())
85 self
.assertTrue('subscriber' in form
.errors
.keys())
87 form
.errors
['subscriber'][0],
88 'Select a valid choice.'
89 ' notaemail is not one of the available choices.',
92 def test_subscribe_validates_email(self
):
93 user_emails
= ['something']
98 {'subscriber': 'something', 'display_name': 'Someone'},
100 self
.assertFalse(form
.is_valid())
101 self
.assertTrue('subscriber' in form
.errors
.keys())
103 form
.errors
['subscriber'][0], 'Please enter a valid email address.'
106 def test_subscribe_with_user_uuid(self
):
107 user_emails
= ['aperson@example.com']
108 form
= ListSubscribe(
110 '00000000000000000000000000000004',
111 'aperson@example.com',
112 {'subscriber': '00000000000000000000000000000004'},
114 self
.assertEqual(form
.is_valid(), True)
116 def test_subscribe_with_invalid_uuid(self
):
117 # Test that invalid UUID returns errors.
118 user_emails
= ['aperson@example.com']
119 form
= ListSubscribe(
120 user_emails
, '4', 'aperson@example.com', {'subscriber': '4'}
122 self
.assertEqual(form
.is_valid(), False)
124 form
.errors
['subscriber'][0], 'Please enter a valid email address.'
127 def test_list_subscribe_with_delivery_mode(self
):
128 user_emails
= ['aperson@example.com']
129 form
= ListSubscribe(
131 '00000000000000000000000000000004',
132 'aperson@example.com',
134 'subscriber': '00000000000000000000000000000004',
135 'delivery_mode': 'plaintext_digests',
136 'delivery_status': 'by_user',
139 self
.assertTrue(form
.is_valid())
141 def test_list_subscribe_with_invalid_delivery_status(self
):
142 user_emails
= ['aperson@example.com']
143 form
= ListSubscribe(
145 '00000000000000000000000000000004',
146 'aperson@example.com',
148 'subscriber': '00000000000000000000000000000004',
149 'delivery_mode': 'plaintext_digest',
150 'delivery_status': 'by_bounces',
153 self
.assertFalse(form
.is_valid())
154 self
.assertTrue('delivery_status' in form
.errors
)
157 class TestChangeSubscription(TestCase
):
158 def test_subscription_changes_only_to_user_addresses(self
):
159 user_emails
= ['one@example.com', 'two@example.com']
160 form
= ChangeSubscriptionForm(
164 {'subscriber': 'abcd@d.com', 'member_id': '1235sdfsd314'},
166 self
.assertFalse(form
.is_valid())
167 self
.assertTrue('subscriber' in form
.errors
.keys())
169 form
.errors
['subscriber'][0],
170 'Select a valid choice. '
171 'abcd@d.com is not one of the available choices.',
174 def test_subscription_works(self
):
175 user_emails
= ['one@example.com', 'two@example.com']
176 form
= ChangeSubscriptionForm(
180 {'subscriber': 'two@example.com', 'member_id': '1235sdfsd314'},
182 self
.assertTrue(form
.is_valid())
184 def test_subscription_form_labels(self
):
185 user_emails
= ['one@example.com', 'two@example.com']
186 form
= ChangeSubscriptionForm(
187 user_emails
, None, None, {'member_id': '1235sdfsd314'}
189 self
.assertTrue(form
.is_valid())
190 self
.assertEqual(form
.fields
['subscriber'].label
, 'Select Email')
192 def test_form_validity(self
):
193 form
= ChangeSubscriptionForm(
194 ['email@example.com', 'john@example.com', 'doe@example.com'],
197 {'subscriber': 'email@example.com', 'member_id': '1235sdfsd314'},
199 self
.assertTrue(form
.is_valid())
201 def test_required_fields(self
):
202 # There is only one required fields.
203 form
= ChangeSubscriptionForm(
204 ['email@example.com', 'john@example.com', 'doe@example.com'],
207 {'member_id': '1235sdfsd314'},
209 self
.assertTrue(form
.is_valid())
211 def test_change_subscription_with_user_uuid(self
):
212 user_emails
= ['aperson@example.com']
213 form
= ChangeSubscriptionForm(
215 '00000000000000000000000000000004',
216 'aperson@example.com',
218 'subscriber': '00000000000000000000000000000004',
219 'member_id': '1235sdfsd314',
222 self
.assertEqual(form
.is_valid(), True)
224 def test_subscribe_with_invalid_uuid(self
):
225 # Test that invalid UUID returns errors.
226 user_emails
= ['aperson@example.com']
227 # UUIDs are 32bit and 4 is not a valid email.
228 form
= ChangeSubscriptionForm(
229 user_emails
, '4', 'aperson@example.com', {'subscriber': '4'}
231 self
.assertEqual(form
.is_valid(), False)
233 form
.errors
['subscriber'][0],
234 'Invalid: "4" should be either email or UUID',
238 class TestListNew(TestCase
):
239 def test_form_fields_list(self
):
241 ('mailman.most-desirable.org', 'mailman.most-desirable.org')
244 ('legacy-default', 'Ordinary discussion mailing list style.'),
245 ('legacy-announce', 'Announce only mailing list style.'),
252 'mail_host': 'mailman.most-desirable.org',
253 'list_owner': 'contact@mailman.most-desirable.org',
254 'advertised': 'True',
255 'list_style': 'legacy-default',
256 'description': 'The Most Desirable organization',
259 self
.assertTrue(form
.is_valid(), form
.errors
)
261 def test_form_fields_list_invalid(self
):
263 ('mailman.most-desirable.org', 'mailman.most-desirable.org')
266 ('legacy-default', 'Ordinary discussion mailing list style.'),
267 ('legacy-announce', 'Announce only mailing list style.'),
274 'mail_host': 'mailman.most-desirable.org',
275 'list_owner': 'mailman.most-desirable.org',
276 'advertised': 'abcd',
277 'list_style': 'defg',
278 'description': 'The Most Desirable organization',
281 self
.assertFalse(form
.is_valid())
282 # Test that all invalid fields are actually checked.
283 for field
in ('list_owner', 'advertised'):
284 self
.assertTrue(field
in form
.errors
)
286 'Enter a valid email address.' in form
.errors
['list_owner']
289 'Select a valid choice. abcd is not one of the available choices.'
290 in form
.errors
['advertised']
293 def test_form_without_domain_choices(self
):
298 'mail_host': 'mailman.most-desirable.org',
299 'list_owner': 'contact@mailman.most-desirable.org',
300 'advertised': 'True',
301 'description': 'The Most Desirable organization',
304 # Without domain choices, the form is not going to be valid.
305 self
.assertFalse(form
.is_valid())
307 form
.fields
['mail_host'].help_text
308 == 'Site admin has not created any domains' # noqa: W504
311 def test_listname_validation(self
):
313 ('mailman.most-desirable.org', 'mailman.most-desirable.org')
316 ('legacy-default', 'Ordinary discussion mailing list style.'),
317 ('legacy-announce', 'Announce only mailing list style.'),
324 'mail_host': 'mailman.most-desirable.org',
325 'list_owner': 'mailman.most-desirable.org',
326 'advertised': 'abcd',
327 'description': 'The Most Desirable organization',
330 self
.assertFalse(form
.is_valid())
331 self
.assertTrue('listname' in form
.errors
)
333 'Please enter a valid listname' in form
.errors
['listname']
336 @unittest.expectedFailure
337 def test_listname_validation_errors_sane(self
):
338 # This test is going to fail right now, but needs to be fixed.
340 [('mailman.most-desirable.org', 'mailman.most-desirable.org')],
343 'mail_host': 'mailman.most-desirable.org',
344 'list_owner': 'mailman.most-desirable.org',
345 'advertised': 'abcd',
346 'description': 'The Most Desirable organization',
349 self
.assertFalse(form
.is_valid())
350 self
.assertTrue('listname' in form
.errors
)
352 'Please enter a valid listname, "@" is not allowed in listname',
353 form
.errors
['listname'],
356 def test_form_fields_order(self
):
358 ('mailman.most-desirable.org', 'mailman.most-desirable.org')
361 ('legacy-default', 'Ordinary discussion mailing list style.'),
362 ('legacy-announce', 'Announce only mailing list style.'),
369 'mail_host': 'mailman.most-desirable.org',
370 'list_owner': 'mailman@most-desirable.org',
371 'list_style': 'legacy-default',
372 'advertised': 'True',
373 'description': 'The Most Desirable organization',
376 self
.assertTrue(form
.is_valid())
377 # The order of the fields should remain exactly like this.
391 class TestListIdentityForm(TestCase
):
392 def test_not_required_fields(self
):
393 # Only advertised is the required form field.
394 form
= ListIdentityForm(
396 'advertised': 'True',
400 self
.assertTrue(form
.is_valid(), form
.errors
)
402 def test_field_validations(self
):
403 form
= ListIdentityForm(
405 'advertised': 'abcd',
406 'description': 'This is the most desirable organization',
407 'info': 'This is a larger description of this mailing list.',
408 'display_name': 'Most Desirable Mailing List',
409 'subject_prefix': ' [Most Desirable] ',
410 'preferred_language': 'en',
411 'member_roster_visibility': 'public',
415 self
.assertFalse(form
.is_valid())
416 self
.assertTrue('advertised' in form
.errors
)
419 'Select a valid choice. abcd is not one of the available choices.' # noqa: E501
421 form
.errors
['advertised'],
423 # We shouldn't be removing trailing whitespaces, but we
424 # should remove the leading ones.
426 form
.cleaned_data
['subject_prefix'],
431 class TestListMassSubscription(TestCase
):
432 def test_all_valid_email_formats(self
):
433 form
= ListMassSubscription(
438 John Doe <jdoe@example.com>
439 "John Doe" <jdoe@example.com>
440 jdoe@example.com (John Doe)"""
443 self
.assertTrue(form
.is_valid())
445 def test_required_fields(self
):
446 form
= ListMassSubscription({'email': ' '})
447 self
.assertFalse(form
.is_valid())
448 self
.assertEqual(['This field is required.'], form
.errors
['emails'])
449 form
= ListMassSubscription({'email': '----'})
450 self
.assertFalse(form
.is_valid())
451 self
.assertEqual(['This field is required.'], form
.errors
['emails'])
455 'mass_subscription_pre_confirm': True,
456 'mass_subscription_pre_approve': True,
457 'mass_subscription_pre_verify': True,
458 'mass_subscription_invite': True,
461 def test_pre_verified_scenarios_with_override(self
):
462 # Test when setting is True
463 form
= ListMassSubscription({'emails': "abc@example.com"})
464 self
.assertTrue(form
.fields
['pre_confirmed'].initial
)
465 self
.assertTrue(form
.fields
['pre_approved'].initial
)
466 self
.assertTrue(form
.fields
['pre_verified'].initial
)
467 self
.assertTrue(form
.fields
['invitation'].initial
)
471 'mass_subscription_pre_confirm': False,
472 'mass_subscription_pre_approve': False,
473 'mass_subscription_pre_verify': False,
474 'mass_subscription_invite': False,
477 def test_pre_verified_scenarios_with_override_false(self
):
478 # Test when setting is False
479 form
= ListMassSubscription({'emails': "abc@example.com"})
480 self
.assertFalse(form
.fields
['pre_confirmed'].initial
)
481 self
.assertFalse(form
.fields
['pre_approved'].initial
)
482 self
.assertFalse(form
.fields
['pre_verified'].initial
)
483 self
.assertFalse(form
.fields
['invitation'].initial
)
485 @override_settings(POSTORIUS_DEFAULTS
={})
486 def test_pre_verified_scenarios_with_no_override(self
):
487 # Test when setting is empty
488 form
= ListMassSubscription({'emails': "abc@example.com"})
489 self
.assertTrue(form
.fields
['pre_confirmed'].initial
)
490 self
.assertTrue(form
.fields
['pre_approved'].initial
)
491 self
.assertFalse(form
.fields
['pre_verified'].initial
)
492 self
.assertFalse(form
.fields
['invitation'].initial
)
495 class TestListMassRemoval(TestCase
):
496 def test_all_valid_formats(self
):
497 form
= ListMassRemoval(
502 John Doe <jdoe@example.com>
503 "John Doe" <jdoe@example.com>
504 jdoe@example.com (John Doe)"""
507 self
.assertTrue(form
.is_valid())
510 class TestAddBanForm(TestCase
):
511 def test_form_validity(self
):
512 form
= AddBanForm({'email': 'jdoe@example.com'})
513 self
.assertTrue(form
.is_valid())
515 def test_missing_fields_errors(self
):
516 form
= AddBanForm({})
517 self
.assertFalse(form
.is_valid())
518 self
.assertTrue('email' in form
.errors
)
520 form
.errors
['email'], ['Please enter an email address.']
523 @unittest.expectedFailure
524 def test_invalid_fields_type(self
):
525 # Valid values for email is either a regexp or an email address.
526 # However, this is currently not validated by the form.
527 form
= AddBanForm({'email': 'invalid@'})
528 self
.assertFalse(form
.is_valid())
529 self
.assertTrue('email' in form
.errors
)
531 form
.errors
['email'], ['Please enter a valid email address.']
535 class TestListHeaderMatchForm(TestCase
):
536 def test_form_validity(self
):
537 # Test by putting only required fields.
538 form
= ListHeaderMatchForm(
539 {'header': 'To', 'pattern': 'value@example.com'}
541 self
.assertTrue(form
.is_valid())
543 form
= ListHeaderMatchForm(
546 'pattern': 'value@example.com',
550 self
.assertTrue(form
.is_valid())
551 # Defer is not a valid action, so validation should fail here.
552 form
= ListHeaderMatchForm(
553 {'header': 'To', 'pattern': 'value@example.com', 'action': 'defer'}
555 self
.assertFalse(form
.is_valid())
556 self
.assertTrue('action' in form
.errors
)
559 class TestMemberModeration(TestCase
):
560 def test_moderation_action_validity(self
):
561 form
= MemberModeration({'moderation_action': 'moderation'})
562 self
.assertFalse(form
.is_valid())
563 self
.assertTrue('moderation_action' in form
.errors
)
565 form
.errors
['moderation_action'],
567 'Select a valid choice. moderation is not one of the available choices.' # noqa: E501
571 def test_required_fields(self
):
572 form
= MemberModeration({})
573 self
.assertTrue(form
.is_valid())
576 class TestListAutomaticResponsesForm(TestCase
):
580 'autoresponse_owner_text',
581 'autorespond_postings',
582 'autoresponse_postings_text',
583 'autorespond_requests',
584 'autoresponse_request_text',
585 'autoresponse_grace_period',
586 'send_welcome_message',
587 'welcome_message_uri',
588 'send_goodbye_message',
589 'goodbye_message_uri',
590 'admin_immed_notify',
591 'admin_notify_mchanges',
594 def prepare_formdata(self
, values
):
598 for key
, val
in zip(self
.fields
, values
)
603 def test_required_fields_only(self
):
605 'respond_and_continue',
607 'respond_and_continue',
609 'respond_and_continue',
618 formdata
= self
.prepare_formdata(values
)
619 form
= ListAutomaticResponsesForm(formdata
, mlist
=None)
620 self
.assertTrue(form
.is_valid())
622 def test_all_values(self
):
624 'respond_and_continue',
626 'respond_and_continue',
628 'respond_and_continue',
632 'http://example.com/welcome_text',
634 'http://example.com/goodbye_message',
638 formdata
= self
.prepare_formdata(values
)
639 form
= ListAutomaticResponsesForm(formdata
, mlist
=None)
641 self
.assertTrue(form
.is_valid())
644 class TestAlterMessageForm(TestCase
):
648 'collapse_alternatives',
649 'convert_html_to_plaintext',
651 'include_rfc2369_headers',
655 'first_strip_reply_to',
656 'reply_goes_to_list',
660 def prepare_formdata(self
, values
):
664 for key
, val
in zip(self
.fields
, values
)
670 class TestDMARCMitigationsForm(TestCase
):
671 def test_required_fields(self
):
672 # All fields in the form are optional, so an empty form should be
674 form
= DMARCMitigationsForm({}, mlist
=None)
675 self
.assertTrue(form
.is_valid())
677 def test_all_fields(self
):
679 dmarc_mitigate_action
='munge_from',
680 dmarc_mitigate_unconditionally
='True',
681 dmarc_addresses
='user@example.com\n^.*@example.net',
682 dmarc_moderation_notice
='This is a moderation notice',
683 dmarc_wrapped_message_text
='This is wrapped message text',
685 form
= DMARCMitigationsForm(formdata
, mlist
=None)
686 self
.assertTrue(form
.is_valid())
689 class TestDigestSettingsForm(TestCase
):
690 def test_required_fields(self
):
691 form
= DigestSettingsForm({}, mlist
=None)
692 self
.assertFalse(form
.is_valid())
693 self
.assertTrue('digest_size_threshold' in form
.errors
)
695 form
.errors
['digest_size_threshold'], ['This field is required.']
697 form
= DigestSettingsForm({'digest_size_threshold': 40}, mlist
=None)
698 self
.assertTrue(form
.is_valid())
700 def test_all_fields(self
):
702 digests_enabled
='True',
703 digests_send_periodic
='True',
704 digests_volume_frequency
='daily',
705 digest_size_threshold
='10',
707 form
= DigestSettingsForm(formdata
, mlist
=None)
708 self
.assertTrue(form
.is_valid())
711 class TestMessageAcceptanceForm(TestCase
):
714 'acceptable_aliases',
715 'require_explicit_destination',
717 'default_member_action',
718 'default_nonmember_action',
720 'max_num_recipients',
723 def prepare_formdata(self
, values
):
727 for key
, val
in zip(self
.fields
, values
)
732 def test_required_fields(self
):
733 # Without any fields, form should not be valid.
734 form
= MessageAcceptanceForm({}, mlist
=None)
735 self
.assertFalse(form
.is_valid())
736 # Now lets try with only required fields.
737 values
= (None, None, None, 'hold', 'hold', 40, 100)
738 form
= MessageAcceptanceForm(self
.prepare_formdata(values
), mlist
=None)
740 self
.assertTrue(form
.is_valid())
742 def test_all_fields(self
):
746 class TestArchiveSettingsForm(TestCase
):
748 self
.mlist
= create_mock_list()
749 archivers
= {'pipermail': True, 'hyperkitty': True}
750 self
.mlist
.archivers
.keys
.side_effect
= archivers
.keys
751 self
.mlist
.archivers
.__getitem
__.side_effect
= archivers
.__getitem
__
752 self
.mlist
.archivers
.__iter
__.side_effect
= archivers
.__iter
__
753 self
.mlist
.archivers
.__contains
__.side_effect
= archivers
.__contains
__
755 def test_required_fields(self
):
756 # First try without any fields.
757 form
= ArchiveSettingsForm({}, mlist
=self
.mlist
)
758 self
.assertFalse(form
.is_valid())
759 self
.assertTrue('archive_policy' in form
.errors
)
760 # Now, with only required fields, this should be a valid form.
761 form
= ArchiveSettingsForm(
762 dict(archive_policy
='public'), mlist
=self
.mlist
764 self
.assertTrue(form
.is_valid())
766 def test_all_fields(self
):
768 archive_policy
='public', archivers
=['pipermail', 'hyperkitty']
770 form
= ArchiveSettingsForm(formdata
, mlist
=self
.mlist
)
771 self
.assertTrue(form
.is_valid())
773 def test_setup_archivers_populated(self
):
775 archive_policy
='public', archivers
=['pipermail', 'hyperkitty']
777 form
= ArchiveSettingsForm(
778 formdata
, mlist
=self
.mlist
, initial
={'archivers': None}
780 self
.assertTrue(form
.is_valid())
782 form
.initial
['archivers'], ['hyperkitty', 'pipermail']
785 def test_optional_archive_rendering_field(self
):
786 # Test that archive rendering mode is set only when Hyperkitty is
788 formdata
= dict(archive_policy
='public', archivers
=['pipermail'])
789 form
= ArchiveSettingsForm(
790 formdata
, mlist
=self
.mlist
, initial
={'archivers': None}
792 self
.assertTrue(form
.is_valid())
793 self
.assertFalse('archive_rendering_mode' in form
.fields
)
794 # With Hyperkitty installed, the field should show up.
795 with unittest
.mock
.patch(
796 'postorius.forms.list_forms.apps.is_installed', return_value
=True
798 formdata
= dict(archive_policy
='public', archivers
=['pipermail'])
799 form
= ArchiveSettingsForm(
800 formdata
, mlist
=self
.mlist
, initial
={'archivers': None}
802 self
.assertTrue(form
.is_valid())
803 self
.assertTrue('archive_rendering_mode' in form
.fields
)
806 class TestMemberPolicyForm(TestCase
):
807 def test_required_fields(self
):
808 form
= MemberPolicyForm({}, mlist
=None)
809 self
.assertFalse(form
.is_valid())
810 self
.assertTrue('subscription_policy' in form
.errors
)
811 self
.assertTrue('unsubscription_policy' in form
.errors
)
812 form
= MemberPolicyForm(
814 subscription_policy
='confirm', unsubscription_policy
='confirm'
818 self
.assertTrue(form
.is_valid())
821 class TestListAnonymousSubscribe(TestCase
):
822 def test_required_fields_only(self
):
823 form
= ListAnonymousSubscribe(dict(email
='bob@exmaple.com'))
824 self
.assertTrue(form
.is_valid())
826 def test_email_is_validated(self
):
827 form
= ListAnonymousSubscribe(dict(email
='invalid'))
828 self
.assertFalse(form
.is_valid())
829 self
.assertTrue('email' in form
.errors
)
831 form
.errors
['email'], ['Please enter a valid email address.']
834 def test_all_fields(self
):
835 form
= ListAnonymousSubscribe(
836 dict(email
='bob@example.com', display_name
='Bob')
838 self
.assertTrue(form
.is_valid())