summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBarry Warsaw2011-04-01 18:51:29 -0400
committerBarry Warsaw2011-04-01 18:51:29 -0400
commit33ad44bc97f08df71f227f6f2a006e770a75c353 (patch)
treeef369d5dc968372d824cbe50421a1f5331461149 /src
parentdee26f391da59c68a23f8fb960dff9ebd879e916 (diff)
downloadmailman-33ad44bc97f08df71f227f6f2a006e770a75c353.tar.gz
mailman-33ad44bc97f08df71f227f6f2a006e770a75c353.tar.zst
mailman-33ad44bc97f08df71f227f6f2a006e770a75c353.zip
Diffstat (limited to 'src')
-rw-r--r--src/mailman/database/mailman.sql1
-rw-r--r--src/mailman/interfaces/user.py3
-rw-r--r--src/mailman/model/docs/users.txt15
-rw-r--r--src/mailman/model/user.py18
-rw-r--r--src/mailman/model/usermanager.py15
-rw-r--r--src/mailman/testing/__init__.py39
-rw-r--r--src/mailman/testing/layers.py18
-rw-r--r--src/mailman/tests/test_documentation.py4
-rw-r--r--src/mailman/utilities/datetime.py11
-rw-r--r--src/mailman/utilities/uid.py67
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)