Import order flake8 plugin.
[mailman.git] / src / mailman / rest / tests / test_membership.py
blob1ea70e90b990720e6c17f9f1f179fae5d115b6d8
1 # Copyright (C) 2011-2016 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 """REST membership tests."""
20 import unittest
22 from mailman.app.lifecycle import create_list
23 from mailman.config import config
24 from mailman.database.transaction import transaction
25 from mailman.interfaces.bans import IBanManager
26 from mailman.interfaces.mailinglist import SubscriptionPolicy
27 from mailman.interfaces.member import DeliveryMode, MemberRole
28 from mailman.interfaces.registrar import IRegistrar
29 from mailman.interfaces.subscriptions import TokenOwner
30 from mailman.interfaces.usermanager import IUserManager
31 from mailman.runners.incoming import IncomingRunner
32 from mailman.testing.helpers import (
33 TestableMaster, call_api, get_lmtp_client, make_testable_runner,
34 set_preferred, subscribe, wait_for_webservice)
35 from mailman.testing.layers import ConfigLayer, RESTLayer
36 from mailman.utilities.datetime import now
37 from urllib.error import HTTPError
38 from zope.component import getUtility
41 class TestMembership(unittest.TestCase):
42 layer = RESTLayer
44 def setUp(self):
45 with transaction():
46 self._mlist = create_list('test@example.com')
47 self._usermanager = getUtility(IUserManager)
49 def test_try_to_join_missing_list(self):
50 # A user tries to join a non-existent list.
51 with self.assertRaises(HTTPError) as cm:
52 call_api('http://localhost:9001/3.0/members', {
53 'list_id': 'missing.example.com',
54 'subscriber': 'nobody@example.com',
56 self.assertEqual(cm.exception.code, 400)
57 self.assertEqual(cm.exception.reason, b'No such list')
59 def test_try_to_leave_missing_list(self):
60 # A user tries to leave a non-existent list.
61 with self.assertRaises(HTTPError) as cm:
62 call_api('http://localhost:9001/3.0/lists/missing@example.com'
63 '/member/nobody@example.com',
64 method='DELETE')
65 self.assertEqual(cm.exception.code, 404)
67 def test_try_to_leave_list_with_bogus_address(self):
68 # Try to leave a mailing list using an invalid membership address.
69 with self.assertRaises(HTTPError) as cm:
70 call_api('http://localhost:9001/3.0/members/1', method='DELETE')
71 self.assertEqual(cm.exception.code, 404)
73 def test_try_to_leave_a_list_twice(self):
74 with transaction():
75 anne = self._usermanager.create_address('anne@example.com')
76 self._mlist.subscribe(anne)
77 url = 'http://localhost:9001/3.0/members/1'
78 content, response = call_api(url, method='DELETE')
79 # For a successful DELETE, the response code is 204 and there is no
80 # content.
81 self.assertEqual(content, None)
82 self.assertEqual(response.status, 204)
83 with self.assertRaises(HTTPError) as cm:
84 call_api(url, method='DELETE')
85 self.assertEqual(cm.exception.code, 404)
87 def test_try_to_join_a_list_twice(self):
88 with transaction():
89 anne = self._usermanager.create_address('anne@example.com')
90 self._mlist.subscribe(anne)
91 with self.assertRaises(HTTPError) as cm:
92 call_api('http://localhost:9001/3.0/members', {
93 'list_id': 'test.example.com',
94 'subscriber': 'anne@example.com',
95 'pre_verified': True,
96 'pre_confirmed': True,
97 'pre_approved': True,
99 self.assertEqual(cm.exception.code, 409)
100 self.assertEqual(cm.exception.reason, b'Member already subscribed')
102 def test_subscribe_user_without_preferred_address(self):
103 with transaction():
104 getUtility(IUserManager).create_user('anne@example.com')
105 # Subscribe the user to the mailing list by hex UUID.
106 with self.assertRaises(HTTPError) as cm:
107 call_api('http://localhost:9001/3.1/members', {
108 'list_id': 'test.example.com',
109 'subscriber': '00000000000000000000000000000001',
110 'pre_verified': True,
111 'pre_confirmed': True,
112 'pre_approved': True,
114 self.assertEqual(cm.exception.code, 400)
115 self.assertEqual(cm.exception.reason, b'User has no preferred address')
117 def test_subscribe_bogus_user_by_uid(self):
118 with self.assertRaises(HTTPError) as cm:
119 call_api('http://localhost:9001/3.1/members', {
120 'list_id': 'test.example.com',
121 'subscriber': '00000000000000000000000000000801',
122 'pre_verified': True,
123 'pre_confirmed': True,
124 'pre_approved': True,
126 self.assertEqual(cm.exception.code, 400)
127 self.assertEqual(cm.exception.reason, b'No such user')
129 def test_add_member_with_mixed_case_email(self):
130 # LP: #1425359 - Mailman is case-perserving, case-insensitive. This
131 # test subscribes the lower case address and ensures the original mixed
132 # case address can't be subscribed.
133 with transaction():
134 anne = self._usermanager.create_address('anne@example.com')
135 self._mlist.subscribe(anne)
136 with self.assertRaises(HTTPError) as cm:
137 call_api('http://localhost:9001/3.0/members', {
138 'list_id': 'test.example.com',
139 'subscriber': 'ANNE@example.com',
140 'pre_verified': True,
141 'pre_confirmed': True,
142 'pre_approved': True,
144 self.assertEqual(cm.exception.code, 409)
145 self.assertEqual(cm.exception.reason, b'Member already subscribed')
147 def test_add_member_with_lower_case_email(self):
148 # LP: #1425359 - Mailman is case-perserving, case-insensitive. This
149 # test subscribes the mixed case address and ensures the lower cased
150 # address can't be added.
151 with transaction():
152 anne = self._usermanager.create_address('ANNE@example.com')
153 self._mlist.subscribe(anne)
154 with self.assertRaises(HTTPError) as cm:
155 call_api('http://localhost:9001/3.0/members', {
156 'list_id': 'test.example.com',
157 'subscriber': 'anne@example.com',
158 'pre_verified': True,
159 'pre_confirmed': True,
160 'pre_approved': True,
162 self.assertEqual(cm.exception.code, 409)
163 self.assertEqual(cm.exception.reason, b'Member already subscribed')
165 def test_join_with_invalid_delivery_mode(self):
166 with self.assertRaises(HTTPError) as cm:
167 call_api('http://localhost:9001/3.0/members', {
168 'list_id': 'test.example.com',
169 'subscriber': 'anne@example.com',
170 'display_name': 'Anne Person',
171 'delivery_mode': 'invalid-mode',
173 self.assertEqual(cm.exception.code, 400)
174 self.assertEqual(cm.exception.reason,
175 b'Cannot convert parameters: delivery_mode')
177 def test_join_email_contains_slash(self):
178 content, response = call_api('http://localhost:9001/3.0/members', {
179 'list_id': 'test.example.com',
180 'subscriber': 'hugh/person@example.com',
181 'display_name': 'Hugh Person',
182 'pre_verified': True,
183 'pre_confirmed': True,
184 'pre_approved': True,
186 self.assertEqual(content, None)
187 self.assertEqual(response.status, 201)
188 self.assertEqual(response['location'],
189 'http://localhost:9001/3.0/members/1')
190 # Reset any current transaction.
191 config.db.abort()
192 members = list(self._mlist.members.members)
193 self.assertEqual(len(members), 1)
194 self.assertEqual(members[0].address.email, 'hugh/person@example.com')
196 def test_join_as_user_with_preferred_address(self):
197 with transaction():
198 anne = self._usermanager.create_user('anne@example.com')
199 set_preferred(anne)
200 self._mlist.subscribe(anne)
201 content, response = call_api('http://localhost:9001/3.0/members')
202 self.assertEqual(response.status, 200)
203 self.assertEqual(int(content['total_size']), 1)
204 entry_0 = content['entries'][0]
205 self.assertEqual(entry_0['self_link'],
206 'http://localhost:9001/3.0/members/1')
207 self.assertEqual(entry_0['role'], 'member')
208 self.assertEqual(entry_0['user'], 'http://localhost:9001/3.0/users/1')
209 self.assertEqual(entry_0['email'], 'anne@example.com')
210 self.assertEqual(
211 entry_0['address'],
212 'http://localhost:9001/3.0/addresses/anne@example.com')
213 self.assertEqual(entry_0['list_id'], 'test.example.com')
215 def test_duplicate_pending_subscription(self):
216 # Issue #199 - a member's subscription is already pending and they try
217 # to subscribe again.
218 registrar = IRegistrar(self._mlist)
219 with transaction():
220 self._mlist.subscription_policy = SubscriptionPolicy.moderate
221 anne = self._usermanager.create_address('anne@example.com')
222 token, token_owner, member = registrar.register(
223 anne, pre_verified=True, pre_confirmed=True)
224 self.assertEqual(token_owner, TokenOwner.moderator)
225 self.assertIsNone(member)
226 with self.assertRaises(HTTPError) as cm:
227 call_api('http://localhost:9001/3.0/members', {
228 'list_id': 'test.example.com',
229 'subscriber': 'anne@example.com',
230 'pre_verified': True,
231 'pre_confirmed': True,
233 self.assertEqual(cm.exception.code, 409)
234 self.assertEqual(cm.exception.reason,
235 b'Subscription request already pending')
237 def test_duplicate_other_pending_subscription(self):
238 # Issue #199 - a member's subscription is already pending and they try
239 # to subscribe again. Unlike above, this pend is waiting for the user
240 # to confirm their subscription.
241 registrar = IRegistrar(self._mlist)
242 with transaction():
243 self._mlist.subscription_policy = (
244 SubscriptionPolicy.confirm_then_moderate)
245 anne = self._usermanager.create_address('anne@example.com')
246 token, token_owner, member = registrar.register(
247 anne, pre_verified=True)
248 self.assertEqual(token_owner, TokenOwner.subscriber)
249 self.assertIsNone(member)
250 with self.assertRaises(HTTPError) as cm:
251 call_api('http://localhost:9001/3.0/members', {
252 'list_id': 'test.example.com',
253 'subscriber': 'anne@example.com',
254 'pre_verified': True,
255 'pre_confirmed': True,
257 self.assertEqual(cm.exception.code, 409)
258 self.assertEqual(cm.exception.reason,
259 b'Subscription request already pending')
261 def test_member_changes_preferred_address(self):
262 with transaction():
263 anne = self._usermanager.create_user('anne@example.com')
264 set_preferred(anne)
265 self._mlist.subscribe(anne)
266 # Take a look at Anne's current membership.
267 content, response = call_api('http://localhost:9001/3.0/members')
268 self.assertEqual(int(content['total_size']), 1)
269 entry_0 = content['entries'][0]
270 self.assertEqual(entry_0['email'], 'anne@example.com')
271 self.assertEqual(
272 entry_0['address'],
273 'http://localhost:9001/3.0/addresses/anne@example.com')
274 # Anne registers a new address and makes it her preferred address.
275 # There are no changes to her membership.
276 with transaction():
277 new_preferred = anne.register('aperson@example.com')
278 new_preferred.verified_on = now()
279 anne.preferred_address = new_preferred
280 # Take another look at Anne's current membership.
281 content, response = call_api('http://localhost:9001/3.0/members')
282 self.assertEqual(int(content['total_size']), 1)
283 entry_0 = content['entries'][0]
284 self.assertEqual(entry_0['email'], 'aperson@example.com')
285 self.assertEqual(
286 entry_0['address'],
287 'http://localhost:9001/3.0/addresses/aperson@example.com')
289 def test_get_nonexistent_member(self):
290 # /members/<bogus> returns 404
291 with self.assertRaises(HTTPError) as cm:
292 call_api('http://localhost:9001/3.0/members/bogus')
293 self.assertEqual(cm.exception.code, 404)
295 def test_patch_nonexistent_member(self):
296 # /members/<missing> PATCH returns 404
297 with self.assertRaises(HTTPError) as cm:
298 call_api('http://localhost:9001/3.0/members/801',
299 {}, method='PATCH')
300 self.assertEqual(cm.exception.code, 404)
302 def test_patch_membership_with_bogus_address(self):
303 # Try to change a subscription address to one that does not yet exist.
304 with transaction():
305 subscribe(self._mlist, 'Anne')
306 with self.assertRaises(HTTPError) as cm:
307 call_api('http://localhost:9001/3.0/members/1', {
308 'address': 'bogus@example.com',
309 }, method='PATCH')
310 self.assertEqual(cm.exception.code, 400)
311 self.assertEqual(cm.exception.reason, b'Address not registered')
313 def test_patch_membership_with_unverified_address(self):
314 # Try to change a subscription address to one that is not yet verified.
315 with transaction():
316 subscribe(self._mlist, 'Anne')
317 self._usermanager.create_address('anne.person@example.com')
318 with self.assertRaises(HTTPError) as cm:
319 call_api('http://localhost:9001/3.0/members/1', {
320 'address': 'anne.person@example.com',
321 }, method='PATCH')
322 self.assertEqual(cm.exception.code, 400)
323 self.assertEqual(cm.exception.reason, b'Unverified address')
325 def test_patch_membership_of_preferred_address(self):
326 # Try to change a subscription to an address when the user is
327 # subscribed via their preferred address.
328 with transaction():
329 subscribe(self._mlist, 'Anne')
330 anne = self._usermanager.create_address('anne.person@example.com')
331 anne.verified_on = now()
332 with self.assertRaises(HTTPError) as cm:
333 call_api('http://localhost:9001/3.0/members/1', {
334 'address': 'anne.person@example.com',
335 }, method='PATCH')
336 self.assertEqual(cm.exception.code, 400)
337 self.assertEqual(cm.exception.reason,
338 b'Address is not controlled by user')
340 def test_patch_member_bogus_attribute(self):
341 # /members/<id> PATCH 'bogus' returns 400
342 with transaction():
343 anne = self._usermanager.create_address('anne@example.com')
344 self._mlist.subscribe(anne)
345 with self.assertRaises(HTTPError) as cm:
346 call_api('http://localhost:9001/3.0/members/1', {
347 'powers': 'super',
348 }, method='PATCH')
349 self.assertEqual(cm.exception.code, 400)
350 self.assertEqual(cm.exception.reason, b'Unexpected parameters: powers')
352 def test_member_all_without_preferences(self):
353 # /members/<id>/all should return a 404 when it isn't trailed by
354 # `preferences`
355 with self.assertRaises(HTTPError) as cm:
356 call_api('http://localhost:9001/3.0/members/1/all')
357 self.assertEqual(cm.exception.code, 404)
359 def test_patch_member_invalid_moderation_action(self):
360 # /members/<id> PATCH with invalid 'moderation_action' returns 400.
361 with transaction():
362 anne = self._usermanager.create_address('anne@example.com')
363 self._mlist.subscribe(anne)
364 with self.assertRaises(HTTPError) as cm:
365 call_api('http://localhost:9001/3.0/members/1', {
366 'moderation_action': 'invalid',
367 }, method='PATCH')
368 self.assertEqual(cm.exception.code, 400)
369 self.assertEqual(cm.exception.reason,
370 b'Cannot convert parameters: moderation_action')
372 def test_bad_preferences_url(self):
373 with transaction():
374 subscribe(self._mlist, 'Anne')
375 with self.assertRaises(HTTPError) as cm:
376 call_api('http://localhost:9001/3.0/members/1/preferences/bogus')
377 self.assertEqual(cm.exception.code, 404)
379 def test_not_a_member_preferences(self):
380 with self.assertRaises(HTTPError) as cm:
381 call_api('http://localhost:9001/3.0/members/1/preferences')
382 self.assertEqual(cm.exception.code, 404)
384 def test_not_a_member_all_preferences(self):
385 with self.assertRaises(HTTPError) as cm:
386 call_api('http://localhost:9001/3.0/members/1/all/preferences')
387 self.assertEqual(cm.exception.code, 404)
389 def test_delete_other_role(self):
390 with transaction():
391 subscribe(self._mlist, 'Anne', MemberRole.moderator)
392 response, headers = call_api(
393 'http://localhost:9001/3.0/members/1',
394 method='DELETE')
395 self.assertEqual(headers.status, 204)
396 self.assertEqual(len(list(self._mlist.moderators.members)), 0)
398 def test_banned_member_tries_to_join(self):
399 # A user tries to join a list they are banned from.
400 with transaction():
401 IBanManager(self._mlist).ban('anne@example.com')
402 with self.assertRaises(HTTPError) as cm:
403 call_api('http://localhost:9001/3.0/members', {
404 'list_id': 'test.example.com',
405 'subscriber': 'anne@example.com',
407 self.assertEqual(cm.exception.code, 400)
408 self.assertEqual(cm.exception.reason, b'Membership is banned')
410 def test_globally_banned_member_tries_to_join(self):
411 # A user tries to join a list they are banned from.
412 with transaction():
413 IBanManager(None).ban('anne@example.com')
414 with self.assertRaises(HTTPError) as cm:
415 call_api('http://localhost:9001/3.0/members', {
416 'list_id': 'test.example.com',
417 'subscriber': 'anne@example.com',
419 self.assertEqual(cm.exception.code, 400)
420 self.assertEqual(cm.exception.reason, b'Membership is banned')
423 class CustomLayer(ConfigLayer):
424 """Custom layer which starts both the REST and LMTP servers."""
426 server = None
427 client = None
429 @classmethod
430 def _wait_for_both(cls):
431 cls.client = get_lmtp_client(quiet=True)
432 wait_for_webservice()
434 @classmethod
435 def setUp(cls):
436 assert cls.server is None, 'Layer already set up'
437 cls.server = TestableMaster(cls._wait_for_both)
438 cls.server.start('lmtp', 'rest')
440 @classmethod
441 def tearDown(cls):
442 assert cls.server is not None, 'Layer is not set up'
443 cls.server.stop()
444 cls.server = None
447 class TestNonmembership(unittest.TestCase):
448 layer = CustomLayer
450 def setUp(self):
451 with transaction():
452 self._mlist = create_list('test@example.com')
453 self._usermanager = getUtility(IUserManager)
455 def _go(self, message):
456 lmtp = get_lmtp_client(quiet=True)
457 lmtp.lhlo('remote.example.org')
458 lmtp.sendmail('nonmember@example.com', ['test@example.com'], message)
459 lmtp.close()
460 # The message will now be sitting in the `in` queue. Run the incoming
461 # runner once to process it, which should result in the nonmember
462 # showing up.
463 inq = make_testable_runner(IncomingRunner, 'in')
464 inq.run()
466 def test_nonmember_findable_after_posting(self):
467 # A nonmember we have never seen before posts a message to the mailing
468 # list. They are findable through the /members/find API using a role
469 # of nonmember.
470 self._go("""\
471 From: nonmember@example.com
472 To: test@example.com
473 Subject: Nonmember post
474 Message-ID: <alpha>
476 Some text.
477 """)
478 # Now use the REST API to try to find the nonmember.
479 response, content = call_api(
480 'http://localhost:9001/3.0/members/find', {
481 # 'list_id': 'test.example.com',
482 'role': 'nonmember',
484 self.assertEqual(response['total_size'], 1)
485 nonmember = response['entries'][0]
486 self.assertEqual(nonmember['role'], 'nonmember')
487 self.assertEqual(nonmember['email'], 'nonmember@example.com')
488 self.assertEqual(
489 nonmember['address'],
490 'http://localhost:9001/3.0/addresses/nonmember@example.com')
491 # There is no user key in the JSON data because there is no user
492 # record associated with the address record.
493 self.assertNotIn('user', nonmember)
495 def test_linked_nonmember_findable_after_posting(self):
496 # Like above, a nonmember posts a message to the mailing list. In
497 # this case though, the nonmember already has a user record. They are
498 # findable through the /members/find API using a role of nonmember.
499 with transaction():
500 self._usermanager.create_user('nonmember@example.com')
501 self._go("""\
502 From: nonmember@example.com
503 To: test@example.com
504 Subject: Nonmember post
505 Message-ID: <alpha>
507 Some text.
508 """)
509 # Now use the REST API to try to find the nonmember.
510 response, content = call_api(
511 'http://localhost:9001/3.0/members/find', {
512 # 'list_id': 'test.example.com',
513 'role': 'nonmember',
515 self.assertEqual(response['total_size'], 1)
516 nonmember = response['entries'][0]
517 self.assertEqual(nonmember['role'], 'nonmember')
518 self.assertEqual(nonmember['email'], 'nonmember@example.com')
519 self.assertEqual(
520 nonmember['address'],
521 'http://localhost:9001/3.0/addresses/nonmember@example.com')
522 # There is a user key in the JSON data because the address had
523 # previously been linked to a user record.
524 self.assertEqual(nonmember['user'],
525 'http://localhost:9001/3.0/users/1')
528 class TestAPI31Members(unittest.TestCase):
529 layer = RESTLayer
531 def setUp(self):
532 with transaction():
533 self._mlist = create_list('ant@example.com')
535 def test_member_ids_are_hex(self):
536 with transaction():
537 subscribe(self._mlist, 'Anne')
538 subscribe(self._mlist, 'Bart')
539 response, headers = call_api('http://localhost:9001/3.1/members')
540 entries = response['entries']
541 self.assertEqual(len(entries), 2)
542 self.assertEqual(
543 entries[0]['self_link'],
544 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
545 self.assertEqual(
546 entries[0]['member_id'],
547 '00000000000000000000000000000001')
548 self.assertEqual(
549 entries[0]['user'],
550 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
551 self.assertEqual(
552 entries[1]['self_link'],
553 'http://localhost:9001/3.1/members/00000000000000000000000000000002')
554 self.assertEqual(
555 entries[1]['member_id'],
556 '00000000000000000000000000000002')
557 self.assertEqual(
558 entries[1]['user'],
559 'http://localhost:9001/3.1/users/00000000000000000000000000000002')
561 def test_get_member_id_by_hex(self):
562 with transaction():
563 subscribe(self._mlist, 'Anne')
564 response, headers = call_api(
565 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
566 self.assertEqual(
567 response['member_id'],
568 '00000000000000000000000000000001')
569 self.assertEqual(
570 response['self_link'],
571 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
572 self.assertEqual(
573 response['user'],
574 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
575 self.assertEqual(
576 response['address'],
577 'http://localhost:9001/3.1/addresses/aperson@example.com')
579 def test_get_list_member_id_by_email(self):
580 with transaction():
581 subscribe(self._mlist, 'Anne', email="aperson@example.com")
582 response, headers = call_api(
583 'http://localhost:9001/3.1/lists/ant.example.com/member'
584 '/aperson@example.com')
585 self.assertEqual(response['member_id'],
586 '00000000000000000000000000000001')
587 self.assertEqual(
588 response['self_link'],
589 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
590 self.assertEqual(
591 response['user'],
592 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
593 self.assertEqual(
594 response['address'],
595 'http://localhost:9001/3.1/addresses/aperson@example.com')
597 def test_cannot_get_member_id_by_int(self):
598 with transaction():
599 subscribe(self._mlist, 'Anne')
600 with self.assertRaises(HTTPError) as cm:
601 call_api('http://localhost:9001/3.1/members/1')
602 self.assertEqual(cm.exception.code, 404)
604 def test_preferences(self):
605 with transaction():
606 member = subscribe(self._mlist, 'Anne')
607 member.preferences.delivery_mode = DeliveryMode.summary_digests
608 response, headers = call_api(
609 'http://localhost:9001/3.1/members'
610 '/00000000000000000000000000000001/preferences')
611 self.assertEqual(response['delivery_mode'], 'summary_digests')
613 def test_all_preferences(self):
614 with transaction():
615 member = subscribe(self._mlist, 'Anne')
616 member.preferences.delivery_mode = DeliveryMode.summary_digests
617 response, headers = call_api(
618 'http://localhost:9001/3.1/members'
619 '/00000000000000000000000000000001/all/preferences')
620 self.assertEqual(response['delivery_mode'], 'summary_digests')
622 def test_create_new_membership_by_hex(self):
623 with transaction():
624 user = getUtility(IUserManager).create_user('anne@example.com')
625 set_preferred(user)
626 # Subscribe the user to the mailing list by hex UUID.
627 response, headers = call_api(
628 'http://localhost:9001/3.1/members', {
629 'list_id': 'ant.example.com',
630 'subscriber': '00000000000000000000000000000001',
631 'pre_verified': True,
632 'pre_confirmed': True,
633 'pre_approved': True,
635 self.assertEqual(headers.status, 201)
636 self.assertEqual(
637 headers['location'],
638 'http://localhost:9001/3.1/members/00000000000000000000000000000001'
641 def test_create_new_owner_by_hex(self):
642 with transaction():
643 user = getUtility(IUserManager).create_user('anne@example.com')
644 set_preferred(user)
645 # Subscribe the user to the mailing list by hex UUID.
646 response, headers = call_api(
647 'http://localhost:9001/3.1/members', {
648 'list_id': 'ant.example.com',
649 'subscriber': '00000000000000000000000000000001',
650 'role': 'owner',
652 self.assertEqual(headers.status, 201)
653 self.assertEqual(
654 headers['location'],
655 'http://localhost:9001/3.1/members/00000000000000000000000000000001'
658 def test_cannot_create_new_membership_by_int(self):
659 with transaction():
660 user = getUtility(IUserManager).create_user('anne@example.com')
661 set_preferred(user)
662 # We can't use the int representation of the UUID with API 3.1.
663 with self.assertRaises(HTTPError) as cm:
664 call_api('http://localhost:9001/3.1/members', {
665 'list_id': 'ant.example.com',
666 'subscriber': '1',
667 'pre_verified': True,
668 'pre_confirmed': True,
669 'pre_approved': True,
671 # This is a bad request because the `subscriber` value isn't something
672 # that's known to the system, in API 3.1. It's not technically a 404
673 # because that's reserved for URL lookups.
674 self.assertEqual(cm.exception.code, 400)
676 def test_duplicate_owner(self):
677 # Server failure when someone is already an owner.
678 with transaction():
679 anne = getUtility(IUserManager).create_address('anne@example.com')
680 self._mlist.subscribe(anne, MemberRole.owner)
681 with self.assertRaises(HTTPError) as cm:
682 call_api('http://localhost:9001/3.1/members', {
683 'list_id': 'ant.example.com',
684 'subscriber': anne.email,
685 'role': 'owner',
687 self.assertEqual(cm.exception.code, 400)
688 self.assertEqual(
689 cm.exception.reason,
690 b'anne@example.com is already an owner of ant@example.com')