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)
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
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."""
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
):
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',
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
):
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
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
):
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',
96 'pre_confirmed': 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
):
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.
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.
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.
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
):
198 anne
= self
._usermanager
.create_user('anne@example.com')
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')
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
)
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
)
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
):
263 anne
= self
._usermanager
.create_user('anne@example.com')
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')
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.
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')
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',
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.
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',
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.
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',
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.
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',
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
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', {
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
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.
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',
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
):
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
):
391 subscribe(self
._mlist
, 'Anne', MemberRole
.moderator
)
392 response
, headers
= call_api(
393 'http://localhost:9001/3.0/members/1',
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.
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.
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."""
430 def _wait_for_both(cls
):
431 cls
.client
= get_lmtp_client(quiet
=True)
432 wait_for_webservice()
436 assert cls
.server
is None, 'Layer already set up'
437 cls
.server
= TestableMaster(cls
._wait
_for
_both
)
438 cls
.server
.start('lmtp', 'rest')
442 assert cls
.server
is not None, 'Layer is not set up'
447 class TestNonmembership(unittest
.TestCase
):
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
)
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
463 inq
= make_testable_runner(IncomingRunner
, 'in')
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
471 From: nonmember@example.com
473 Subject: Nonmember post
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',
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')
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.
500 self
._usermanager
.create_user('nonmember@example.com')
502 From: nonmember@example.com
504 Subject: Nonmember post
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',
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')
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
):
533 self
._mlist
= create_list('ant@example.com')
535 def test_member_ids_are_hex(self
):
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)
543 entries
[0]['self_link'],
544 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
546 entries
[0]['member_id'],
547 '00000000000000000000000000000001')
550 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
552 entries
[1]['self_link'],
553 'http://localhost:9001/3.1/members/00000000000000000000000000000002')
555 entries
[1]['member_id'],
556 '00000000000000000000000000000002')
559 'http://localhost:9001/3.1/users/00000000000000000000000000000002')
561 def test_get_member_id_by_hex(self
):
563 subscribe(self
._mlist
, 'Anne')
564 response
, headers
= call_api(
565 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
567 response
['member_id'],
568 '00000000000000000000000000000001')
570 response
['self_link'],
571 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
574 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
577 'http://localhost:9001/3.1/addresses/aperson@example.com')
579 def test_get_list_member_id_by_email(self
):
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')
588 response
['self_link'],
589 'http://localhost:9001/3.1/members/00000000000000000000000000000001')
592 'http://localhost:9001/3.1/users/00000000000000000000000000000001')
595 'http://localhost:9001/3.1/addresses/aperson@example.com')
597 def test_cannot_get_member_id_by_int(self
):
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
):
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
):
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
):
624 user
= getUtility(IUserManager
).create_user('anne@example.com')
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)
638 'http://localhost:9001/3.1/members/00000000000000000000000000000001'
641 def test_create_new_owner_by_hex(self
):
643 user
= getUtility(IUserManager
).create_user('anne@example.com')
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',
652 self
.assertEqual(headers
.status
, 201)
655 'http://localhost:9001/3.1/members/00000000000000000000000000000001'
658 def test_cannot_create_new_membership_by_int(self
):
660 user
= getUtility(IUserManager
).create_user('anne@example.com')
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',
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.
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
,
687 self
.assertEqual(cm
.exception
.code
, 400)
690 b
'anne@example.com is already an owner of ant@example.com')