~abompard/mailman/moderation_reasons

« back to all changes in this revision

Viewing changes to src/mailman/utilities/tests/test_import.py

  • Committer: Barry Warsaw
  • Date: 2014-04-14 18:00:23 UTC
  • mfrom: (7226.2.3 abompard)
  • Revision ID: barry@list.org-20140414180023-v26bi1l2xb9v08m2
 * Greatly improve the fidelity of the Mailman 2.1 list importer functionality
   (i.e. ``mailman import21``).  [Aurélien Bompard].

 * Fixed a typo in the IMailingList interface.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
 
28
28
 
29
29
import cPickle
 
30
import os
30
31
import unittest
31
32
 
32
 
from mailman.app.lifecycle import create_list, remove_list
 
33
from datetime import timedelta, datetime
 
34
from enum import Enum
 
35
from pkg_resources import resource_filename
 
36
from zope.component import getUtility
 
37
 
 
38
from mailman.app.lifecycle import create_list
 
39
from mailman.config import config
 
40
from mailman.handlers.decorate import decorate
 
41
from mailman.interfaces.action import Action, FilterAction
33
42
from mailman.interfaces.archiver import ArchivePolicy
 
43
from mailman.interfaces.autorespond import ResponseAction
 
44
from mailman.interfaces.bans import IBanManager
 
45
from mailman.interfaces.bounce import UnrecognizedBounceDisposition
 
46
from mailman.interfaces.languages import ILanguageManager
 
47
from mailman.interfaces.mailinglist import IAcceptableAliasSet
 
48
from mailman.interfaces.member import DeliveryMode, DeliveryStatus
 
49
from mailman.interfaces.nntp import NewsgroupModeration
 
50
from mailman.interfaces.templates import ITemplateLoader
 
51
from mailman.interfaces.usermanager import IUserManager
34
52
from mailman.testing.layers import ConfigLayer
35
 
from mailman.utilities.importer import import_config_pck
36
 
from pkg_resources import resource_filename
 
53
from mailman.utilities.filesystem import makedirs
 
54
from mailman.utilities.importer import import_config_pck, Import21Error
 
55
from mailman.utilities.string import expand
 
56
 
 
57
 
 
58
 
 
59
NL = '\n'
 
60
 
 
61
 
 
62
class DummyEnum(Enum):
 
63
    # For testing purposes
 
64
    val = 42
 
65
 
 
66
 
 
67
def list_to_string(data):
 
68
    return NL.join(data).encode('utf-8')
37
69
 
38
70
 
39
71
 
46
78
        with open(pickle_file) as fp:
47
79
            self._pckdict = cPickle.load(fp)
48
80
 
49
 
    def tearDown(self):
50
 
        remove_list(self._mlist)
51
 
 
52
81
    def _import(self):
53
82
        import_config_pck(self._mlist, self._pckdict)
54
83
 
58
87
        self._import()
59
88
        self.assertEqual(self._mlist.display_name, 'Test')
60
89
 
61
 
    def test_mail_host(self):
62
 
        # The mlist.mail_host gets set.
 
90
    def test_mail_host_invariant(self):
 
91
        # The mlist.mail_host must not be updated when importing (it will
 
92
        # change the list_id property, which is supposed to be read-only).
63
93
        self.assertEqual(self._mlist.mail_host, 'example.com')
64
94
        self._import()
65
 
        self.assertEqual(self._mlist.mail_host, 'heresy.example.org')
 
95
        self.assertEqual(self._mlist.mail_host, 'example.com')
66
96
 
67
97
    def test_rfc2369_headers(self):
68
98
        self._mlist.allow_list_posts = False
71
101
        self.assertTrue(self._mlist.allow_list_posts)
72
102
        self.assertTrue(self._mlist.include_rfc2369_headers)
73
103
 
 
104
    def test_no_overwrite_rosters(self):
 
105
        # The mlist.members and mlist.digest_members rosters must not be
 
106
        # overwritten.
 
107
        for rname in ('members', 'digest_members'):
 
108
            roster = getattr(self._mlist, rname)
 
109
            self.assertFalse(isinstance(roster, dict))
 
110
            self._import()
 
111
            self.assertFalse(
 
112
                isinstance(roster, dict),
 
113
                'The %s roster has been overwritten by the import' % rname)
 
114
 
 
115
    def test_last_post_time(self):
 
116
        # last_post_time -> last_post_at
 
117
        self._pckdict['last_post_time'] = 1270420800.274485
 
118
        self.assertEqual(self._mlist.last_post_at, None)
 
119
        self._import()
 
120
        # convert 1270420800.2744851 to datetime
 
121
        expected = datetime(2010, 4, 4, 22, 40, 0, 274485)
 
122
        self.assertEqual(self._mlist.last_post_at, expected)
 
