chore: Bump copyright year
[mailman-postorious.git] / src / postorius / tests / mailman_api_tests / test_user.py
blob16864578577fb2334a5d7ed26cfee9a81fc66a7d
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)
9 # any later version.
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
13 # more details.
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):
34 """
35 Tests for the mailman user preferences settings page.
36 """
38 def setUp(self):
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
75 # # data.
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)
79 # prefs_with_none = (
80 # 'receive_own_postings', 'acknowledge_posts',
81 # 'hide_address', 'receive_list_copy',
82 # )
83 # # Prepare a Preferences subclass that will check the POST data
84 # import mailmanclient._client
86 # class TestingPrefs(mailmanclient._client.Preferences):
87 # testcase = self
89 # def save(self):
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
95 # # Simple forms
96 # for url in (
97 # reverse('user_mailmansettings'),
98 # reverse('user_list_options',
99 # args=[self.foo_list.list_id]),
100 # ):
101 # response = self.client.post(
102 # url, dict((pref, None) for pref in prefs_with_none))
103 # self.assertEqual(response.status_code, 302)
104 # # Formsets
105 # for url in ('user_address_preferences',
106 # 'user_subscription_preferences'):
107 # url = reverse(url)
108 # post_data = dict(
109 # ('form-0-%s' % pref, None)
110 # for pref in prefs_with_none)
111 # post_data.update({
112 # 'form-TOTAL_FORMS': '1',
113 # 'form-INITIAL_FORMS': '0',
114 # 'form-MAX_NUM_FORMS': ''
115 # })
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')
128 self.assertRaises(
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(
145 self.user.email,
146 pre_verified=True,
147 pre_confirmed=True,
148 pre_approved=True,
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(
158 self.user.email,
159 pre_verified=True,
160 pre_confirmed=True,
161 pre_approved=True,
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(
176 self.user.email,
177 pre_verified=True,
178 pre_confirmed=True,
179 pre_approved=True,
181 # Add another email
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')
187 address.verify()
188 # Check response
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:
197 addr.verify()
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')
208 address.verify()
209 member = self.foo_list.subscribe(
210 self.user.email,
211 pre_verified=True,
212 pre_confirmed=True,
213 pre_approved=True,
215 # Now, first verify that the list_options page has all the emails.
216 # Check response
217 response = self.client.get(
218 reverse('user_list_options', args=[member.member_id])
220 self.assertContains(response, 'anotheremail@example.com')
221 self.assertContains(
222 response,
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
231 # custom.
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.
254 self.assertEqual(
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(
265 self.user.email,
266 pre_verified=True,
267 pre_confirmed=True,
268 pre_approved=True,
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)')
275 self.assertContains(
276 response,
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
285 # custom.
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(
309 self.user.email,
310 pre_verified=True,
311 pre_confirmed=True,
312 pre_approved=True,
314 # Now, first verify that the list_options page has all the emails.
315 # Check response
316 response = self.client.get(
317 reverse('user_list_options', args=[member.member_id])
319 self.assertContains(
320 response,
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(
339 user.user_id,
340 pre_verified=True,
341 pre_confirmed=True,
342 pre_approved=True,
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])
348 self.assertContains(
349 response,
351 '<option value="{}" selected>Primary Address (user@example.com)' # noqa: E501
352 '</option>'
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(
369 self.user.email,
370 pre_verified=True,
371 pre_confirmed=True,
372 pre_approved=True,
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)
378 @expectedFailure
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
390 # primary address.
391 member_primary = self.foo_list.subscribe(
392 mm_user.user_id,
393 pre_verified=True,
394 pre_confirmed=True,
395 pre_approved=True,
397 member_addr = self.foo_list.subscribe(
398 self.user.email,
399 pre_verified=True,
400 pre_confirmed=True,
401 pre_approved=True,
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 ==
412 # address.
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(
434 self.user.email,
435 pre_verified=True,
436 pre_confirmed=True,
437 pre_approved=True,
439 another_member = self.foo_list.subscribe(
440 'anoter@example.com',
441 pre_verified=True,
442 pre_confirmed=True,
443 pre_approved=True,
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):
458 def setUp(self):
459 super().setUp()
460 for i in range(11):
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.
478 url += '?count=15'
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)
487 def _get_url(query):
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)
500 self.assertEqual(
501 response.context['all_users'][0].display_name, 'User 7'
505 class TestManageUser(ViewTestCase):
506 def setUp(self):
507 super().setUp()
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'},
562 follow=True,
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)
572 formdata = {
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]),
582 data=formdata,
583 follow=True,
585 self.assertEqual(response.status_code, 200)
586 self.assertTrue(self.user.addresses[0].verified)
587 self.assertIn(
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)
594 data = {
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]),
605 data=data,
606 follow=True,
608 self.assertEqual(response.status_code, 200)
609 self.assertIn(
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')
619 self.assertTrue(
620 self.client.login(username='myuser', password='mypassword')
622 self.client.force_login(self.su)
623 data = {
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.
633 self.assertTrue(
634 self.client.login(username='myuser', password='newpsdsd1987')