Commit a11e089c authored by Barry Warsaw's avatar Barry Warsaw

The ``mailman members`` command can now be used to display members based on

subscription roles.  Also, the positional "list" argument can now accept
list names or list-ids.
parent ea1d7f36
Pipeline #619933 passed with stage
# Copyright (C) 2002-2015 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/>.
import sys
import optparse
from zope.component import getUtility
from mailman.MailList import MailList
from mailman.core.i18n import _
from mailman.initialize import initialize
from mailman.interfaces.listmanager import IListManager
from mailman.version import MAILMAN_VERSION
def parseargs():
parser = optparse.OptionParser(version=MAILMAN_VERSION,
usage=_("""\
%prog [options] [listname ...]
List the owners of a mailing list, or all mailing lists if no list names are
given."""))
parser.add_option('-w', '--with-listnames',
default=False, action='store_true',
help=_("""\
Group the owners by list names and include the list names in the output.
Otherwise, the owners will be sorted and uniquified based on the email
address."""))
parser.add_option('-m', '--moderators',
default=False, action='store_true',
help=_('Include the list moderators in the output.'))
parser.add_option('-C', '--config',
help=_('Alternative configuration file to use'))
opts, args = parser.parse_args()
return parser, opts, args
def main():
parser, opts, args = parseargs()
initialize(opts.config)
list_manager = getUtility(IListManager)
listnames = set(args or list_manager.names)
bylist = {}
for listname in listnames:
mlist = list_manager.get(listname)
addrs = [addr.address for addr in mlist.owners.addresses]
if opts.moderators:
addrs.extend([addr.address for addr in mlist.moderators.addresses])
bylist[listname] = addrs
if opts.with_listnames:
for listname in listnames:
unique = set()
for addr in bylist[listname]:
unique.add(addr)
keys = list(unique)
keys.sort()
print listname
for k in keys:
print '\t', k
else:
unique = set()
for listname in listnames:
for addr in bylist[listname]:
unique.add(addr)
for k in sorted(unique):
print k
if __name__ == '__main__':
main()
...@@ -23,8 +23,8 @@ __all__ = [ ...@@ -23,8 +23,8 @@ __all__ = [
import sys import sys
import codecs
from contextlib import ExitStack
from email.utils import formataddr, parseaddr from email.utils import formataddr, parseaddr
from mailman.app.membership import add_member from mailman.app.membership import add_member
from mailman.core.i18n import _ from mailman.core.i18n import _
...@@ -32,7 +32,7 @@ from mailman.database.transaction import transactional ...@@ -32,7 +32,7 @@ from mailman.database.transaction import transactional
from mailman.interfaces.command import ICLISubCommand from mailman.interfaces.command import ICLISubCommand
from mailman.interfaces.listmanager import IListManager from mailman.interfaces.listmanager import IListManager
from mailman.interfaces.member import ( from mailman.interfaces.member import (
AlreadySubscribedError, DeliveryMode, DeliveryStatus) AlreadySubscribedError, DeliveryMode, DeliveryStatus, MemberRole)
from mailman.interfaces.subscriptions import RequestRecord from mailman.interfaces.subscriptions import RequestRecord
from operator import attrgetter from operator import attrgetter
from zope.component import getUtility from zope.component import getUtility
...@@ -62,6 +62,15 @@ class Members: ...@@ -62,6 +62,15 @@ class Members:
dest='output_filename', metavar='FILENAME', dest='output_filename', metavar='FILENAME',
help=_("""Display output to FILENAME instead of stdout. FILENAME help=_("""Display output to FILENAME instead of stdout. FILENAME
can be '-' to indicate standard output.""")) can be '-' to indicate standard output."""))
command_parser.add_argument(
'-R', '--role',
default=None, metavar='ROLE',
choices=('any', 'owner', 'moderator', 'nonmember', 'member',
'administrator'),
help=_("""Display only members with a given ROLE. The role may be
'any', 'member', 'nonmember', 'owner', 'moderator', or
'administrator' (i.e. owners and moderators). If not
given, then delivery members are used. """))
command_parser.add_argument( command_parser.add_argument(
'-r', '--regular', '-r', '--regular',
default=None, action='store_true', default=None, action='store_true',
...@@ -89,20 +98,26 @@ class Members: ...@@ -89,20 +98,26 @@ class Members:
was disabled for unknown (legacy) reasons.""")) was disabled for unknown (legacy) reasons."""))
# Required positional argument. # Required positional argument.
command_parser.add_argument( command_parser.add_argument(
'listname', metavar='LISTNAME', nargs=1, 'list', metavar='LIST', nargs=1,
help=_("""\ help=_("""\
The 'fully qualified list name', i.e. the posting address of the The list to operate on. This can be the fully qualified list
mailing list. It must be a valid email address and the domain name', i.e. the posting address of the mailing list or the
must be registered with Mailman. List names are forced to lower List-ID."""))
case.""")) command_parser.epilog = _(
"""Display a mailing list's members, with filtering along various
criteria.""")
def process(self, args): def process(self, args):
"""See `ICLISubCommand`.""" """See `ICLISubCommand`."""
assert len(args.listname) == 1, 'Missing mailing list name' assert len(args.list) == 1, 'Missing mailing list name'
fqdn_listname = args.listname[0] list_spec = args.list[0]
mlist = getUtility(IListManager).get(fqdn_listname) list_manager = getUtility(IListManager)
if '@' in list_spec:
mlist = list_manager.get(list_spec)
else:
mlist = list_manager.get_by_list_id(list_spec)
if mlist is None: if mlist is None:
self.parser.error(_('No such list: $fqdn_listname')) self.parser.error(_('No such list: $list_spec'))
if args.input_filename is None: if args.input_filename is None:
self.display_members(mlist, args) self.display_members(mlist, args)
else: else:
...@@ -116,10 +131,6 @@ class Members: ...@@ -116,10 +131,6 @@ class Members:
:param args: The command line arguments. :param args: The command line arguments.
:type args: `argparse.Namespace` :type args: `argparse.Namespace`
""" """
if args.output_filename == '-' or args.output_filename is None:
fp = sys.stdout
else:
fp = codecs.open(args.output_filename, 'w', 'utf-8')
if args.digest == 'any': if args.digest == 'any':
digest_types = [DeliveryMode.plaintext_digests, digest_types = [DeliveryMode.plaintext_digests,
DeliveryMode.mime_digests, DeliveryMode.mime_digests,
...@@ -129,6 +140,7 @@ class Members: ...@@ -129,6 +140,7 @@ class Members:
else: else:
# Don't filter on digest type. # Don't filter on digest type.
pass pass
if args.nomail is None: if args.nomail is None:
# Don't filter on delivery status. # Don't filter on delivery status.
pass pass
...@@ -146,31 +158,49 @@ class Members: ...@@ -146,31 +158,49 @@ class Members:
DeliveryStatus.by_moderator, DeliveryStatus.by_moderator,
DeliveryStatus.unknown] DeliveryStatus.unknown]
else: else:
raise AssertionError('Unknown delivery status: %s' % args.nomail) status = args.nomail
try: self.parser.error(_('Unknown delivery status: $status'))
addresses = list(mlist.members.addresses)
if args.role is None:
# By default, filter on members.
roster = mlist.members
elif args.role == 'administrator':
roster = mlist.administrators
elif args.role == 'any':
roster = mlist.subscribers
else:
try:
roster = mlist.get_roster(MemberRole[args.role])
except KeyError:
role = args.role
self.parser.error(_('Unknown member role: $role'))
with ExitStack() as resources:
if args.output_filename == '-' or args.output_filename is None:
fp = sys.stdout
else:
fp = resources.enter_context(
open(args.output_filename, 'w', encoding='utf-8'))
addresses = list(roster.addresses)
if len(addresses) == 0: if len(addresses) == 0:
print(mlist.fqdn_listname, 'has no members', file=fp) print(_('$mlist.list_id has no members'), file=fp)
return return
for address in sorted(addresses, key=attrgetter('email')): for address in sorted(addresses, key=attrgetter('email')):
if args.regular: if args.regular:
member = mlist.members.get_member(address.email) member = roster.get_member(address.email)
if member.delivery_mode != DeliveryMode.regular: if member.delivery_mode != DeliveryMode.regular:
continue continue
if args.digest is not None: if args.digest is not None:
member = mlist.members.get_member(address.email) member = roster.get_member(address.email)
if member.delivery_mode not in digest_types: if member.delivery_mode not in digest_types:
continue continue
if args.nomail is not None: if args.nomail is not None:
member = mlist.members.get_member(address.email) member = roster.get_member(address.email)
if member.delivery_status not in status_types: if member.delivery_status not in status_types:
continue continue
print( print(
formataddr((address.display_name, address.original_email)), formataddr((address.display_name, address.original_email)),
file=fp) file=fp)
finally:
if fp is not sys.stdout:
fp.close()
@transactional @transactional
def add_members(self, mlist, args): def add_members(self, mlist, args):
...@@ -181,11 +211,12 @@ class Members: ...@@ -181,11 +211,12 @@ class Members:
:param args: The command line arguments. :param args: The command line arguments.
:type args: `argparse.Namespace` :type args: `argparse.Namespace`
""" """
if args.input_filename == '-': with ExitStack() as resources:
fp = sys.stdin if args.input_filename == '-':
else: fp = sys.stdin
fp = codecs.open(args.input_filename, 'r', 'utf-8') else:
try: fp = resources.enter_context(
open(args.input_filename, 'r', encoding='utf-8'))
for line in fp: for line in fp:
# Ignore blank lines and lines that start with a '#'. # Ignore blank lines and lines that start with a '#'.
if line.startswith('#') or len(line.strip()) == 0: if line.startswith('#') or len(line.strip()) == 0:
...@@ -200,8 +231,8 @@ class Members: ...@@ -200,8 +231,8 @@ class Members:
except AlreadySubscribedError: except AlreadySubscribedError:
# It's okay if the address is already subscribed, just # It's okay if the address is already subscribed, just
# print a warning and continue. # print a warning and continue.
print('Already subscribed (skipping):', if not display_name:
email, display_name) print(_('Already subscribed (skipping): $email'))
finally: else:
if fp is not sys.stdin: print(_('Already subscribed (skipping): '
fp.close() '$display_name <$email>'))
...@@ -6,15 +6,16 @@ The ``mailman members`` command allows a site administrator to display, add, ...@@ -6,15 +6,16 @@ The ``mailman members`` command allows a site administrator to display, add,
and remove members from a mailing list. and remove members from a mailing list.
:: ::
>>> mlist1 = create_list('test1@example.com') >>> ant = create_list('ant@example.com')
>>> class FakeArgs: >>> class FakeArgs:
... input_filename = None ... input_filename = None
... output_filename = None ... output_filename = None
... listname = [] ... list = []
... regular = False ... regular = False
... digest = None ... digest = None
... nomail = None ... nomail = None
... role = None
>>> args = FakeArgs() >>> args = FakeArgs()
>>> from mailman.commands.cli_members import Members >>> from mailman.commands.cli_members import Members
...@@ -27,19 +28,18 @@ Listing members ...@@ -27,19 +28,18 @@ Listing members
You can list all the members of a mailing list by calling the command with no You can list all the members of a mailing list by calling the command with no
options. To start with, there are no members of the mailing list. options. To start with, there are no members of the mailing list.
>>> args.listname = [mlist1.fqdn_listname] >>> args.list = ['ant.example.com']
>>> command.process(args) >>> command.process(args)
test1@example.com has no members ant.example.com has no members
Once the mailing list add some members, they will be displayed. Once the mailing list add some members, they will be displayed.
::
>>> from mailman.testing.helpers import subscribe >>> from mailman.testing.helpers import subscribe
>>> subscribe(mlist1, 'Anne', email='anne@example.com') >>> subscribe(ant, 'Anne', email='anne@example.com')
<Member: Anne Person <anne@example.com> on test1@example.com <Member: Anne Person <anne@example.com> on ant@example.com
as MemberRole.member> as MemberRole.member>
>>> subscribe(mlist1, 'Bart', email='bart@example.com') >>> subscribe(ant, 'Bart', email='bart@example.com')
<Member: Bart Person <bart@example.com> on test1@example.com <Member: Bart Person <bart@example.com> on ant@example.com
as MemberRole.member> as MemberRole.member>
>>> command.process(args) >>> command.process(args)
Anne Person <anne@example.com> Anne Person <anne@example.com>
...@@ -48,8 +48,8 @@ Once the mailing list add some members, they will be displayed. ...@@ -48,8 +48,8 @@ Once the mailing list add some members, they will be displayed.
Members are displayed in alphabetical order based on their address. Members are displayed in alphabetical order based on their address.
:: ::
>>> subscribe(mlist1, 'Anne', email='anne@aaaxample.com') >>> subscribe(ant, 'Anne', email='anne@aaaxample.com')
<Member: Anne Person <anne@aaaxample.com> on test1@example.com <Member: Anne Person <anne@aaaxample.com> on ant@example.com
as MemberRole.member> as MemberRole.member>
>>> command.process(args) >>> command.process(args)
Anne Person <anne@aaaxample.com> Anne Person <anne@aaaxample.com>
...@@ -58,17 +58,15 @@ Members are displayed in alphabetical order based on their address. ...@@ -58,17 +58,15 @@ Members are displayed in alphabetical order based on their address.
You can also output this list to a file. You can also output this list to a file.
>>> from tempfile import mkstemp >>> from tempfile import NamedTemporaryFile
>>> fd, args.output_filename = mkstemp() >>> with NamedTemporaryFile() as outfp:
>>> import os ... args.output_filename = outfp.name
>>> os.close(fd) ... command.process(args)
>>> command.process(args) ... with open(args.output_filename) as infp:
>>> with open(args.output_filename) as fp: ... print(infp.read())
... print(fp.read())
Anne Person <anne@aaaxample.com> Anne Person <anne@aaaxample.com>
Anne Person <anne@example.com> Anne Person <anne@example.com>
Bart Person <bart@example.com> Bart Person <bart@example.com>
>>> os.remove(args.output_filename)
>>> args.output_filename = None >>> args.output_filename = None
The output file can also be standard out. The output file can also be standard out.
...@@ -88,7 +86,7 @@ You can limit output to just the regular non-digest members... ...@@ -88,7 +86,7 @@ You can limit output to just the regular non-digest members...
>>> from mailman.interfaces.member import DeliveryMode >>> from mailman.interfaces.member import DeliveryMode
>>> args.regular = True >>> args.regular = True
>>> member = mlist1.members.get_member('anne@example.com') >>> member = ant.members.get_member('anne@example.com')
>>> member.preferences.delivery_mode = DeliveryMode.plaintext_digests >>> member.preferences.delivery_mode = DeliveryMode.plaintext_digests
>>> command.process(args) >>> command.process(args)
Anne Person <anne@aaaxample.com> Anne Person <anne@aaaxample.com>
...@@ -97,7 +95,7 @@ You can limit output to just the regular non-digest members... ...@@ -97,7 +95,7 @@ You can limit output to just the regular non-digest members...
...or just the digest members. Furthermore, you can either display all digest ...or just the digest members. Furthermore, you can either display all digest
members... members...
>>> member = mlist1.members.get_member('anne@aaaxample.com') >>> member = ant.members.get_member('anne@aaaxample.com')
>>> member.preferences.delivery_mode = DeliveryMode.mime_digests >>> member.preferences.delivery_mode = DeliveryMode.mime_digests
>>> args.regular = False >>> args.regular = False
>>> args.digest = 'any' >>> args.digest = 'any'
...@@ -132,16 +130,16 @@ status is enabled... ...@@ -132,16 +130,16 @@ status is enabled...
>>> from mailman.interfaces.member import DeliveryStatus >>> from mailman.interfaces.member import DeliveryStatus
>>> member = mlist1.members.get_member('anne@aaaxample.com') >>> member = ant.members.get_member('anne@aaaxample.com')
>>> member.preferences.delivery_status = DeliveryStatus.by_moderator >>> member.preferences.delivery_status = DeliveryStatus.by_moderator
>>> member = mlist1.members.get_member('bart@example.com') >>> member = ant.members.get_member('bart@example.com')
>>> member.preferences.delivery_status = DeliveryStatus.by_user >>> member.preferences.delivery_status = DeliveryStatus.by_user
>>> member = subscribe(mlist1, 'Cris', email='cris@example.com') >>> member = subscribe(ant, 'Cris', email='cris@example.com')
>>> member.preferences.delivery_status = DeliveryStatus.unknown >>> member.preferences.delivery_status = DeliveryStatus.unknown
>>> member = subscribe(mlist1, 'Dave', email='dave@example.com') >>> member = subscribe(ant, 'Dave', email='dave@example.com')
>>> member.preferences.delivery_status = DeliveryStatus.enabled >>> member.preferences.delivery_status = DeliveryStatus.enabled
>>> member = subscribe(mlist1, 'Elle', email='elle@example.com') >>> member = subscribe(ant, 'Elle', email='elle@example.com')
>>> member.preferences.delivery_status = DeliveryStatus.by_bounces >>> member.preferences.delivery_status = DeliveryStatus.by_bounces
>>> args.nomail = 'enabled' >>> args.nomail = 'enabled'
...@@ -195,23 +193,20 @@ need a file containing email addresses and full names that can be parsed by ...@@ -195,23 +193,20 @@ need a file containing email addresses and full names that can be parsed by
``email.utils.parseaddr()``. ``email.utils.parseaddr()``.
:: ::
>>> mlist2 = create_list('test2@example.com') >>> bee = create_list('bee@example.com')
>>> with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as fp:
>>> import os
>>> path = os.path.join(config.VAR_DIR, 'addresses.txt')
>>> with open(path, 'w') as fp:
... for address in ('aperson@example.com', ... for address in ('aperson@example.com',
... 'Bart Person <bperson@example.com>', ... 'Bart Person <bperson@example.com>',
... 'cperson@example.com (Cate Person)', ... 'cperson@example.com (Cate Person)',
... ): ... ):
... print(address, file=fp) ... print(address, file=fp)
... fp.flush()
>>> args.input_filename = path ... args.input_filename = fp.name
>>> args.listname = [mlist2.fqdn_listname] ... args.list = ['bee.example.com']
>>> command.process(args) ... command.process(args)
>>> from operator import attrgetter >>> from operator import attrgetter
>>> dump_list(mlist2.members.addresses, key=attrgetter('email')) >>> dump_list(bee.members.addresses, key=attrgetter('email'))
aperson@example.com aperson@example.com
Bart Person <bperson@example.com> Bart Person <bperson@example.com>
Cate Person <cperson@example.com> Cate Person <cperson@example.com>
...@@ -227,15 +222,17 @@ taken from standard input. ...@@ -227,15 +222,17 @@ taken from standard input.
... 'fperson@example.com (Fred Person)', ... 'fperson@example.com (Fred Person)',
... ): ... ):
... print(address, file=fp) ... print(address, file=fp)
>>> args.input_filename = '-'
>>> filepos = fp.seek(0) >>> filepos = fp.seek(0)
>>> import sys >>> import sys
>>> sys.stdin = fp >>> try:
... stdin = sys.stdin
>>> args.input_filename = '-' ... sys.stdin = fp
>>> command.process(args) ... command.process(args)
>>> sys.stdin = sys.__stdin__ ... finally:
... sys.stdin = stdin
>>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
>>> dump_list(bee.members.addresses, key=attrgetter('email'))
aperson@example.com aperson@example.com
Bart Person <bperson@example.com> Bart Person <bperson@example.com>
Cate Person <cperson@example.com> Cate Person <cperson@example.com>
...@@ -246,7 +243,7 @@ taken from standard input. ...@@ -246,7 +243,7 @@ taken from standard input.
Blank lines and lines that begin with '#' are ignored. Blank lines and lines that begin with '#' are ignored.
:: ::
>>> with open(path, 'w') as fp: >>> with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as fp:
... for address in ('gperson@example.com', ... for address in ('gperson@example.com',
... '# hperson@example.com', ... '# hperson@example.com',
... ' ', ... ' ',
...@@ -254,10 +251,10 @@ Blank lines and lines that begin with '#' are ignored. ...@@ -254,10 +251,10 @@ Blank lines and lines that begin with '#' are ignored.
... 'iperson@example.com', ... 'iperson@example.com',
... ): ... ):
... print(address, file=fp) ... print(address, file=fp)
... args.input_filename = fp.name
... command.process(args)
>>> args.input_filename = path >>> dump_list(bee.members.addresses, key=attrgetter('email'))
>>> command.process(args)
>>> dump_list(mlist2.members.addresses, key=attrgetter('email'))
aperson@example.com aperson@example.com
Bart Person <bperson@example.com> Bart Person <bperson@example.com>
Cate Person <cperson@example.com> Cate Person <cperson@example.com>
...@@ -271,18 +268,18 @@ Addresses which are already subscribed are ignored, although a warning is ...@@ -271,18 +268,18 @@ Addresses which are already subscribed are ignored, although a warning is
printed. printed.
:: ::
>>> with open(path, 'w') as fp: >>> with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as fp:
... for address in ('gperson@example.com', ... for address in ('gperson@example.com',
... 'aperson@example.com', ... 'aperson@example.com',
... 'jperson@example.com', ... 'jperson@example.com',
... ): ... ):
... print(address, file=fp) ... print(address, file=fp)
... args.input_filename = fp.name
>>> command.process(args) ... command.process(args)
Already subscribed (skipping): gperson@example.com Already subscribed (skipping): gperson@example.com
Already subscribed (skipping): aperson@example.com Already subscribed (skipping): aperson@example.com
>>> dump_list(mlist2.members.addresses, key=attrgetter('email')) >>> dump_list(bee.members.addresses, key=attrgetter('email'))
aperson@example.com aperson@example.com
Bart Person <bperson@example.com> Bart Person <bperson@example.com>
Cate Person <cperson@example.com> Cate Person <cperson@example.com>
......
# Copyright (C) 2015 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 `mailman members` command."""
__all__ = [
'TestCLIMembers',
]
import sys
import unittest
from functools import partial
from io import StringIO
from mailman.app.lifecycle import create_list
from mailman.commands.cli_members import Members
from mailman.interfaces.member import MemberRole
from mailman.testing.helpers import subscribe
from mailman.testing.layers import ConfigLayer
from tempfile import NamedTemporaryFile
from unittest.mock import patch
class FakeArgs:
input_filename = None
output_filename = None
role = None
regular = None
digest = None
nomail = None
list = None
class FakeParser:
def __init__(self):
self.message = None
def error(self, message):
self.message = message
sys.exit(1)
class TestCLIMembers(unittest.TestCase):
layer = ConfigLayer
def setUp(self):
self._mlist = create_list('ant@example.com')
self.command = Members()
self.command.parser = FakeParser()
self.args = FakeArgs()
def test_no_such_list(self):
self.args.list = ['bee.example.com']
with self.assertRaises(SystemExit):
self.command.process(self.args)
self.assertEqual(self.command.parser.message,
'No such list: bee.example.com')
def test_bad_delivery_status(self):
self.args.list = ['ant.example.com']
self.args.nomail = 'bogus'
with self.assertRaises(SystemExit):
self.command.process(self.args)
self.assertEqual(self.command.parser.message,
'Unknown delivery status: bogus')
def test_role_administrator(self):
subscribe(self._mlist, 'Anne', role=MemberRole.owner)
subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
subscribe(self._mlist, 'Dave', role=MemberRole.member)
self.args.list = ['ant.example.com']
self.args.role = 'administrator'
with NamedTemporaryFile('w', encoding='utf-8') as outfp:
self.args.output_filename = outfp.name
self.command.process(self.args)
with open(outfp.name, 'r', encoding='utf-8') as infp:
lines = infp.readlines()
self.assertEqual(len(lines), 2)
self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
self.assertEqual(lines[1], 'Bart Person <bperson@example.com>\n')
def test_role_any(self):
subscribe(self._mlist, 'Anne', role=MemberRole.owner)
subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
subscribe(self._mlist, 'Dave', role=MemberRole.member)
self.args.list = ['ant.example.com']
self.args.role = 'any'
with NamedTemporaryFile('w', encoding='utf-8') as outfp:
self.args.output_filename = outfp.name
self.command.process(self.args)
with open(outfp.name, 'r', encoding='utf-8') as infp:
lines = infp.readlines()
self.assertEqual(len(lines), 4)
self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
self.assertEqual(lines[1], 'Bart Person <bperson@example.com>\n')
self.assertEqual(lines[2], 'Cate Person <cperson@example.com>\n')
self.assertEqual(lines[3], 'Dave Person <dperson@example.com>\n')