123
 
 
124
    def test_autoresponse_grace_period(self):
 
125
        # autoresponse_graceperiod -> autoresponse_grace_period
 
126
        # must be a timedelta, not an int
 
127
        self._mlist.autoresponse_grace_period = timedelta(days=42)
 
128
        self._import()
 
129
        self.assertTrue(
 
130
            isinstance(self._mlist.autoresponse_grace_period, timedelta))
 
131
        self.assertEqual(self._mlist.autoresponse_grace_period,
 
132
                         timedelta(days=90))
 
133
 
 
134
    def test_autoresponse_admin_to_owner(self):
 
135
        # admin -> owner
 
136
        self._mlist.autorespond_owner = DummyEnum.val
 
137
        self._mlist.autoresponse_owner_text = 'DUMMY'
 
138
        self._import()
 
139
        self.assertEqual(self._mlist.autorespond_owner, ResponseAction.none)
 
140
        self.assertEqual(self._mlist.autoresponse_owner_text, '')
 
141
 
 
142
    def test_administrativia(self):
 
143
       self._mlist.administrivia = None
 
144
       self._import()
 
145
       self.assertTrue(self._mlist.administrivia)
 
146
 
 
147
    def test_filter_pass_renames(self):
 
148
        # mime_types -> types
 
149
        # filename_extensions -> extensions
 
150
        self._mlist.filter_types = ['dummy']
 
151
        self._mlist.pass_types = ['dummy']
 
152
        self._mlist.filter_extensions = ['dummy']
 
153
        self._mlist.pass_extensions = ['dummy']
 
154
        self._import()
 
155
        self.assertEqual(list(self._mlist.filter_types), [])
 
156
        self.assertEqual(list(self._mlist.filter_extensions),
 
157
                         ['exe', 'bat', 'cmd', 'com', 'pif',
 
158
                          'scr', 'vbs', 'cpl'])
 
159
        self.assertEqual(list(self._mlist.pass_types),
 
160
                ['multipart/mixed', 'multipart/alternative', 'text/plain'])
 
161
        self.assertEqual(list(self._mlist.pass_extensions), [])
 
162
 
 
163
    def test_process_bounces(self):
 
164
        # bounce_processing -> process_bounces
 
165
        self._mlist.process_bounces = None
 
166
        self._import()
 
167
        self.assertTrue(self._mlist.process_bounces)
 
168
 
 
169
    def test_forward_unrecognized_bounces_to(self):
 
170
        # bounce_unrecognized_goes_to_list_owner
 
171
        #   -> forward_unrecognized_bounces_to
 
172
        self._mlist.forward_unrecognized_bounces_to = DummyEnum.val
 
173
        self._import()
 
174
        self.assertEqual(self._mlist.forward_unrecognized_bounces_to,
 
175
                         UnrecognizedBounceDisposition.administrators)
 
176
 
 
177
    def test_moderator_password(self):
 
178
        # mod_password -> moderator_password
 
179
        self._mlist.moderator_password = str('TESTDATA')
 
180
        self._import()
 
181
        self.assertEqual(self._mlist.moderator_password, None)
 
182
 
 
183
    def test_moderator_password_str(self):
 
184
        # moderator_password must not be unicode
 
185
        self._pckdict[b'mod_password'] = b'TESTVALUE'
 
186
        self._import()
 
187
        self.assertFalse(isinstance(self._mlist.moderator_password, unicode))
 
188
        self.assertEqual(self._mlist.moderator_password, b'TESTVALUE')
 
189
 
 
190
    def test_newsgroup_moderation(self):
 
191
        # news_moderation -> newsgroup_moderation
 
192
        # news_prefix_subject_too -> nntp_prefix_subject_too
 
193
        self._mlist.newsgroup_moderation = DummyEnum.val
 
194
        self._mlist.nntp_prefix_subject_too = None
 
195
        self._import()
 
196
        self.assertEqual(self._mlist.newsgroup_moderation,
 
197
                         NewsgroupModeration.none)
 
198
        self.assertTrue(self._mlist.nntp_prefix_subject_too)
 
199
 
 
200
    def test_msg_to_message(self):
 
201
        # send_welcome_msg -> send_welcome_message
 
202
        # send_goodbye_msg -> send_goodbye_message
 
203
        self._mlist.send_welcome_message = None
 
204
        self._mlist.send_goodbye_message = None
 
205
        self._import()
 
206
        self.assertTrue(self._mlist.send_welcome_message)
 
207
        self.assertTrue(self._mlist.send_goodbye_message)
 
208
 
 
209
    def test_ban_list(self):
 
210
        banned = [
 
211
            ('anne@example.com', 'anne@example.com'),
 
212
            ('^.*@example.com', 'bob@example.com'),
 
213
            ('non-ascii-\xe8@example.com', 'non-ascii-\ufffd@example.com'),
 
214
            ]
 
