Don't RFC 2047 encode non-ascii display names in mailman members output.
[mailman.git] / src / mailman / commands / tests / test_cli_members.py
blob177863766e95844fa127e56d053fca6a503395f1
1 # Copyright (C) 2015-2022 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 <https://www.gnu.org/licenses/>.
18 """Test the `mailman members` command."""
20 import unittest
22 from click.testing import CliRunner
23 from mailman.app.lifecycle import create_list
24 from mailman.commands.cli_members import members
25 from mailman.interfaces.member import MemberRole
26 from mailman.testing.helpers import subscribe
27 from mailman.testing.layers import ConfigLayer
28 from tempfile import NamedTemporaryFile
31 class TestCLIMembers(unittest.TestCase):
32 layer = ConfigLayer
34 def setUp(self):
35 self._mlist = create_list('ant@example.com')
36 self._command = CliRunner()
38 def test_no_such_list(self):
39 result = self._command.invoke(members, ('bee.example.com',))
40 self.assertEqual(result.exit_code, 2)
41 self.assertEqual(
42 result.output,
43 'Usage: members [OPTIONS] LISTSPEC\n'
44 'Try \'members --help\' for help.\n\n'
45 'Error: No such list: bee.example.com\n')
47 def test_role_administrator(self):
48 subscribe(self._mlist, 'Anne', role=MemberRole.owner)
49 subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
50 subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
51 subscribe(self._mlist, 'Dave', role=MemberRole.member)
52 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
53 self._command.invoke(members, (
54 '--role', 'administrator', '-o', outfp.name,
55 'ant.example.com'))
56 with open(outfp.name, 'r', encoding='utf-8') as infp:
57 lines = infp.readlines()
58 self.assertEqual(len(lines), 2)
59 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
60 self.assertEqual(lines[1], 'Bart Person <bperson@example.com>\n')
62 def test_role_any(self):
63 subscribe(self._mlist, 'Anne', role=MemberRole.owner)
64 subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
65 subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
66 subscribe(self._mlist, 'Dave', role=MemberRole.member)
67 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
68 self._command.invoke(members, (
69 '--role', 'any', '-o', outfp.name, 'ant.example.com'))
70 with open(outfp.name, 'r', encoding='utf-8') as infp:
71 lines = infp.readlines()
72 self.assertEqual(len(lines), 4)
73 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
74 self.assertEqual(lines[1], 'Bart Person <bperson@example.com>\n')
75 self.assertEqual(lines[2], 'Cate Person <cperson@example.com>\n')
76 self.assertEqual(lines[3], 'Dave Person <dperson@example.com>\n')
78 def test_role_moderator(self):
79 subscribe(self._mlist, 'Anne', role=MemberRole.owner)
80 subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
81 subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
82 subscribe(self._mlist, 'Dave', role=MemberRole.member)
83 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
84 self._command.invoke(members, (
85 '--role', 'moderator', '-o', outfp.name, 'ant.example.com'))
86 with open(outfp.name, 'r', encoding='utf-8') as infp:
87 lines = infp.readlines()
88 self.assertEqual(len(lines), 1)
89 self.assertEqual(lines[0], 'Bart Person <bperson@example.com>\n')
91 def test_role_nonmember(self):
92 subscribe(self._mlist, 'Anne', role=MemberRole.owner)
93 subscribe(self._mlist, 'Bart', role=MemberRole.moderator)
94 subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
95 subscribe(self._mlist, 'Dave', role=MemberRole.member)
96 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
97 self._command.invoke(members, (
98 '--role', 'nonmember', '-o', outfp.name, 'ant.example.com'))
99 with open(outfp.name, 'r', encoding='utf-8') as infp:
100 lines = infp.readlines()
101 self.assertEqual(len(lines), 1)
102 self.assertEqual(lines[0], 'Cate Person <cperson@example.com>\n')
104 def test_already_subscribed_with_display_name(self):
105 subscribe(self._mlist, 'Anne')
106 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
107 print('Anne Person <aperson@example.com>', file=infp)
108 result = self._command.invoke(members, (
109 '--add', infp.name, 'ant.example.com'))
110 self.assertEqual(
111 result.output,
112 'Usage: members [OPTIONS] LISTSPEC\n'
113 'Try \'members --help\' for help.\n\n'
114 'Error: The --add option is removed. Use '
115 '`mailman addmembers` instead.\n')
117 def test_add_invalid_email(self):
118 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
119 print('foobar@', file=infp)
120 result = self._command.invoke(members, (
121 '--add', infp.name, 'ant.example.com'))
122 self.assertEqual(
123 result.output,
124 'Usage: members [OPTIONS] LISTSPEC\n'
125 'Try \'members --help\' for help.\n\n'
126 'Error: The --add option is removed. Use '
127 '`mailman addmembers` instead.\n')
129 def test_not_subscribed_without_display_name(self):
130 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
131 print('aperson@example.com', file=infp)
132 result = self._command.invoke(members, (
133 '--delete', infp.name, 'ant.example.com'))
134 self.assertEqual(
135 result.output,
136 'Usage: members [OPTIONS] LISTSPEC\n'
137 'Try \'members --help\' for help.\n\n'
138 'Error: The --delete option is removed. Use '
139 '`mailman delmembers` instead.\n')
141 def test_not_subscribed_with_display_name(self):
142 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
143 print('Anne Person <aperson@example.com>', file=infp)
144 result = self._command.invoke(members, (
145 '--delete', infp.name, 'ant.example.com'))
146 self.assertEqual(
147 result.output,
148 'Usage: members [OPTIONS] LISTSPEC\n'
149 'Try \'members --help\' for help.\n\n'
150 'Error: The --delete option is removed. Use '
151 '`mailman delmembers` instead.\n')
153 def test_deletion_blank_lines(self):
154 subscribe(self._mlist, 'Anne')
155 subscribe(self._mlist, 'Bart')
156 subscribe(self._mlist, 'Cate')
157 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
158 print('Anne Person <aperson@example.com>', file=infp)
159 print('', file=infp)
160 print(' ', file=infp)
161 print('\t', file=infp)
162 print('Bart Person <bperson@example.com>', file=infp)
163 result = self._command.invoke(members, (
164 '--delete', infp.name, 'ant.example.com'))
165 self.assertEqual(
166 result.output,
167 'Usage: members [OPTIONS] LISTSPEC\n'
168 'Try \'members --help\' for help.\n\n'
169 'Error: The --delete option is removed. Use '
170 '`mailman delmembers` instead.\n')
171 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
172 self._command.invoke(members, (
173 '-o', outfp.name, 'ant.example.com'))
174 with open(outfp.name, 'r', encoding='utf-8') as infp:
175 lines = infp.readlines()
176 self.assertEqual(len(lines), 3)
177 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
179 def test_deletion_commented_lines(self):
180 subscribe(self._mlist, 'Anne')
181 subscribe(self._mlist, 'Bart')
182 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
183 print('Anne Person <aperson@example.com>', file=infp)
184 print('#Bart Person <bperson@example.com>', file=infp)
185 result = self._command.invoke(members, (
186 '--delete', infp.name, 'ant.example.com'))
187 self.assertEqual(
188 result.output,
189 'Usage: members [OPTIONS] LISTSPEC\n'
190 'Try \'members --help\' for help.\n\n'
191 'Error: The --delete option is removed. Use '
192 '`mailman delmembers` instead.\n')
194 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
195 self._command.invoke(members, (
196 '-o', outfp.name, 'ant.example.com'))
197 with open(outfp.name, 'r', encoding='utf-8') as infp:
198 lines = infp.readlines()
199 self.assertEqual(len(lines), 2)
200 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
202 def test_sync_invalid_email(self):
203 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
204 print('Dont Subscribe <not-a-valid-email>', file=infp)
205 print('not-a-valid@email', file=infp)
206 result = self._command.invoke(members, (
207 '--sync', infp.name, 'ant.example.com'))
208 self.assertEqual(
209 result.output,
210 'Usage: members [OPTIONS] LISTSPEC\n'
211 'Try \'members --help\' for help.\n\n'
212 'Error: The --sync option is removed. '
213 'Use `mailman syncmembers` instead.\n')
215 def test_sync_no_change(self):
216 subscribe(self._mlist, 'Anne')
217 subscribe(self._mlist, 'Bart')
218 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
219 print('Anne Person <aperson@example.com>', file=infp)
220 result = self._command.invoke(members, (
221 '--no-change', '--sync', infp.name, 'ant.example.com'))
222 self.assertEqual(
223 result.output,
224 'Usage: members [OPTIONS] LISTSPEC\n'
225 'Try \'members --help\' for help.\n\n'
226 'Error: The --sync option is removed. '
227 'Use `mailman syncmembers` instead.\n')
229 def test_sync_empty_tuple(self):
230 subscribe(self._mlist, 'Anne')
231 subscribe(self._mlist, 'Bart')
232 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
233 print('Anne Person <aperson@example.com>', file=infp)
234 print('\"\"', file=infp)
235 result = self._command.invoke(members, (
236 '--no-change', '--sync', infp.name, 'ant.example.com'))
237 self.assertEqual(
238 result.output,
239 'Usage: members [OPTIONS] LISTSPEC\n'
240 'Try \'members --help\' for help.\n\n'
241 'Error: The --sync option is removed. '
242 'Use `mailman syncmembers` instead.\n')
244 def test_sync_commented_lines(self):
245 subscribe(self._mlist, 'Anne')
246 subscribe(self._mlist, 'Bart')
247 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
248 print('Anne Person <aperson@example.com>', file=infp)
249 print('#Bart Person <bperson@example.com>', file=infp)
250 result = self._command.invoke(members, (
251 '--sync', infp.name, 'ant.example.com'))
252 self.assertEqual(
253 result.output,
254 'Usage: members [OPTIONS] LISTSPEC\n'
255 'Try \'members --help\' for help.\n\n'
256 'Error: The --sync option is removed. '
257 'Use `mailman syncmembers` instead.\n')
259 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
260 self._command.invoke(members, (
261 '-o', outfp.name, 'ant.example.com'))
262 with open(outfp.name, 'r', encoding='utf-8') as infp:
263 lines = infp.readlines()
264 self.assertEqual(len(lines), 2)
265 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
267 def test_sync_blank_lines(self):
268 subscribe(self._mlist, 'Anne')
269 subscribe(self._mlist, 'Bart')
270 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
271 print('Anne Person <aperson@example.com>', file=infp)
272 print('', file=infp)
273 print('', file=infp)
274 result = self._command.invoke(members, (
275 '--sync', infp.name, 'ant.example.com'))
276 self.assertEqual(
277 result.output,
278 'Usage: members [OPTIONS] LISTSPEC\n'
279 'Try \'members --help\' for help.\n\n'
280 'Error: The --sync option is removed. '
281 'Use `mailman syncmembers` instead.\n')
283 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
284 self._command.invoke(members, (
285 '-o', outfp.name, 'ant.example.com'))
286 with open(outfp.name, 'r', encoding='utf-8') as infp:
287 lines = infp.readlines()
288 self.assertEqual(len(lines), 2)
289 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
291 def test_sync_nothing_to_do(self):
292 subscribe(self._mlist, 'Anne')
293 subscribe(self._mlist, 'Bart')
294 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
295 print('Anne Person <aperson@example.com>', file=infp)
296 print('Bart Person <bperson@example.com>', file=infp)
297 result = self._command.invoke(members, (
298 '--sync', infp.name, 'ant.example.com'))
299 self.assertEqual(
300 result.output,
301 'Usage: members [OPTIONS] LISTSPEC\n'
302 'Try \'members --help\' for help.\n\n'
303 'Error: The --sync option is removed. '
304 'Use `mailman syncmembers` instead.\n')
306 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
307 self._command.invoke(members, (
308 '-o', outfp.name, 'ant.example.com'))
309 with open(outfp.name, 'r', encoding='utf-8') as infp:
310 lines = infp.readlines()
311 self.assertEqual(len(lines), 2)
312 self.assertEqual(lines[0], 'Anne Person <aperson@example.com>\n')
313 self.assertEqual(lines[1], 'Bart Person <bperson@example.com>\n')
315 def test_sync_no_display_name(self):
316 subscribe(self._mlist, 'Bart')
317 subscribe(self._mlist, 'Cate', role=MemberRole.nonmember)
318 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
319 print('<aperson@example.com>', file=infp)
320 result = self._command.invoke(members, (
321 '--sync', infp.name, 'ant.example.com'))
322 self.assertEqual(
323 result.output,
324 'Usage: members [OPTIONS] LISTSPEC\n'
325 'Try \'members --help\' for help.\n\n'
326 'Error: The --sync option is removed. '
327 'Use `mailman syncmembers` instead.\n')
329 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
330 self._command.invoke(members, (
331 '-o', outfp.name, 'ant.example.com'))
332 with open(outfp.name, 'r', encoding='utf-8') as infp:
333 lines = infp.readlines()
334 self.assertEqual(len(lines), 1)
335 self.assertEqual(lines[0], 'Bart Person <bperson@example.com>\n')
337 def test_sync_del_no_display_name(self):
338 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
339 print('bperson@example.com', file=infp)
340 result = self._command.invoke(members, (
341 '--add', infp.name, 'ant.example.com'))
343 with NamedTemporaryFile('w', buffering=1, encoding='utf-8') as infp:
344 print('<aperson@example.com>', file=infp)
345 result = self._command.invoke(members, (
346 '--sync', infp.name, 'ant.example.com'))
347 self.assertEqual(
348 result.output,
349 'Usage: members [OPTIONS] LISTSPEC\n'
350 'Try \'members --help\' for help.\n\n'
351 'Error: The --sync option is removed. '
352 'Use `mailman syncmembers` instead.\n')
354 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
355 self._command.invoke(members, (
356 '-o', outfp.name, 'ant.example.com'))
357 with open(outfp.name, 'r', encoding='utf-8') as infp:
358 lines = infp.readlines()
359 self.assertEqual(len(lines), 1)
360 self.assertEqual(lines[0], 'ant.example.com has no members\n')
362 def test_email_only(self):
363 subscribe(self._mlist, 'Anne')
364 subscribe(self._mlist, 'Bart')
365 result = self._command.invoke(members, (
366 '--email-only', 'ant.example.com'))
367 self.assertEqual(
368 result.output, 'aperson@example.com\nbperson@example.com\n')
370 def test_count_only(self):
371 subscribe(self._mlist, 'Anne')
372 subscribe(self._mlist, 'Bart')
373 result = self._command.invoke(members, (
374 '--count-only', 'ant.example.com'))
375 self.assertEqual(
376 result.output, '2\n')
378 def test_incompatible_email_only_count_only(self):
379 result = self._command.invoke(members, (
380 '--email-only', '--count-only', 'ant.example.com'))
381 self.assertEqual(
382 result.output,
383 'Usage: members [OPTIONS] LISTSPEC\n'
384 'Try \'members --help\' for help.\n\n'
385 'Error: The --email_only and --count_only options are '
386 'mutually exclusive.\n')
388 def test_incompatible_role_any_regular(self):
389 result = self._command.invoke(members, (
390 '--role', 'any', '--regular', 'ant.example.com'))
391 self.assertEqual(
392 result.output,
393 'Usage: members [OPTIONS] LISTSPEC\n'
394 'Try \'members --help\' for help.\n\n'
395 'Error: The --regular, --digest and --nomail options are '
396 'incompatible with role=any.\n')
398 def test_incompatible_role_any_digest(self):
399 result = self._command.invoke(members, (
400 '--role', 'any', '--digest', 'any', 'ant.example.com'))
401 self.assertEqual(
402 result.output,
403 'Usage: members [OPTIONS] LISTSPEC\n'
404 'Try \'members --help\' for help.\n\n'
405 'Error: The --regular, --digest and --nomail options are '
406 'incompatible with role=any.\n')
408 def test_incompatible_role_any_nomail(self):
409 result = self._command.invoke(members, (
410 '--role', 'any', '--nomail', 'any', 'ant.example.com'))
411 self.assertEqual(
412 result.output,
413 'Usage: members [OPTIONS] LISTSPEC\n'
414 'Try \'members --help\' for help.\n\n'
415 'Error: The --regular, --digest and --nomail options are '
416 'incompatible with role=any.\n')
418 def test_non_ascii_display_name(self):
419 subscribe(self._mlist, 'Bögüs', role=MemberRole.member)
420 with NamedTemporaryFile('w', encoding='utf-8') as outfp:
421 self._command.invoke(members, (
422 '-o', outfp.name, 'ant.example.com'))
423 with open(outfp.name, 'r', encoding='utf-8') as infp:
424 lines = infp.readlines()
425 self.assertEqual(len(lines), 1)
426 self.assertEqual(lines[0], 'Bögüs Person <bperson@example.com>\n')