diff options
| author | Barry Warsaw | 2011-04-01 18:51:29 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-04-01 18:51:29 -0400 |
| commit | 33ad44bc97f08df71f227f6f2a006e770a75c353 (patch) | |
| tree | ef369d5dc968372d824cbe50421a1f5331461149 /src | |
| parent | dee26f391da59c68a23f8fb960dff9ebd879e916 (diff) | |
| download | mailman-33ad44bc97f08df71f227f6f2a006e770a75c353.tar.gz mailman-33ad44bc97f08df71f227f6f2a006e770a75c353.tar.zst mailman-33ad44bc97f08df71f227f6f2a006e770a75c353.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/database/mailman.sql | 1 | ||||
| -rw-r--r-- | src/mailman/interfaces/user.py | 3 | ||||
| -rw-r--r-- | src/mailman/model/docs/users.txt | 15 | ||||
| -rw-r--r-- | src/mailman/model/user.py | 18 | ||||
| -rw-r--r-- | src/mailman/model/usermanager.py | 15 | ||||
| -rw-r--r-- | src/mailman/testing/__init__.py | 39 | ||||
| -rw-r--r-- | src/mailman/testing/layers.py | 18 | ||||
| -rw-r--r-- | src/mailman/tests/test_documentation.py | 4 | ||||
| -rw-r--r-- | src/mailman/utilities/datetime.py | 11 | ||||
| -rw-r--r-- | src/mailman/utilities/uid.py | 67 |
10 files changed, 159 insertions, 32 deletions
diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql index 8d5a424a3..7d67dea05 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/mailman.sql @@ -253,6 +253,7 @@ CREATE TABLE user ( real_name TEXT, password TEXT, _user_id TEXT, + _created_on TIMESTAMP, preferences_id INTEGER, PRIMARY KEY (id), CONSTRAINT user_preferences_id_fk diff --git a/src/mailman/interfaces/user.py b/src/mailman/interfaces/user.py index 18655e4d8..2c2652413 100644 --- a/src/mailman/interfaces/user.py +++ b/src/mailman/interfaces/user.py @@ -41,6 +41,9 @@ class IUser(Interface): user_id = Attribute( """The user's unique, random, identifier (sha1 hex digest).""") + created_on = Attribute( + """The date and time at which this user was created.""") + addresses = Attribute( """An iterator over all the `IAddresses` controlled by this user.""") diff --git a/src/mailman/model/docs/users.txt b/src/mailman/model/docs/users.txt index 1703db1ee..29d8601cd 100644 --- a/src/mailman/model/docs/users.txt +++ b/src/mailman/model/docs/users.txt @@ -36,15 +36,16 @@ The password and real name can be changed at any time. another password -User id -======= +Basic user identification +========================= Although rarely visible to users, every user has a unique ID in Mailman, which never changes. This ID is generated randomly at the time the user is created. - >>> print len(user_1.user_id) - 40 + # The test suite uses a predictable user id. + >>> print user_1.user_id + 1 The user id cannot change. @@ -53,6 +54,12 @@ The user id cannot change. ... AttributeError: can't set attribute +User records also have a date on which they where created. + + # The test suite uses a predictable timestamp. + >>> print user_1.created_on + 2005-08-01 07:49:23 + Users addresses =============== diff --git a/src/mailman/model/user.py b/src/mailman/model/user.py index f037bdd48..05ce356ca 100644 --- a/src/mailman/model/user.py +++ b/src/mailman/model/user.py @@ -24,7 +24,7 @@ __all__ = [ 'User', ] -from storm.locals import Int, Reference, ReferenceSet, Unicode +from storm.locals import DateTime, Int, Reference, ReferenceSet, Unicode from zope.interface import implements from mailman.config import config @@ -35,6 +35,8 @@ from mailman.interfaces.user import IUser from mailman.model.address import Address from mailman.model.preferences import Preferences from mailman.model.roster import Memberships +from mailman.utilities.datetime import factory as date_factory +from mailman.utilities.uid import factory as uid_factory @@ -47,11 +49,20 @@ class User(Model): real_name = Unicode() password = Unicode() _user_id = Unicode() + _created_on = DateTime() addresses = ReferenceSet(id, 'Address.user_id') preferences_id = Int() preferences = Reference(preferences_id, 'Preferences.id') + def __init__(self, real_name=None, preferences=None): + super(User, self).__init__() + self._created_on = date_factory.now() + self._user_id = uid_factory.new_uid() + self.real_name = ('' if real_name is None else real_name) + self.preferences = preferences + config.db.store.add(self) + def __repr__(self): return '<User "{0.real_name}" ({0.user_id}) at {1:#x}>'.format( self, id(self)) @@ -61,6 +72,11 @@ class User(Model): """See `IUser`.""" return self._user_id + @property + def created_on(self): + """See `IUser`.""" + return self._created_on + def link(self, address): """See `IUser`.""" if address.user is not None: diff --git a/src/mailman/model/usermanager.py b/src/mailman/model/usermanager.py index 3294b3e7f..d6817021d 100644 --- a/src/mailman/model/usermanager.py +++ b/src/mailman/model/usermanager.py @@ -25,10 +25,6 @@ __all__ = [ ] -import os -import time -import hashlib - from zope.interface import implements from mailman.config import config @@ -37,7 +33,6 @@ from mailman.interfaces.usermanager import IUserManager from mailman.model.address import Address from mailman.model.preferences import Preferences from mailman.model.user import User -from mailman.utilities.passwords import SALT_LENGTH @@ -45,18 +40,10 @@ class UserManager: implements(IUserManager) def create_user(self, email=None, real_name=None): - user = User() - user.real_name = ('' if real_name is None else real_name) + user = User(real_name, Preferences()) if email: address = self.create_address(email, real_name) user.link(address) - user.preferences = Preferences() - # Generate a unique random SHA1 hash for the user id. - salt = os.urandom(SALT_LENGTH) - h = hashlib.sha1(repr(time.time())) - h.update(salt) - user._user_id = unicode(h.hexdigest(), 'us-ascii') - config.db.store.add(user) return user def delete_user(self, user): diff --git a/src/mailman/testing/__init__.py b/src/mailman/testing/__init__.py index e69de29bb..84182e1f1 100644 --- a/src/mailman/testing/__init__.py +++ b/src/mailman/testing/__init__.py @@ -0,0 +1,39 @@ +# Copyright (C) 2011 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/>. + +"""Set up testing. + +This is used as an interface to buildout.cfg's [test] section. +zope.testrunner supports an initialization variable. It is set to import and +run the following test initialization method. +""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'initialize', + ] + + + +def initialize(root_directory): + """Initialize the test infrastructure.""" + from mailman.testing import layers + layers.MockAndMonkeyLayer.testing_mode = True + layers.ConfigLayer.enable_stderr(); + layers.ConfigLayer.set_root_directory(root_directory) diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index 353dd9edd..2e765ea3e 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -48,7 +48,6 @@ from mailman.core.logging import get_handler from mailman.interfaces.domain import IDomainManager from mailman.testing.helpers import TestableMaster, reset_the_world from mailman.testing.mta import ConnectionCountingController -from mailman.utilities.datetime import factory from mailman.utilities.string import expand @@ -60,17 +59,20 @@ NL = '\n' class MockAndMonkeyLayer: """Layer for mocking and monkey patching for testing.""" - @classmethod - def setUp(cls): - factory.testing_mode = True + # Set this to True to enable predictable datetimes, uids, etc. + testing_mode = False - @classmethod - def tearDown(cls): - factory.testing_mode = False + # A registration of all testing factories, for resetting between tests. + _resets = [] @classmethod def testTearDown(cls): - factory.reset() + for reset in cls._resets: + reset() + + @classmethod + def register_reset(cls, reset): + cls._resets.append(reset) diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py index 23cc189d0..2a72a367f 100644 --- a/src/mailman/tests/test_documentation.py +++ b/src/mailman/tests/test_documentation.py @@ -126,7 +126,7 @@ def dump_list(list_of_things, key=str): def call_http(url, data=None, method=None, username=None, password=None): - """'Call' a URL with a given HTTP method and return the resulting object. + """'Call a URL with a given HTTP method and return the resulting object. The object will have been JSON decoded. @@ -142,6 +142,8 @@ def call_http(url, data=None, method=None, username=None, password=None): :param password: The HTTP Basic Auth password. None means use the value from the configuration. :type username: str + :return: The decoded JSON data structure. + :raises HTTPError: when a non-2xx return code is received. """ headers = {} if data is not None: diff --git a/src/mailman/utilities/datetime.py b/src/mailman/utilities/datetime.py index 7e727346d..9dcd21f1e 100644 --- a/src/mailman/utilities/datetime.py +++ b/src/mailman/utilities/datetime.py @@ -36,25 +36,27 @@ __all__ = [ import datetime +from mailman.testing.layers import MockAndMonkeyLayer + class DateFactory: """A factory for today() and now() that works with testing.""" - # Set to True to produce predictable dates and times. - testing_mode = False # The predictable time. predictable_now = None predictable_today = None def now(self, tz=None): + # We can't automatically fast-forward because some tests require us to + # stay on the same day for a while, e.g. autorespond.txt. return (self.predictable_now - if self.testing_mode + if MockAndMonkeyLayer.testing_mode else datetime.datetime.now(tz)) def today(self): return (self.predictable_today - if self.testing_mode + if MockAndMonkeyLayer.testing_mode else datetime.date.today()) @classmethod @@ -72,3 +74,4 @@ factory = DateFactory() factory.reset() today = factory.today now = factory.now +MockAndMonkeyLayer.register_reset(factory.reset) diff --git a/src/mailman/utilities/uid.py b/src/mailman/utilities/uid.py new file mode 100644 index 000000000..cc2cf3b12 --- /dev/null +++ b/src/mailman/utilities/uid.py @@ -0,0 +1,67 @@ +# Copyright (C) 2011 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/>. + +"""Unique ID generation. + +Use these functions to create unique ids rather than inlining calls to hashlib +and whatnot. These are better instrumented for testing purposes. +""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'UniqueIDFactory', + 'factory', + ] + + +import os +import time +import hashlib + +from mailman.testing.layers import MockAndMonkeyLayer +from mailman.utilities.passwords import SALT_LENGTH + + + +class UniqueIDFactory: + """A factory for unique ids.""" + + # The predictable id. + predictable_id = None + + def new_uid(self, bytes=None): + if MockAndMonkeyLayer.testing_mode: + uid = self.predictable_id + self.predictable_id += 1 + return unicode(uid) + salt = os.urandom(SALT_LENGTH) + h = hashlib.sha1(repr(time.time())) + h.update(salt) + if bytes is not None: + h.update(bytes) + return unicode(h.hexdigest(), 'us-ascii') + + def reset(self): + self.predictable_id = 1 + + + +factory = UniqueIDFactory() +factory.reset() +MockAndMonkeyLayer.register_reset(factory.reset) |