215
        self._pckdict['ban_list'] = [b[0].encode('iso-8859-1') for b in banned]
 
216
        self._import()
 
217
        for _pattern, addr in banned:
 
218
            self.assertTrue(IBanManager(self._mlist).is_banned(addr))
 
219
 
 
220
    def test_acceptable_aliases(self):
 
221
        # This used to be a plain-text field (values are newline-separated).
 
222
        aliases = ['alias1@example.com',
 
223
                   'alias2@exemple.com',
 
224
                   'non-ascii-\xe8@example.com',
 
225
                   ]
 
226
        self._pckdict[b'acceptable_aliases'] = list_to_string(aliases)
 
227
        self._import()
 
228
        alias_set = IAcceptableAliasSet(self._mlist)
 
229
        self.assertEqual(sorted(alias_set.aliases), aliases)
 
230
 
 
231
    def test_acceptable_aliases_invalid(self):
 
232
        # Values without an '@' sign used to be matched against the local
 
233
        # part, now we need to add the '^' sign to indicate it's a regexp.
 
234
        aliases = ['invalid-value']
 
235
        self._pckdict[b'acceptable_aliases'] = list_to_string(aliases)
 
236
        self._import()
 
237
        alias_set = IAcceptableAliasSet(self._mlist)
 
238
        self.assertEqual(sorted(alias_set.aliases),
 
239
                         [('^' + alias) for alias in aliases])
 
240
 
 
241
    def test_acceptable_aliases_as_list(self):
 
242
        # In some versions of the pickle, this can be a list, not a string
 
243
        # (seen in the wild).
 
244
        aliases = [b'alias1@example.com', b'alias2@exemple.com' ]
 
245
        self._pckdict[b'acceptable_aliases'] = aliases
 
246
        self._import()
 
247
        alias_set = IAcceptableAliasSet(self._mlist)
 
248
        self.assertEqual(sorted(alias_set.aliases), aliases)
 
249
 
 
250
    def test_info_non_ascii(self):
 
251
        # info can contain non-ascii characters.
 
252
        info = 'O idioma aceito \xe9 somente Portugu\xeas do Brasil'
 
253
        self._pckdict[b'info'] = info.encode('utf-8')
 
254
        self._import()
 
255
        self.assertEqual(self._mlist.info, info,
 
256
                         'Encoding to UTF-8 is not handled')
 
257
        # Test fallback to ascii with replace.
 
258
        self._pckdict[b'info'] = info.encode('iso-8859-1')
 
259
        self._import()
 
260
        self.assertEqual(self._mlist.info,
 
261
                         unicode(self._pckdict[b'info'], 'ascii', 'replace'),
 
262
                         "We don't fall back to replacing non-ascii chars")
 
263
 
 
264
    def test_preferred_language(self):
 
265
        self._pckdict[b'preferred_language'] = b'ja'
 
266
        english = getUtility(ILanguageManager).get('en')
 
267
        japanese = getUtility(ILanguageManager).get('ja')
 
268
        self.assertEqual(self._mlist.preferred_language, english)
 
269
        self._import()
 
270
        self.assertEqual(self._mlist.preferred_language, japanese)
 
271
 
 
272
    def test_preferred_language_unknown_previous(self):
 
273
        # When the previous language is unknown, it should not fail.
 
274
        self._mlist._preferred_language = 'xx'
 
275
        self._import()
 
276
        english = getUtility(ILanguageManager).get('en')
 
277
        self.assertEqual(self._mlist.preferred_language, english)
 
278
 
 
279
    def test_new_language(self):
 
280
        self._pckdict[b'preferred_language'] = b'xx_XX'
 
281
        try:
 
282
            self._import()
 
283
        except Import21Error as error:
 
284
            # Check the message.
 
285
            self.assertIn('[language.xx_XX]', str(error))
 
286
        else:
 
287
            self.fail('Import21Error was not raised')
 
288
 
74
289
 
75
290
 
76
291
class TestArchiveImport(unittest.TestCase):
83
298
 
84
299
    def setUp(self):
85
300
        self._mlist = create_list('blank@example.com')
86
 
        self._mlist.archive_policy = 'INITIAL-TEST-VALUE'
 
301
        self._mlist.archive_policy = DummyEnum.val
87
302
 
88
303
    def _do_test(self, pckdict, expected):
89
304
        import_config_pck(self._mlist, pckdict)
123
338
        # For some reason, the old list was missing an `archive_private` key.
124
339
        # For maximum safety, we treat this as private archiving.
125
340
        self._do_test(dict(archive=True), ArchivePolicy.private)
 
341
 
 
342
 
 
343
 
 
344
class TestFilterActionImport(unittest.TestCase):
 
