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
* Re-organize the interface between buildout.cfg and the zope.testing layer
initialization. buildout.cfg is now really simple; it calls one method. That method does all the relevant layer initializations. This better localizes what has to be set up before testing can even begin. * IUsers now have a created_on property which contains the datetime at which the user record was created. * Rework the date and uid factories so that they consult the MockAndMonkeyLayer for the current testing flag. Also, those factories register themselves with the layer so that they'll get automatically reset between tests, without the layer actually having to know about them. * Move the User model object initialization into User.__init__() from the user manager. The User now also adds itself to the store. * Add a 'uid factory' for unique id creation, which is test suite aware.
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)