1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-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)
10 # Postorius 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 # Postorius. If not, see <http://www.gnu.org/licenses/>.
19 from unittest
import expectedFailure
21 from django
.contrib
.auth
.models
import User
22 from django
.test
.utils
import override_settings
23 from django
.urls
import reverse
25 from allauth
.account
.models
import EmailAddress
26 from django_mailman3
.lib
.mailman
import get_mailman_user
28 from postorius
.forms
import ChangeSubscriptionForm
, UserPreferences
29 from postorius
.models
import Mailman404Error
, MailmanUser
30 from postorius
.tests
.utils
import ViewTestCase
33 class MailmanUserTest(ViewTestCase
):
35 Tests for the mailman user preferences settings page.
39 super(MailmanUserTest
, self
).setUp()
40 self
.domain
= self
.mm_client
.create_domain('example.com')
41 self
.foo_list
= self
.domain
.create_list('foo')
42 self
.foo_list
.send_welcome_message
= False
43 self
.user
= User
.objects
.create_user(
44 'user', 'user@example.com', 'testpass'
46 EmailAddress
.objects
.create(
47 user
=self
.user
, email
=self
.user
.email
, verified
=True, primary
=True
49 self
.mm_user
= get_mailman_user(self
.user
)
51 def test_address_preferences_not_logged_in(self
):
52 self
.assertRedirectsToLogin(reverse('user_address_preferences'))
54 def test_subscriptions_not_logged_in(self
):
55 self
.assertRedirectsToLogin(reverse('ps_user_profile'))
57 def test_subscriptions_logged_in(self
):
58 self
.client
.login(username
='user', password
='testpass')
59 response
= self
.client
.get(reverse('ps_user_profile'))
60 self
.assertEqual(response
.status_code
, 200)
62 def test_address_based_preferences(self
):
63 self
.client
.login(username
='user', password
='testpass')
64 self
.mm_user
.add_address('user2@example.com')
65 self
.mm_user
.add_address('user3@example.com')
66 response
= self
.client
.get(reverse('user_address_preferences'))
67 self
.assertEqual(response
.status_code
, 200)
68 self
.assertEqual(len(response
.context
['formset']), 3)
70 # this test needs re-thinking with the way we've hacked preferences
71 # it has been disabled for now
72 # def test_preferences_none(self):
73 # # Mailman does not accept None values for boolean preferences. When
74 # # those preferences are unset, they must be excluded from the POST
76 # self.client.login(username='user', password='testpass')
77 # self.foo_list.subscribe(self.user.email, pre_verified=True,
78 # pre_confirmed=True, pre_approved=True)
80 # 'receive_own_postings', 'acknowledge_posts',
81 # 'hide_address', 'receive_list_copy',
83 # # Prepare a Preferences subclass that will check the POST data
84 # import mailmanclient._client
86 # class TestingPrefs(mailmanclient._client.Preferences):
90 # for pref in prefs_with_none:
91 # self.testcase.assertNotIn(pref, self._changed_rest_data)
92 # # Now check the relevant URLs
93 # with patch('mailmanclient._client.Preferences') as pref_class:
94 # pref_class.side_effect = TestingPrefs
97 # reverse('user_mailmansettings'),
98 # reverse('user_list_options',
99 # args=[self.foo_list.list_id]),
101 # response = self.client.post(
102 # url, dict((pref, None) for pref in prefs_with_none))
103 # self.assertEqual(response.status_code, 302)
105 # for url in ('user_address_preferences',
106 # 'user_subscription_preferences'):
109 # ('form-0-%s' % pref, None)
110 # for pref in prefs_with_none)
112 # 'form-TOTAL_FORMS': '1',
113 # 'form-INITIAL_FORMS': '0',
114 # 'form-MAX_NUM_FORMS': ''
116 # response = self.client.post(url, post_data)
117 # self.assertEqual(response.status_code, 302)
119 @override_settings(AUTOCREATE_MAILMAN_USER
=False)
120 def test_subscriptions_no_mailman_user(self
):
121 # Existing Django users without a corresponding Mailman user must not
122 # cause views to crash.
123 user
= User
.objects
.create_user(
124 'old-user', 'old-user@example.com', 'testpass'
126 EmailAddress
.objects
.create(user
=user
, email
=user
.email
, verified
=True)
127 self
.client
.login(username
='old-user', password
='testpass')
129 Mailman404Error
, MailmanUser
.objects
.get
, address
=user
.email
131 response
= self
.client
.get(reverse('ps_user_profile'))
132 self
.assertEqual(response
.status_code
, 200)
133 # The Mailman user must have been created
134 self
.assertIsNotNone(MailmanUser
.objects
.get(address
=user
.email
))
136 def test_presence_of_form_in_user_global_settings(self
):
137 self
.client
.login(username
='user', password
='testpass')
138 response
= self
.client
.get(reverse('user_mailmansettings'))
139 self
.assertEqual(response
.status_code
, 200)
140 self
.assertIsInstance(response
.context
['form'], UserPreferences
)
142 def test_presence_of_form_in_user_subscription_preferences(self
):
143 self
.client
.login(username
='user', password
='testpass')
144 self
.foo_list
.subscribe(
150 response
= self
.client
.get(reverse('user_subscription_preferences'))
151 self
.assertEqual(response
.status_code
, 200)
152 self
.assertIsNotNone(response
.context
['formset'])
153 self
.assertEqual(len(response
.context
['formset']), 1)
155 def test_presence_of_form_in_user_list_options(self
):
156 self
.client
.login(username
='user', password
='testpass')
157 member
= self
.foo_list
.subscribe(
163 response
= self
.client
.get(
164 reverse('user_list_options', args
=[member
.member_id
])
166 self
.assertEqual(response
.status_code
, 200)
167 self
.assertIsInstance(response
.context
['form'], UserPreferences
)
168 self
.assertIsInstance(
169 response
.context
['change_subscription_form'],
170 ChangeSubscriptionForm
,
173 def test_list_options_shows_all_addresses(self
):
174 self
.client
.login(username
='user', password
='testpass')
175 member
= self
.foo_list
.subscribe(
182 EmailAddress
.objects
.create(
183 user
=self
.user
, email
='anotheremail@example.com', verified
=True
185 user
= self
.mm_client
.get_user('user@example.com')
186 address
= user
.add_address('anotheremail@example.com')
189 response
= self
.client
.get(
190 reverse('user_list_options', args
=[member
.member_id
])
192 self
.assertEqual(response
.status_code
, 200)
193 self
.assertContains(response
, 'anotheremail@example.com')
195 def _set_primary(self
, user
, mm_user
):
196 for addr
in mm_user
.addresses
:
198 mm_user
.preferred_address
= user
.email
200 def test_change_subscription_to_new_email(self
):
201 # Test that we can change subscription to a new email.
202 self
.client
.login(username
='user', password
='testpass')
203 user
= self
.mm_client
.get_user('user@example.com')
204 EmailAddress
.objects
.create(
205 user
=self
.user
, email
='anotheremail@example.com', verified
=True
207 address
= user
.add_address('anotheremail@example.com')
209 member
= self
.foo_list
.subscribe(
215 # Now, first verify that the list_options page has all the emails.
217 response
= self
.client
.get(
218 reverse('user_list_options', args
=[member
.member_id
])
220 self
.assertContains(response
, 'anotheremail@example.com')
223 '<option value="user@example.com"'
224 ' selected>user@example.com</option>',
226 member
= self
.mm_client
.get_member(
227 self
.foo_list
.list_id
, 'user@example.com'
229 self
.assertIsNotNone(member
)
230 # Initially, all preferences are none. Let's set it to something
232 self
.assertIsNone(member
.preferences
.get('acknowledge_posts'))
233 member
.preferences
['acknowledge_posts'] = True
234 member
.preferences
.save()
235 # now, let's switch the subscription to a new user.
236 response
= self
.client
.post(
237 reverse('change_subscription', args
=(self
.foo_list
.list_id
,)),
239 'subscriber': 'anotheremail@example.com',
240 'member_id': member
.member_id
,
243 self
.assertEqual(response
.status_code
, 302)
244 self
.assertHasSuccessMessage(response
)
245 member_new
= self
.mm_client
.get_member(
246 self
.foo_list
.list_id
, 'anotheremail@example.com'
248 self
.assertIsNotNone(member_new
)
249 # There is no 'member_id' attribute, so we simply use the self_link to
250 # compare and make sure that the Member object is same.
251 self
.assertEqual(member
.self_link
, member_new
.self_link
)
252 self
.assertEqual(member_new
.subscription_mode
, 'as_address')
253 # Also, assert that the new member's preferences are same.
255 member
.preferences
['acknowledge_posts'],
256 member_new
.preferences
['acknowledge_posts'],
259 def test_change_subscription_to_from_primary_address(self
):
260 # Test that we can change subscription to a new email.
261 self
.client
.login(username
='user', password
='testpass')
262 user
= self
.mm_client
.get_user('user@example.com')
263 self
._set
_primary
(self
.user
, user
)
264 member
= self
.foo_list
.subscribe(
270 # Now, first verify that the list_options page has the primary address.
271 response
= self
.client
.get(
272 reverse('user_list_options', args
=[member
.member_id
])
274 self
.assertContains(response
, 'Primary Address (user@example.com)')
277 '<option value="user@example.com" '
278 'selected>user@example.com</option>',
280 member
= self
.mm_client
.get_member(
281 self
.foo_list
.list_id
, 'user@example.com'
283 self
.assertIsNotNone(member
)
284 # Initially, all preferences are none. Let's set it to something
286 self
.assertIsNone(member
.preferences
.get('acknowledge_posts'))
287 member
.preferences
['acknowledge_posts'] = True
288 member
.preferences
.save()
289 # now, let's switch the subscription to a new user.
290 response
= self
.client
.post(
291 reverse('change_subscription', args
=(self
.foo_list
.list_id
,)),
292 {'subscriber': str(user
.user_id
), 'member_id': member
.member_id
},
294 self
.assertEqual(response
.status_code
, 302)
295 self
.assertHasSuccessMessage(response
)
296 new_member
= self
.mm_client
.get_member(
297 self
.foo_list
.list_id
, 'user@example.com'
299 self
.assertIsNotNone(new_member
)
300 self
.assertEqual(new_member
.subscription_mode
, 'as_user')
301 # we can't compare against the preferences object of `member` since the
302 # resource is now Deleted due to unsubscribe-subscribe dance.
303 self
.assertEqual(new_member
.preferences
['acknowledge_posts'], True)
305 def test_already_subscribed(self
):
306 self
.client
.login(username
='user', password
='testpass')
308 member
= self
.foo_list
.subscribe(
314 # Now, first verify that the list_options page has all the emails.
316 response
= self
.client
.get(
317 reverse('user_list_options', args
=[member
.member_id
])
321 '<option value="user@example.com" '
322 'selected>user@example.com</option>',
324 # now, let's switch the subscription to a new user.
325 response
= self
.client
.post(
326 reverse('change_subscription', args
=(self
.foo_list
.list_id
,)),
327 {'subscriber': 'user@example.com', 'member_id': member
.member_id
},
329 self
.assertEqual(response
.status_code
, 302)
330 error
= self
.assertHasErrorMessage(response
)
331 self
.assertIn('You are already subscribed', error
)
333 def test_already_subscribed_with_primary_address(self
):
334 # Test that we can change subscription to a new email.
335 self
.client
.login(username
='user', password
='testpass')
336 user
= self
.mm_client
.get_user('user@example.com')
337 self
._set
_primary
(self
.user
, user
)
338 member
= self
.foo_list
.subscribe(
344 # Now, first verify that the list_options page has the primary address.
345 response
= self
.client
.get(
346 reverse('user_list_options', args
=[member
.member_id
])
351 '<option value="{}" selected>Primary Address (user@example.com)' # noqa: E501
353 ).format(user
.user_id
),
355 # now, let's switch the subscription to a new user.
356 response
= self
.client
.post(
357 reverse('change_subscription', args
=(self
.foo_list
.list_id
,)),
358 {'subscriber': str(user
.user_id
), 'member_id': member
.member_id
},
360 self
.assertEqual(response
.status_code
, 302)
361 error
= self
.assertHasErrorMessage(response
)
362 self
.assertIn('You are already subscribed', error
)
364 def test_list_options_sets_preferred_address(self
):
365 # Test that preferred address is set.
366 mm_user
= get_mailman_user(self
.user
)
367 self
.assertIsNone(mm_user
.preferred_address
)
368 member
= self
.foo_list
.subscribe(
374 self
.client
.login(username
='user', password
='testpass')
375 self
.client
.get(reverse('user_list_options', args
=[member
.member_id
]))
376 self
.assertEqual(mm_user
.preferred_address
.email
, self
.user
.email
)
379 def test_access_list_options_multiple_subscriptions(self
):
380 # Test that when multiple addresses of a single user are subscribed to
381 # the same list that they are able to access them.
382 # This test is now expected to fail due to
383 # https://gitlab.com/mailman/mailman/-/merge_requests/997 which no
384 # longer permits a User whose primary address is the same as a
385 # subscribed Address to subscribe and vice versa.
386 mm_user
= get_mailman_user(self
.user
)
387 self
.assertIsNone(mm_user
.preferred_address
)
388 self
._set
_primary
(self
.user
, mm_user
)
389 # Subscribe the user twice, once with their address and then with their
391 member_primary
= self
.foo_list
.subscribe(
397 member_addr
= self
.foo_list
.subscribe(
403 self
.assertEqual(len(self
.foo_list
.members
), 2)
405 self
.client
.login(username
='user', password
='testpass')
406 response
= self
.client
.get(reverse('user_subscription_preferences'))
407 self
.assertEqual(response
.status_code
, 200)
408 # There should be list options for two users.
409 self
.assertContains(response
, 'Primary Address')
410 self
.assertContains(response
, 'user@example.com')
411 # Get the list options for both memberships and check subscriber ==
413 response
= self
.client
.get(
414 reverse('user_list_options', args
=[member_addr
.member_id
])
416 self
.assertEqual(response
.status_code
, 200)
417 subscriber
= response
.context
.get(
418 'change_subscription_form'
419 ).initial
.get('subscriber')
420 self
.assertEqual(subscriber
, member_addr
.address
.email
)
421 # Check subscriber == member_id
422 response
= self
.client
.get(
423 reverse('user_list_options', args
=[member_primary
.member_id
])
425 self
.assertEqual(response
.status_code
, 200)
426 subscriber
= response
.context
.get(
427 'change_subscription_form'
428 ).initial
.get('subscriber')
429 self
.assertEqual(subscriber
, member_primary
.user
.user_id
)
431 def test_access_list_options_other_member(self
):
432 # Test that a user can't access member options for a different user.
433 member_addr
= self
.foo_list
.subscribe(
439 another_member
= self
.foo_list
.subscribe(
440 'anoter@example.com',
445 self
.client
.login(username
='user', password
='testpass')
446 response
= self
.client
.get(
447 reverse('user_list_options', args
=[another_member
.member_id
])
449 self
.assertEqual(response
.status_code
, 404)
450 # But they can access their own.
451 response
= self
.client
.get(
452 reverse('user_list_options', args
=[member_addr
.member_id
])
454 self
.assertEqual(response
.status_code
, 200)
457 class TestListAllUsers(ViewTestCase
):
461 self
.mm_client
.create_user(
462 'user{}@example.com'.format(i
), 'testpass', 'User {}'.format(i
)
464 self
.su
= User
.objects
.create_superuser('su', 'su@example.com', 'pass')
466 def test_get_all_users_forbidden(self
):
467 response
= self
.client
.get(reverse('list_users'))
468 self
.assertEqual(response
.status_code
, 403)
470 def test_get_all_users_as_superuser(self
):
471 self
.client
.force_login(self
.su
)
472 url
= reverse('list_users')
473 response
= self
.client
.get(url
)
474 self
.assertEqual(response
.status_code
, 200)
475 # default page size is 10, so we will get 10.
476 self
.assertEqual(len(response
.context
['all_users']), 10)
477 # lets get all users by setting count.
479 response
= self
.client
.get(url
)
480 self
.assertEqual(response
.status_code
, 200)
481 # default page size is 10, so we will get 10.
482 self
.assertEqual(len(response
.context
['all_users']), 11)
484 def test_search_user(self
):
485 self
.client
.force_login(self
.su
)
488 return reverse('list_users') + '?q={}'.format(query
)
490 response
= self
.client
.get(_get_url('0@e'))
491 self
.assertEqual(response
.status_code
, 200)
492 # It should be two users, user0@example.com and user10@example.com
493 self
.assertEqual(len(response
.context
['all_users']), 2)
494 # search with display name.
495 response
= self
.client
.get(_get_url('User 7'))
496 self
.assertEqual(response
.status_code
, 200)
497 # It should be one user, user7@example.com, but it should search with
498 # display name because of the space.
499 self
.assertEqual(len(response
.context
['all_users']), 1)
501 response
.context
['all_users'][0].display_name
, 'User 7'
505 class TestManageUser(ViewTestCase
):
508 self
.user
= self
.mm_client
.create_user('user@example.com', 'testpass')
509 self
.su
= User
.objects
.create_superuser('su', 'su@example.com', 'pass')
510 dom
= self
.mm_client
.create_domain('example.com')
511 self
.mlist
= dom
.create_list('test')
512 self
.mlist
.subscribe(
513 'user@example.com', pre_verified
=True, pre_confirmed
=True
516 def test_get_all_users_forbidden(self
):
517 response
= self
.client
.get(
518 reverse('manage_user', args
=[self
.user
.user_id
])
520 self
.assertEqual(response
.status_code
, 403)
522 def test_get_manage_user(self
):
523 self
.client
.force_login(self
.su
)
524 response
= self
.client
.get(
525 reverse('manage_user', args
=[self
.user
.user_id
])
527 self
.assertEqual(response
.status_code
, 200)
528 self
.assertEqual(response
.context
['auser'].user_id
, self
.user
.user_id
)
529 user_form
= response
.context
['user_form']
530 self
.assertEqual(user_form
.user
.user_id
, self
.user
.user_id
)
531 addr_forms
= response
.context
['addresses']
532 self
.assertEqual(len(addr_forms
.forms
), 1)
533 subform
= response
.context
['subscriptions']
534 self
.assertEqual(len(subform
.forms
), 1)
535 self
.assertIsNone(response
.context
['django_user'])
536 self
.assertIsNone(response
.context
['change_password'])
538 def test_get_manage_user_with_django_user(self
):
539 user
= User
.objects
.create_user(username
='tester', password
='test')
540 for addr
in self
.user
.addresses
:
541 EmailAddress
.objects
.create(user
=user
, email
=addr
.email
)
542 self
.client
.force_login(self
.su
)
543 response
= self
.client
.get(
544 reverse('manage_user', args
=[self
.user
.user_id
])
546 self
.assertEqual(response
.status_code
, 200)
547 self
.assertEqual(response
.context
['auser'].user_id
, self
.user
.user_id
)
548 user_form
= response
.context
['user_form']
549 self
.assertEqual(user_form
.user
.user_id
, self
.user
.user_id
)
550 addr_forms
= response
.context
['addresses']
551 self
.assertEqual(len(addr_forms
.forms
), 1)
552 subform
= response
.context
['subscriptions']
553 self
.assertEqual(len(subform
.forms
), 1)
554 self
.assertEqual(response
.context
['django_user'], user
)
555 self
.assertIsNotNone(response
.context
['change_password'])
557 def test_update_display_name(self
):
558 self
.client
.force_login(self
.su
)
559 response
= self
.client
.post(
560 reverse('manage_user', args
=[self
.user
.user_id
]),
561 data
={'display_name': 'My User', 'user_form': 'Update'},
564 self
.assertEqual(response
.status_code
, 200)
565 self
.assertIn('Successfully updated user.', response
.content
.decode())
567 def test_update_user_address(self
):
568 self
.client
.force_login(self
.su
)
569 addresses
= self
.user
.addresses
570 addresses
[0].unverify()
571 self
.assertFalse(self
.user
.addresses
[0].verified
)
573 'form-TOTAL_FORMS': '1',
574 'form-INITIAL_FORMS': '1',
575 'form-MIN_NUM_FORMS': '1',
576 'form-MAX_NUM_FORMS': '1',
577 'form-0-verified': 'on',
578 'address_form': 'Update',
580 response
= self
.client
.post(
581 reverse('manage_user', args
=[self
.user
.user_id
]),
585 self
.assertEqual(response
.status_code
, 200)
586 self
.assertTrue(self
.user
.addresses
[0].verified
)
588 'Successfully updated addresses user@example.com',
589 response
.content
.decode(),
592 def test_update_user_subscriptions(self
):
593 self
.client
.force_login(self
.su
)
595 'form-TOTAL_FORMS': '1',
596 'form-INITIAL_FORMS': '1',
597 'form-MIN_NUM_FORMS': '0',
598 'form-MAX_NUM_FORMS': '1',
599 'form-0-moderation_action': 'discard',
600 'form-0-delivery_mode': 'mime_digests',
601 'subs_form': 'Update',
603 response
= self
.client
.post(
604 reverse('manage_user', args
=[self
.user
.user_id
]),
608 self
.assertEqual(response
.status_code
, 200)
610 'Successfully updated memberships for test.example.com',
611 response
.content
.decode(),
614 def test_update_user_password(self
):
615 user
= User
.objects
.create_user(
616 username
='myuser', password
='mypassword'
618 EmailAddress
.objects
.create(user
=user
, email
='user@example.com')
620 self
.client
.login(username
='myuser', password
='mypassword')
622 self
.client
.force_login(self
.su
)
624 'change_password': 'Update',
625 'password1': 'newpsdsd1987',
626 'password2': 'newpsdsd1987',
628 response
= self
.client
.post(
629 reverse('manage_user', args
=[self
.user
.user_id
]), data
=data
631 self
.assertEqual(response
.status_code
, 302)
632 # Verify by tring to login.
634 self
.client
.login(username
='myuser', password
='newpsdsd1987')