345
    # The mlist.filter_action enum values have changed.  In Mailman 2.1 the
 
346
    # order was 'Discard', 'Reject', 'Forward to List Owner', 'Preserve'.
 
347
 
 
348
    layer = ConfigLayer
 
349
 
 
350
    def setUp(self):
 
351
        self._mlist = create_list('blank@example.com')
 
352
        self._mlist.filter_action = DummyEnum.val
 
353
 
 
354
    def _do_test(self, original, expected):
 
355
        import_config_pck(self._mlist, dict(filter_action=original))
 
356
        self.assertEqual(self._mlist.filter_action, expected)
 
357
 
 
358
    def test_discard(self):
 
359
        self._do_test(0, FilterAction.discard)
 
360
 
 
361
    def test_reject(self):
 
362
        self._do_test(1, FilterAction.reject)
 
363
 
 
364
    def test_forward(self):
 
365
        self._do_test(2, FilterAction.forward)
 
366
 
 
367
    def test_preserve(self):
 
368
        self._do_test(3, FilterAction.preserve)
 
369
 
 
370
 
 
371
 
 
372
class TestMemberActionImport(unittest.TestCase):
 
373
    # The mlist.default_member_action and mlist.default_nonmember_action enum
 
374
    # values are different in Mailman 2.1; they have been merged into a
 
375
    # single enum in Mailman 3.
 
376
    #
 
377
    # For default_member_action, which used to be called
 
378
    # member_moderation_action, the values were:
 
379
    # 0==Hold, 1=Reject, 2==Discard
 
380
    #
 
381
    # For default_nonmember_action, which used to be called
 
382
    # generic_nonmember_action, the values were:
 
383
    # 0==Accept, 1==Hold, 2==Reject, 3==Discard
 
384
 
 
385
    layer = ConfigLayer
 
386
 
 
387
    def setUp(self):
 
388
        self._mlist = create_list('blank@example.com')
 
389
        self._mlist.default_member_action = DummyEnum.val
 
390
        self._mlist.default_nonmember_action = DummyEnum.val
 
391
        self._pckdict = dict(
 
392
            member_moderation_action=DummyEnum.val,
 
393
            generic_nonmember_action=DummyEnum.val,
 
394
            )
 
395
 
 
396
    def _do_test(self, expected):
 
397
        import_config_pck(self._mlist, self._pckdict)
 
398
        for key, value in expected.iteritems():
 
399
            self.assertEqual(getattr(self._mlist, key), value)
 
400
 
 
401
    def test_member_hold(self):
 
402
        self._pckdict[b'member_moderation_action'] = 0
 
403
        self._do_test(dict(default_member_action=Action.hold))
 
404
 
 
405
    def test_member_reject(self):
 
406
        self._pckdict[b'member_moderation_action'] = 1
 
407
        self._do_test(dict(default_member_action=Action.reject))
 
408
 
 
409
    def test_member_discard(self):
 
410
        self._pckdict[b'member_moderation_action'] = 2
 
411
        self._do_test(dict(default_member_action=Action.discard))
 
412
 
 
413
    def test_nonmember_accept(self):
 
414
        self._pckdict[b'generic_nonmember_action'] = 0
 
415
        self._do_test(dict(default_nonmember_action=Action.accept))
 
416
 
 
417
    def test_nonmember_hold(self):
 
418
        self._pckdict[b'generic_nonmember_action'] = 1
 
419
        self._do_test(dict(default_nonmember_action=Action.hold))
 
420
 
 
421
    def test_nonmember_reject(self):
 
422
        self._pckdict[b'generic_nonmember_action'] = 2
 
423
        self._do_test(dict(default_nonmember_action=Action.reject))
 
424
 
 
425
    def test_nonmember_discard(self):
 
426
        self._pckdict[b'generic_nonmember_action'] = 3
 
427
        self._do_test(dict(default_nonmember_action=Action.discard))
 
428
 
 
429
 
 
430
 
 
431
class TestConvertToURI(unittest.TestCase):
 
432
    # The following values were plain text, and are now URIs in Mailman 3:
 
433
    # - welcome_message_uri
 
434
    # - goodbye_message_uri
 
435
    # - header_uri
 
436
    # - footer_uri
 
437
    # - digest_header_uri
 
438
    # - digest_footer_uri
 
439
    #
 
440
    # The templates contain variables that must be replaced:
 
441
    # - %(real_name)s -> %(display_name)s
 
442
    # - %(real_name)s@%(host_name)s -> %(fqdn_listname)s
 
443
    # - %(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s
 
444
    #       -> %(listinfo_uri)s
 
445
 
 
446
    layer = ConfigLayer
 
447
 
 
448
    def setUp(self):
 
449
        self._mlist = create_list('blank@example.com')
 
