diff options
| author | Barry Warsaw | 2016-01-07 18:33:01 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2016-01-07 18:33:01 -0500 |
| commit | 433ce1580295a410ebe83b731e54672c13e04e15 (patch) | |
| tree | 2474a30749d66260cad28945210ee778f32436ce /src | |
| parent | 4f549f8655857aef64ed8c92c3339c32f5e942db (diff) | |
| download | mailman-433ce1580295a410ebe83b731e54672c13e04e15.tar.gz mailman-433ce1580295a410ebe83b731e54672c13e04e15.tar.zst mailman-433ce1580295a410ebe83b731e54672c13e04e15.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/tests/test_subscriptions.py | 204 | ||||
| -rw-r--r-- | src/mailman/interfaces/subscriptions.py | 45 | ||||
| -rw-r--r-- | src/mailman/model/docs/subscriptions.rst (renamed from src/mailman/app/docs/subscriptions.rst) | 23 | ||||
| -rw-r--r-- | src/mailman/model/subscriptions.py | 23 | ||||
| -rw-r--r-- | src/mailman/model/tests/test_subscriptions.py | 260 |
5 files changed, 339 insertions, 216 deletions
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py index 229c85aa8..096b7dac6 100644 --- a/src/mailman/app/tests/test_subscriptions.py +++ b/src/mailman/app/tests/test_subscriptions.py @@ -27,9 +27,9 @@ import unittest from mailman.app.lifecycle import create_list from mailman.app.subscriptions import SubscriptionWorkflow from mailman.interfaces.bans import IBanManager -from mailman.interfaces.member import MemberRole, MembershipIsBannedError +from mailman.interfaces.member import MembershipIsBannedError from mailman.interfaces.pending import IPendings -from mailman.interfaces.subscriptions import ISubscriptionService, TokenOwner +from mailman.interfaces.subscriptions import TokenOwner from mailman.testing.helpers import LogFileMark, get_queue_messages from mailman.testing.layers import ConfigLayer from mailman.interfaces.mailinglist import SubscriptionPolicy @@ -641,203 +641,3 @@ approval: self.assertIsNone(workflow.token) self.assertEqual(workflow.token_owner, TokenOwner.no_one) self.assertEqual(workflow.member.address, anne) - - - -class TestSubscriptionService(unittest.TestCase): - layer = ConfigLayer - - def setUp(self): - self._mlist = create_list('test@example.com') - self._mlist.admin_immed_notify = False - self._user_manager = getUtility(IUserManager) - self._service = getUtility(ISubscriptionService) - - def test_find_member_address_no_user(self): - # Find address-based memberships when no user is linked to the address. - address = self._user_manager.create_address( - 'anne@example.com', 'Anne Address') - self._mlist.subscribe(address) - members = self._service.find_members('anne@example.com') - self.assertEqual(len(members), 1) - self.assertEqual(members[0].address, address) - - def test_find_member_address_with_user(self): - # Find address-based memberships when a user is linked to the address. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Subscribe the address. - self._mlist.subscribe(address) - members = self._service.find_members('anne@example.com') - self.assertEqual(len(members), 1) - self.assertEqual(members[0].user, user) - - def test_find_member_user(self): - # Find user-based memberships by address. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Subscribe the user. - self._mlist.subscribe(user) - members = self._service.find_members('anne@example.com') - self.assertEqual(len(members), 1) - self.assertEqual(members[0].user, user) - - def test_find_member_user_secondary_address(self): - # Find user-based memberships using a secondary address. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Create a secondary address. - address_2 = self._user_manager.create_address( - 'anne2@example.com', 'Anne User 2') - address_2.user = user - # Subscribe the user. - self._mlist.subscribe(user) - # Search for the secondary address. - members = self._service.find_members('anne2@example.com') - self.assertEqual(len(members), 1) - self.assertEqual(members[0].user, user) - - def test_wont_find_member_secondary_address(self): - # A user is subscribed with one of their address, and a search is - # performed on another of their addresses. This is not supported; the - # subscription is not returned. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Create a secondary address. - address_2 = self._user_manager.create_address( - 'anne2@example.com', 'Anne User 2') - address_2.verified_on = now() - address_2.user = user - # Subscribe the secondary address. - self._mlist.subscribe(address_2) - # Search for the primary address. - members = self._service.find_members('anne@example.com') - self.assertEqual(len(members), 0) - - def test_find_member_user_id(self): - # Find user-based memberships by user_id. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Subscribe the user. - self._mlist.subscribe(user) - members = self._service.find_members(user.user_id) - self.assertEqual(len(members), 1) - self.assertEqual(members[0].user, user) - - def test_find_member_user_id_controlled_addresses(self): - # Find address-based memberships by user_id when a secondary address is - # subscribed. - user = self._user_manager.create_user( - 'anne@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Create a secondary address. - address_2 = self._user_manager.create_address( - 'anne2@example.com', 'Anne User 2') - address_2.verified_on = now() - address_2.user = user - # Create a third address. - address_3 = self._user_manager.create_address( - 'anne3@example.com', 'Anne User 3') - address_3.verified_on = now() - address_3.user = user - # Subscribe the secondary address only. - self._mlist.subscribe(address_2) - members = self._service.find_members(user.user_id) - self.assertEqual(len(members), 1) - self.assertEqual(members[0].address, address_2) - - def test_find_member_sorting(self): - # Check that the memberships are properly sorted. - user = self._user_manager.create_user( - 'anne1@example.com', 'Anne User') - address = user.addresses[0] - address.verified_on = now() - user.preferred_address = address - # Create a secondary address. - address_2 = self._user_manager.create_address( - 'anne2@example.com', 'Anne User 2') - address_2.verified_on = now() - address_2.user = user - # Create a third address. - address_3 = self._user_manager.create_address( - 'anne3@example.com', 'Anne User 3') - address_3.verified_on = now() - address_3.user = user - # Create three lists. - mlist1 = create_list('test1@example.com') - mlist1.admin_immed_notify = False - mlist2 = create_list('test2@example.com') - mlist2.admin_immed_notify = False - mlist3 = create_list('test3@example.com') - mlist3.admin_immed_notify = False - # Subscribe the addresses in random order - # https://www.xkcd.com/221/ - mlist3.subscribe(address_3, MemberRole.moderator) - mlist3.subscribe(address_3, MemberRole.owner) - mlist3.subscribe(address_3, MemberRole.member) - mlist3.subscribe(address_2, MemberRole.member) - mlist3.subscribe(address_2, MemberRole.owner) - mlist3.subscribe(address_2, MemberRole.moderator) - mlist3.subscribe(address, MemberRole.owner) - mlist3.subscribe(address, MemberRole.member) - mlist3.subscribe(address, MemberRole.moderator) - mlist2.subscribe(address_2, MemberRole.moderator) - mlist2.subscribe(address_2, MemberRole.member) - mlist2.subscribe(address_2, MemberRole.owner) - mlist2.subscribe(address_3, MemberRole.moderator) - mlist2.subscribe(address_3, MemberRole.member) - mlist2.subscribe(address_3, MemberRole.owner) - mlist2.subscribe(address, MemberRole.owner) - mlist2.subscribe(address, MemberRole.moderator) - mlist2.subscribe(address, MemberRole.member) - mlist1.subscribe(address_2, MemberRole.moderator) - mlist1.subscribe(address, MemberRole.member) - mlist1.subscribe(address_3, MemberRole.owner) - # The results should be sorted first by list id, then by address, then - # by member role. - members = self._service.find_members(user.user_id) - self.assertEqual(len(members), 21) - self.assertListEqual( - [(m.list_id.partition('.')[0], - m.address.email.partition('@')[0], - m.role) - for m in members], - [('test1', 'anne1', MemberRole.member), - ('test1', 'anne2', MemberRole.moderator), - ('test1', 'anne3', MemberRole.owner), - ('test2', 'anne1', MemberRole.member), - ('test2', 'anne1', MemberRole.owner), - ('test2', 'anne1', MemberRole.moderator), - ('test2', 'anne2', MemberRole.member), - ('test2', 'anne2', MemberRole.owner), - ('test2', 'anne2', MemberRole.moderator), - ('test2', 'anne3', MemberRole.member), - ('test2', 'anne3', MemberRole.owner), - ('test2', 'anne3', MemberRole.moderator), - ('test3', 'anne1', MemberRole.member), - ('test3', 'anne1', MemberRole.owner), - ('test3', 'anne1', MemberRole.moderator), - ('test3', 'anne2', MemberRole.member), - ('test3', 'anne2', MemberRole.owner), - ('test3', 'anne2', MemberRole.moderator), - ('test3', 'anne3', MemberRole.member), - ('test3', 'anne3', MemberRole.owner), - ('test3', 'anne3', MemberRole.moderator), - ]) diff --git a/src/mailman/interfaces/subscriptions.py b/src/mailman/interfaces/subscriptions.py index f9e0c44dd..d4626a1bc 100644 --- a/src/mailman/interfaces/subscriptions.py +++ b/src/mailman/interfaces/subscriptions.py @@ -22,13 +22,14 @@ __all__ = [ 'MissingUserError', 'RequestRecord', 'TokenOwner', + 'TooManyMembersError', ] from collections import namedtuple from enum import Enum from mailman.interfaces.errors import MailmanError -from mailman.interfaces.member import DeliveryMode +from mailman.interfaces.member import DeliveryMode, MembershipError from zope.interface import Interface @@ -37,13 +38,21 @@ class MissingUserError(MailmanError): """A an invalid user id was given.""" def __init__(self, user_id): - super(MissingUserError, self).__init__() + super().__init__() self.user_id = user_id def __str__(self): return self.user_id +class TooManyMembersError(MembershipError): + def __init__(self, subscriber, list_id, role): + super().__init__() + self.subscriber = subscriber + self.list_id = list_id + self.role = role + + _RequestRecord = namedtuple( 'RequestRecord', @@ -92,12 +101,13 @@ class ISubscriptionService(Interface): """ def find_members(subscriber=None, list_id=None, role=None): - """Search for and return a specific member. + """Search for members matching some criteria. - The members are sorted first by fully-qualified mailing list name, - then by subscribed email address, then by role. Because the user may - be a member of the list under multiple roles (e.g. as an owner and as - a digest member), the member can appear multiple times in this list. + The members are sorted first by list-id, then by subscribed + email address, then by role. Because the user may be a member + of the list under multiple roles (e.g. as an owner and as a + digest member), the member can appear multiple times in this + list. :param subscriber: The email address or user id of the user getting subscribed. @@ -111,6 +121,27 @@ class ISubscriptionService(Interface): :rtype: list of `IMember` """ + def find_member(subscriber=None, list_id=None, role=None): + """Search for a member matching some criteria. + + This is like find_members() but is guaranteed to return exactly + one member. + + :param subscriber: The email address or user id of the user getting + subscribed. + :type subscriber: string or int + :param list_id: The list id of the mailing list to search for the + subscriber's memberships on. + :type list_id: string + :param role: The member role. + :type role: `MemberRole` + :return: The member matching the given criteria or None if no + members match the criteria. + :rtype: `IMember` or None + :raises TooManyMembersError: when the given criteria matches + more than one membership. + """ + def __iter__(): """See `get_members()`.""" diff --git a/src/mailman/app/docs/subscriptions.rst b/src/mailman/model/docs/subscriptions.rst index fa5d0edfe..5d141d5a9 100644 --- a/src/mailman/app/docs/subscriptions.rst +++ b/src/mailman/model/docs/subscriptions.rst @@ -86,8 +86,8 @@ There is an iteration shorthand for getting all the members. on cat@example.com as MemberRole.member> -Finding members -=============== +Searching for members +===================== The subscription service can be used to find memberships based on specific search criteria. For example, we can find all the mailing lists that Anne is @@ -152,6 +152,25 @@ You can also find a specific membership by all three criteria. on bee@example.com as MemberRole.owner> +Finding a single member +======================= + +If you expect only zero or one member to match your criteria, you can use a +the more efficient ``find_member()`` method. This takes exactly the same +criteria as ``find_members()``. + +There may be no matching members. + + >>> print(service.find_member('dave@example.com')) + None + +But if there is exactly one membership, it is returned. + + >>> service.find_member('bperson@example.com', 'ant.example.com') + <Member: Bart Person <bperson@example.com> + on ant@example.com as MemberRole.moderator> + + Removing members ================ diff --git a/src/mailman/model/subscriptions.py b/src/mailman/model/subscriptions.py index d1b805ef1..93c7c97c2 100644 --- a/src/mailman/model/subscriptions.py +++ b/src/mailman/model/subscriptions.py @@ -25,7 +25,8 @@ __all__ = [ from mailman.app.membership import delete_member from mailman.database.transaction import dbconnection from mailman.interfaces.listmanager import IListManager, NoSuchListError -from mailman.interfaces.subscriptions import ISubscriptionService +from mailman.interfaces.subscriptions import ( + ISubscriptionService, TooManyMembersError) from mailman.interfaces.usermanager import IUserManager from mailman.model.address import Address from mailman.model.member import Member @@ -76,13 +77,12 @@ class SubscriptionService: return members[0] @dbconnection - def find_members(self, store, subscriber=None, list_id=None, role=None): - """See `ISubscriptionService`.""" + def _find_members(self, store, subscriber, list_id, role): # If `subscriber` is a user id, then we'll search for all addresses # which are controlled by the user, otherwise we'll just search for # the given address. if subscriber is None and list_id is None and role is None: - return [] + raise NoResultFound order = (Member.list_id, Address.email, Member.role) # Querying for the subscriber is the most complicated part, because # the parameter can either be an email address or a user id. Start by @@ -112,12 +112,25 @@ class SubscriptionService: q_address = q_address.filter(Member.role == role) q_user = q_user.filter(Member.role == role) # Do a UNION of the two queries, sort the result and generate Members. + return q_address.union(q_user).order_by(*order).from_self(Member) + + def find_members(self, subscriber=None, list_id=None, role=None): + """See `ISubscriptionService`.""" try: - query = q_address.union(q_user).order_by(*order).from_self(Member) + query = self._find_members(subscriber, list_id, role) except NoResultFound: query = None return QuerySequence(query) + def find_member(self, subscriber=None, list_id=None, role=None): + """See `ISubscriptionService`.""" + try: + return self._find_members(subscriber, list_id, role).one() + except NoResultFound: + return None + except MultipleResultsFound: + raise TooManyMembersError(subscriber, list_id, role) + def __iter__(self): for member in self.get_members(): yield member diff --git a/src/mailman/model/tests/test_subscriptions.py b/src/mailman/model/tests/test_subscriptions.py new file mode 100644 index 000000000..3e97bd427 --- /dev/null +++ b/src/mailman/model/tests/test_subscriptions.py @@ -0,0 +1,260 @@ +# Copyright (C) 2016 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Test the subscription service.""" + +__all__ = [ + 'TestSubscriptionService', + ] + + +import unittest + +from mailman.app.lifecycle import create_list +from mailman.interfaces.listmanager import NoSuchListError +from mailman.interfaces.member import MemberRole +from mailman.interfaces.subscriptions import ( + ISubscriptionService, TooManyMembersError) +from mailman.interfaces.usermanager import IUserManager +from mailman.testing.helpers import subscribe +from mailman.testing.layers import ConfigLayer +from mailman.utilities.datetime import now +from zope.component import getUtility + + +class TestSubscriptionService(unittest.TestCase): + layer = ConfigLayer + + def setUp(self): + self._mlist = create_list('test@example.com') + self._mlist.admin_immed_notify = False + self._user_manager = getUtility(IUserManager) + self._service = getUtility(ISubscriptionService) + + def test_find_member_address_no_user(self): + # Find address-based memberships when no user is linked to the address. + address = self._user_manager.create_address( + 'anne@example.com', 'Anne Address') + self._mlist.subscribe(address) + members = self._service.find_members('anne@example.com') + self.assertEqual(len(members), 1) + self.assertEqual(members[0].address, address) + + def test_find_member_address_with_user(self): + # Find address-based memberships when a user is linked to the address. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Subscribe the address. + self._mlist.subscribe(address) + members = self._service.find_members('anne@example.com') + self.assertEqual(len(members), 1) + self.assertEqual(members[0].user, user) + + def test_find_member_user(self): + # Find user-based memberships by address. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Subscribe the user. + self._mlist.subscribe(user) + members = self._service.find_members('anne@example.com') + self.assertEqual(len(members), 1) + self.assertEqual(members[0].user, user) + + def test_find_member_user_secondary_address(self): + # Find user-based memberships using a secondary address. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Create a secondary address. + address_2 = self._user_manager.create_address( + 'anne2@example.com', 'Anne User 2') + address_2.user = user + # Subscribe the user. + self._mlist.subscribe(user) + # Search for the secondary address. + members = self._service.find_members('anne2@example.com') + self.assertEqual(len(members), 1) + self.assertEqual(members[0].user, user) + + def test_wont_find_member_secondary_address(self): + # A user is subscribed with one of their address, and a search is + # performed on another of their addresses. This is not supported; the + # subscription is not returned. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Create a secondary address. + address_2 = self._user_manager.create_address( + 'anne2@example.com', 'Anne User 2') + address_2.verified_on = now() + address_2.user = user + # Subscribe the secondary address. + self._mlist.subscribe(address_2) + # Search for the primary address. + members = self._service.find_members('anne@example.com') + self.assertEqual(len(members), 0) + + def test_find_member_user_id(self): + # Find user-based memberships by user_id. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Subscribe the user. + self._mlist.subscribe(user) + members = self._service.find_members(user.user_id) + self.assertEqual(len(members), 1) + self.assertEqual(members[0].user, user) + + def test_find_member_user_id_controlled_addresses(self): + # Find address-based memberships by user_id when a secondary address is + # subscribed. + user = self._user_manager.create_user( + 'anne@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Create a secondary address. + address_2 = self._user_manager.create_address( + 'anne2@example.com', 'Anne User 2') + address_2.verified_on = now() + address_2.user = user + # Create a third address. + address_3 = self._user_manager.create_address( + 'anne3@example.com', 'Anne User 3') + address_3.verified_on = now() + address_3.user = user + # Subscribe the secondary address only. + self._mlist.subscribe(address_2) + members = self._service.find_members(user.user_id) + self.assertEqual(len(members), 1) + self.assertEqual(members[0].address, address_2) + + def test_find_member_sorting(self): + # Check that the memberships are properly sorted. + user = self._user_manager.create_user( + 'anne1@example.com', 'Anne User') + address = user.addresses[0] + address.verified_on = now() + user.preferred_address = address + # Create a secondary address. + address_2 = self._user_manager.create_address( + 'anne2@example.com', 'Anne User 2') + address_2.verified_on = now() + address_2.user = user + # Create a third address. + address_3 = self._user_manager.create_address( + 'anne3@example.com', 'Anne User 3') + address_3.verified_on = now() + address_3.user = user + # Create three lists. + mlist1 = create_list('test1@example.com') + mlist1.admin_immed_notify = False + mlist2 = create_list('test2@example.com') + mlist2.admin_immed_notify = False + mlist3 = create_list('test3@example.com') + mlist3.admin_immed_notify = False + # Subscribe the addresses in random order + # https://www.xkcd.com/221/ + mlist3.subscribe(address_3, MemberRole.moderator) + mlist3.subscribe(address_3, MemberRole.owner) + mlist3.subscribe(address_3, MemberRole.member) + mlist3.subscribe(address_2, MemberRole.member) + mlist3.subscribe(address_2, MemberRole.owner) + mlist3.subscribe(address_2, MemberRole.moderator) + mlist3.subscribe(address, MemberRole.owner) + mlist3.subscribe(address, MemberRole.member) + mlist3.subscribe(address, MemberRole.moderator) + mlist2.subscribe(address_2, MemberRole.moderator) + mlist2.subscribe(address_2, MemberRole.member) + mlist2.subscribe(address_2, MemberRole.owner) + mlist2.subscribe(address_3, MemberRole.moderator) + mlist2.subscribe(address_3, MemberRole.member) + mlist2.subscribe(address_3, MemberRole.owner) + mlist2.subscribe(address, MemberRole.owner) + mlist2.subscribe(address, MemberRole.moderator) + mlist2.subscribe(address, MemberRole.member) + mlist1.subscribe(address_2, MemberRole.moderator) + mlist1.subscribe(address, MemberRole.member) + mlist1.subscribe(address_3, MemberRole.owner) + # The results should be sorted first by list id, then by address, then + # by member role. + members = self._service.find_members(user.user_id) + self.assertEqual(len(members), 21) + self.assertListEqual( + [(m.list_id.partition('.')[0], + m.address.email.partition('@')[0], + m.role) + for m in members], + [('test1', 'anne1', MemberRole.member), + ('test1', 'anne2', MemberRole.moderator), + ('test1', 'anne3', MemberRole.owner), + ('test2', 'anne1', MemberRole.member), + ('test2', 'anne1', MemberRole.owner), + ('test2', 'anne1', MemberRole.moderator), + ('test2', 'anne2', MemberRole.member), + ('test2', 'anne2', MemberRole.owner), + ('test2', 'anne2', MemberRole.moderator), + ('test2', 'anne3', MemberRole.member), + ('test2', 'anne3', MemberRole.owner), + ('test2', 'anne3', MemberRole.moderator), + ('test3', 'anne1', MemberRole.member), + ('test3', 'anne1', MemberRole.owner), + ('test3', 'anne1', MemberRole.moderator), + ('test3', 'anne2', MemberRole.member), + ('test3', 'anne2', MemberRole.owner), + ('test3', 'anne2', MemberRole.moderator), + ('test3', 'anne3', MemberRole.member), + ('test3', 'anne3', MemberRole.owner), + ('test3', 'anne3', MemberRole.moderator), + ]) + + def test_find_members_shortcut(self): + members = self._service.find_members() + self.assertEqual(len(members), 0) + + def test_find_members_no_results(self): + members = self._service.find_members('zack@example.com') + self.assertEqual(len(members), 0) + self.assertEqual(list(members), []) + + def test_find_member_error(self): + # .find_member() can only return zero or one memberships. Anything + # else is an error. + subscribe(self._mlist, 'Anne') + subscribe(self._mlist, 'Anne', MemberRole.owner) + with self.assertRaises(TooManyMembersError) as cm: + self._service.find_member('aperson@example.com') + self.assertEqual(cm.exception.subscriber, 'aperson@example.com') + self.assertEqual(cm.exception.list_id, None) + self.assertEqual(cm.exception.role, None) + + def test_leave_no_such_list(self): + # Trying to leave a nonexistent list raises an exception. + self.assertRaises(NoSuchListError, self._service.leave, + 'bogus.example.com', 'anne@example.com') |