450
        self._conf_mapping = dict(
 
451
            welcome_msg='welcome_message_uri',
 
452
            goodbye_msg='goodbye_message_uri',
 
453
            msg_header='header_uri',
 
454
            msg_footer='footer_uri',
 
455
            digest_header='digest_header_uri',
 
456
            digest_footer='digest_footer_uri',
 
457
            )
 
458
        self._pckdict = dict()
 
459
 
 
460
    def test_text_to_uri(self):
 
461
        for oldvar, newvar in self._conf_mapping.items():
 
462
            self._pckdict[str(oldvar)] = b'TEST VALUE'
 
463
            import_config_pck(self._mlist, self._pckdict)
 
464
            newattr = getattr(self._mlist, newvar)
 
465
            text = decorate(self._mlist, newattr)
 
466
            self.assertEqual(text, 'TEST VALUE',
 
467
                    'Old variable %s was not properly imported to %s'
 
468
                    % (oldvar, newvar))
 
469
 
 
470
    def test_substitutions(self):
 
471
        test_text = ('UNIT TESTING %(real_name)s mailing list\n'
 
472
                     '%(real_name)s@%(host_name)s\n'
 
473
                     '%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s')
 
474
        expected_text = ('UNIT TESTING $display_name mailing list\n'
 
475
                         '$fqdn_listname\n'
 
476
                         '$listinfo_uri')
 
477
        for oldvar, newvar in self._conf_mapping.items():
 
478
            self._pckdict[str(oldvar)] = str(test_text)
 
479
            import_config_pck(self._mlist, self._pckdict)
 
480
            newattr = getattr(self._mlist, newvar)
 
481
            template_uri = expand(newattr, dict(
 
482
                listname=self._mlist.fqdn_listname,
 
483
                language=self._mlist.preferred_language.code,
 
484
                ))
 
485
            loader = getUtility(ITemplateLoader)
 
486
            text = loader.get(template_uri)
 
487
            self.assertEqual(text, expected_text,
 
488
                    'Old variables were not converted for %s' % newvar)
 
489
 
 
490
    def test_keep_default(self):
 
491
        # If the value was not changed from MM2.1's default, don't import it.
 
492
        default_msg_footer = (
 
493
            '_______________________________________________\n'
 
494
            '%(real_name)s mailing list\n'
 
495
            '%(real_name)s@%(host_name)s\n'
 
496
            '%(web_page_url)slistinfo%(cgiext)s/%(_internal_name)s'
 
497
            )
 
498
        for oldvar in ('msg_footer', 'digest_footer'):
 
499
            newvar = self._conf_mapping[oldvar]
 
500
            self._pckdict[str(oldvar)] = str(default_msg_footer)
 
501
            old_value = getattr(self._mlist, newvar)
 
502
            import_config_pck(self._mlist, self._pckdict)
 
503
            new_value = getattr(self._mlist, newvar)
 
504
            self.assertEqual(old_value, new_value,
 
505
                    'Default value was not preserved for %s' % newvar)
 
506
 
 
507
    def test_keep_default_if_fqdn_changed(self):
 
508
        # Use case: importing the old a@ex.com into b@ex.com.  We can't check
 
509
        # if it changed from the default so don't import.  We may do more harm
 
510
        # than good and it's easy to change if needed.
 
511
        test_value = b'TEST-VALUE'
 
512
        for oldvar, newvar in self._conf_mapping.iteritems():
 
513
            self._mlist.mail_host = 'example.com'
 
514
            self._pckdict[b'mail_host'] = b'test.example.com'
 
515
            self._pckdict[str(oldvar)] = test_value
 
516
            old_value = getattr(self._mlist, newvar)
 
517
            import_config_pck(self._mlist, self._pckdict)
 
518
            new_value = getattr(self._mlist, newvar)
 
519
            self.assertEqual(old_value, new_value,
 
520
                'Default value was not preserved for %s' % newvar)
 
521
 
 
522
    def test_unicode(self):
 
523
        # non-ascii templates
 
524
        for oldvar in self._conf_mapping:
 
525
            self._pckdict[str(oldvar)] = b'Ol\xe1!'
 
526
        import_config_pck(self._mlist, self._pckdict)
 
527
        for oldvar, newvar in self._conf_mapping.iteritems():
 
528
            newattr = getattr(self._mlist, newvar)
 
529
            text = decorate(self._mlist, newattr)
 
530
            expected = u'Ol\ufffd!'
 
531
            self.assertEqual(text, expected)
 
532
 
 
533
    def test_unicode_in_default(self):
 
534
        # What if the default template is already in UTF-8?   For example, if
 
535
        # you import it twice.
 
536
        footer = b'\xe4\xb8\xad $listinfo_uri'
 
537
        footer_path = os.path.join(
 
538
            config.VAR_DIR, 'templates', 'lists',
 
539
            'blank@example.com', 'en', 'footer-generic.txt')
 
540
        makedirs(os.path.dirname(footer_path))
 
541
        with open(footer_path, 'wb') as fp:
 
542
            fp.write(footer)
 
543
        self._pckdict[b'msg_footer'] = b'NEW-VALUE'
 
544
        import_config_pck(self._mlist, self._pckdict)
 
545
        text = decorate(self._mlist, self._mlist.footer_uri)
 
546
        self.assertEqual(text, 'NEW-VALUE')
 
547
 
 
548
 
 
549
class TestRosterImport(unittest.TestCase):
 
550
    """Test that rosters are imported correctly."""
 
551
 
 
552
    layer = ConfigLayer
 
553
 
 
554
    def setUp(self):
 
555
        self._mlist = create_list('blank@example.com')
 
556
        self._pckdict = {
 
557
            'members': {
 
558
                'anne@example.com': 0,
 
559
                'bob@example.com': b'bob@ExampLe.Com',
 
560
            },
 
561
            'digest_members': {
 
562
                'cindy@example.com': 0,
 
563
                'dave@example.com': b'dave@ExampLe.Com',
 
564
            },
 
565
            'passwords': {
 
566
                'anne@example.com' : b'annepass',
 
567
                'bob@example.com'  : b'bobpass',
 
568
                'cindy@example.com': b'cindypass',
 
569
                'dave@example.com' : b'davepass',
 
570
            },
 
571
            'language': {
 
572
                'anne@example.com' : b'fr',
 
573
                'bob@example.com'  : b'de',
 
574
                'cindy@example.com': b'es',
 
575
                'dave@example.com' : b'it',
 
576
            },
 
577
            'usernames': { # Usernames are unicode strings in the pickle
 
578
                'anne@example.com' : 'Anne',
 
579
                'bob@example.com'  : 'Bob',
 
580
                'cindy@example.com': 'Cindy',
 
581
                'dave@example.com' : 'Dave',
 
582
            },
 
583
            'owner': [
 
584
                'anne@example.com',
 
585
                'emily@example.com',
 
586
            ],
 
587
            'moderator': [
 
588
                'bob@example.com',
 
589
                'fred@example.com',
 
590
            ],
 
591
        }
 
592
        self._usermanager = getUtility(IUserManager)
 
593
        language_manager = getUtility(ILanguageManager)
 
594
        for code in self._pckdict['language'].values():
 
595
            if code not in language_manager.codes:
 
596
                language_manager.add(code, 'utf-8', code)
 
597
 
 
598
    def test_member(self):
 
599
        import_config_pck(self._mlist, self._pckdict)
 
600
        for name in ('anne', 'bob', 'cindy', 'dave'):
 
601
            addr = '%s@example.com' % name
 
602
            self.assertIn(addr,
 
603
                          [a.email for a in self._mlist.members.addresses],
 
604
                          'Address %s was not imported' % addr)
 
605
        self.assertIn('anne@example.com',
 
606
                      [a.email for a in self._mlist.regular_members.addresses])
 
607
        self.assertIn('bob@example.com',
 
608
                      [a.email for a in self._mlist.regular_members.addresses])
 
609
        self.assertIn('cindy@example.com',
 
610
                      [a.email for a in self._mlist.digest_members.addresses])
 
611
        self.assertIn('dave@example.com',
 
612
                      [a.email for a in self._mlist.digest_members.addresses])
 
613
 
 
614
    def test_original_email(self):
 
615
        import_config_pck(self._mlist, self._pckdict)
 
616
        bob = self._usermanager.get_address('bob@example.com')
 
617
        self.assertEqual(bob.original_email, 'bob@ExampLe.Com')
 
618
        dave = self._usermanager.get_address('dave@example.com')
 
619
        self.assertEqual(dave.original_email, 'dave@ExampLe.Com')
 
620
 
 
621
    def test_language(self):
 
622
        import_config_pck(self._mlist, self._pckdict)
 
623
        for name in ('anne', 'bob', 'cindy', 'dave'):
 
624
            addr = '%s@example.com' % name
 
625
            member = self._mlist.members.get_member(addr)
 
626
            self.assertIsNotNone(member, 'Address %s was not imported' % addr)
 
627
            self.assertEqual(member.preferred_language.code,
 
628
                             self._pckdict['language'][addr])
 
629
 
 
630
    def test_new_language(self):
 
631
        self._pckdict[b'language']['anne@example.com'] = b'xx_XX'
 
632
        try:
 
633
            import_config_pck(self._mlist, self._pckdict)
 
634
        except Import21Error as error:
 
635
            self.assertIn('[language.xx_XX]', str(error))
 
636
        else:
 
637
            self.fail('Import21Error was not raised')
 
638
 
 
639
    def test_username(self):
 
640
        import_config_pck(self._mlist, self._pckdict)
 
641
        for name in ('anne', 'bob', 'cindy', 'dave'):
 
642
            addr = '%s@example.com' % name
 
643
            user = self._usermanager.get_user(addr)
 
644
            address = self._usermanager.get_address(addr)
 
645
            self.assertIsNotNone(user, 'User %s was not imported' % addr)
 
646
            self.assertIsNotNone(address, 'Address %s was not imported' % addr)
 
647
            display_name = self._pckdict['usernames'][addr]
 
648
            self.assertEqual(user.display_name, display_name,
 
649
                'The display name was not set for User %s' % addr)
 
650
            self.assertEqual(address.display_name, display_name,
 
651
                'The display name was not set for Address %s' % addr)
 
652
 
 
653
    def test_owner(self):
 
654
        import_config_pck(self._mlist, self._pckdict)
 
655
        for name in ('anne', 'emily'):
 
656
            addr = '%s@example.com' % name
 
657
            self.assertIn(addr,
 
658
                          [a.email for a in self._mlist.owners.addresses],
 
659
                          'Address %s was not imported as owner' % addr)
 
660
        self.assertNotIn(
 
661
            'emily@example.com',
 
662
            [a.email for a in self._mlist.members.addresses],
 
663
            'Address emily@ was wrongly added to the members list')
 
664
 
 
665
    def test_moderator(self):
 
666
        import_config_pck(self._mlist, self._pckdict)
 
667
        for name in ('bob', 'fred'):
 
668
            addr = '%s@example.com' % name
 
669
            self.assertIn(addr,
 
670
                          [a.email for a in self._mlist.moderators.addresses],
 
671
                          'Address %s was not imported as moderator' % addr)
 
672
        self.assertNotIn('fred@example.com',
 
673
                         [a.email for a in self._mlist.members.addresses],
 
674
                         'Address fred@ was wrongly added to the members list')
 
675
 
 
676
    def test_password(self):
 
677
        #self.anne.password = config.password_context.encrypt('abc123')
 
678
        import_config_pck(self._mlist, self._pckdict)
 
679
        for name in ('anne', 'bob', 'cindy', 'dave'):
 
680
            addr = '%s@example.com' % name
 
681
            user = self._usermanager.get_user(addr)
 
682
            self.assertIsNotNone(user, 'Address %s was not imported' % addr)
 
683
            self.assertEqual(
 
684
                user.password, b'{plaintext}%spass' % name,
 
685
                'Password for %s was not imported' % addr)
 
686
 
 
687
    def test_same_user(self):
 
688
        # Adding the address of an existing User must not create another user.
 
689
        user = self._usermanager.create_user('anne@example.com', 'Anne')
 
690
        user.register('bob@example.com') # secondary email
 
691
        import_config_pck(self._mlist, self._pckdict)
 
692
        member = self._mlist.members.get_member('bob@example.com')
 
693
        self.assertEqual(member.user, user)
 
694
 
 
695
    def test_owner_and_moderator_not_lowercase(self):
 
696
        # In the v2.1 pickled dict, the owner and moderator lists are not
 
697
        # necessarily lowercased already.
 
698
        self._pckdict['owner'] = [b'Anne@example.com']
 
699
        self._pckdict['moderator'] = [b'Anne@example.com']
 
700
        import_config_pck(self._mlist, self._pckdict)
 
701
        self.assertIn('anne@example.com',
 
702
                      [a.email for a in self._mlist.owners.addresses])
 
703
        self.assertIn('anne@example.com',
 
704
                      [a.email for a in self._mlist.moderators.addresses])
 
705
 
 
706
    def test_address_already_exists_but_no_user(self):
 
707
        # An address already exists, but it is not linked to a user nor
 
708
        # subscribed.
 
709
        anne_addr = self._usermanager.create_address(
 
710
            'anne@example.com', 'Anne')
 
711
        import_config_pck(self._mlist, self._pckdict)
 
712
        anne = self._usermanager.get_user('anne@example.com')
 
713
        self.assertTrue(anne.controls('anne@example.com'))
 
714
        self.assertIn(anne_addr, self._mlist.regular_members.addresses)
 
715
 
 
716
    def test_address_already_subscribed_but_no_user(self):
 
717
        # An address is already subscribed, but it is not linked to a user.
 
718
        anne_addr = self._usermanager.create_address(
 
719
            'anne@example.com', 'Anne')
 
720
        self._mlist.subscribe(anne_addr)
 
721
        import_config_pck(self._mlist, self._pckdict)
 
722
        anne = self._usermanager.get_user('anne@example.com')
 
723
        self.assertTrue(anne.controls('anne@example.com'))
 
724
 
 
725
 
 
726
 
 
727
 
 
728
class TestPreferencesImport(unittest.TestCase):
 
729
    """Preferences get imported too."""
 
730
 
 
731
    layer = ConfigLayer
 
732
 
 
733
    def setUp(self):
 
734
        self._mlist = create_list('blank@example.com')
 
735
        self._pckdict = dict(
 
736
            members={'anne@example.com': 0},
 
737
            user_options=dict(),
 
738
            delivery_status=dict(),
 
739
            )
 
740
        self._usermanager = getUtility(IUserManager)
 
741
 
 
742
    def _do_test(self, oldvalue, expected):
 
743
        self._pckdict['user_options']['anne@example.com'] = oldvalue
 
744
        import_config_pck(self._mlist, self._pckdict)
 
745
        user = self._usermanager.get_user('anne@example.com')
 
746
        self.assertIsNotNone(user, 'User was not imported')
 
747
        member = self._mlist.members.get_member('anne@example.com')
 
748
        self.assertIsNotNone(member, 'Address was not subscribed')
 
749
        for exp_name, exp_val in expected.iteritems():
 
750
            try:
 
751
                currentval = getattr(member, exp_name)
 
752
            except AttributeError:
 
753
                # hide_address has no direct getter
 
754
                currentval = getattr(member.preferences, exp_name)
 
755
            self.assertEqual(
 
756
                currentval, exp_val,
 
757
                'Preference %s was not imported' % exp_name)
 
758
        # XXX: should I check that other params are still equal to
 
759
        # mailman.core.constants.system_preferences?
 
760
 
 
761
    def test_acknowledge_posts(self):
 
762
        # AcknowledgePosts
 
763
        self._do_test(4, dict(acknowledge_posts=True))
 
764
 
 
765
    def test_hide_address(self):
 
766
        # ConcealSubscription
 
767
        self._do_test(16, dict(hide_address=True))
 
768
 
 
769
    def test_receive_own_postings(self):
 
770
        # DontReceiveOwnPosts
 
771
        self._do_test(2, dict(receive_own_postings=False))
 
772
 
 
773
    def test_receive_list_copy(self):
 
774
        # DontReceiveDuplicates
 
775
        self._do_test(256, dict(receive_list_copy=False))
 
776
 
 
777
    def test_digest_plain(self):
 
778
        # Digests & DisableMime
 
779
        self._pckdict['digest_members'] = self._pckdict['members'].copy()
 
780
        self._pckdict['members'] = dict()
 
781
        self._do_test(8, dict(delivery_mode=DeliveryMode.plaintext_digests))
 
782
 
 
783
    def test_digest_mime(self):
 
784
        # Digests & not DisableMime
 
785
        self._pckdict['digest_members'] = self._pckdict['members'].copy()
 
786
        self._pckdict['members'] = dict()
 
787
        self._do_test(0, dict(delivery_mode=DeliveryMode.mime_digests))
 
788
 
 
789
    def test_delivery_status(self):
 
790
        # Look for the pckdict['delivery_status'] key which will look like
 
791
        # (status, time) where status is among the following:
 
792
        # ENABLED  = 0 # enabled
 
793
        # UNKNOWN  = 1 # legacy disabled
 
794
        # BYUSER   = 2 # disabled by user choice
 
795
        # BYADMIN  = 3 # disabled by admin choice
 
796
        # BYBOUNCE = 4 # disabled by bounces
 
797
        for oldval, expected in enumerate((
 
798
                DeliveryStatus.enabled,
 
799
                DeliveryStatus.unknown, DeliveryStatus.by_user,
 
800
                DeliveryStatus.by_moderator, DeliveryStatus.by_bounces)):
 
801
            self._pckdict['delivery_status']['anne@example.com'] = (oldval, 0)
 
802
            import_config_pck(self._mlist, self._pckdict)
 
803
            member = self._mlist.members.get_member('anne@example.com')
 
804
            self.assertIsNotNone(member, 'Address was not subscribed')
 
805
            self.assertEqual(member.delivery_status, expected)
 
806
            member.unsubscribe()
 
807
 
 
808
    def test_moderate(self):
 
809
        # Option flag Moderate is translated to
 
810
        # member.moderation_action = Action.hold
 
811
        self._do_test(128, dict(moderation_action=Action.hold))
 
812
 
 
813
    def test_multiple_options(self):
 
814
        # DontReceiveDuplicates & DisableMime & SuppressPasswordReminder
 
815
        self._pckdict[b'digest_members'] = self._pckdict[b'members'].copy()
 
816
        self._pckdict[b'members'] = dict()
 
817
        self._do_test(296, dict(
 
818
                receive_list_copy=False,
 
819
                delivery_mode=DeliveryMode.plaintext_digests,
 
820
                ))