Aurélien Bompard has proposed merging lp:~abompard/mailman.client/mockingclient
into lp:mailman.client.
Requested reviews:
Mailman Coders (mailman-coders)
For more details, see:
https://code.launchpad.net/~abompard/mailman.client/mockingclient/+merge/242185
This branch adds a mocking client which only instantiates the REST API server
in memory, not by forking a specific mailman server.
Then, the requests are made through the WSGI API using WebTest.
--
Your team Mailman Coders is requested to review the proposed merge of
lp:~abompard/mailman.client/mockingclient into lp:mailman.client.
=== modified file 'setup.py'
--- setup.py 2014-04-22 14:38:40 +0000
+++ setup.py 2014-11-19 11:04:25 +0000
@@ -44,5 +44,5 @@
# Auto-conversion to Python 3.
use_2to3=True,
convert_2to3_doctests=find_doctests(),
- install_requires=['httplib2', 'mock', ],
+ install_requires=['httplib2', 'mock', 'WebTest', ],
)
=== modified file 'src/mailmanclient/docs/using.txt'
--- src/mailmanclient/docs/using.txt 2014-04-22 12:47:00 +0000
+++ src/mailmanclient/docs/using.txt 2014-11-19 11:04:25 +0000
@@ -640,12 +640,7 @@
... Some text.
...
... """
- >>> server = smtplib.LMTP('localhost', 8024)
- >>> server.sendmail('[email protected]', '[email protected]', msg)
- {}
- >>> server.quit()
- (221, 'Bye')
- >>> time.sleep(2)
+ >>> inject_message('[email protected]', msg)
Messages held for moderation can be listed on a per list basis.
=== modified file 'src/mailmanclient/tests/test_docs.py'
--- src/mailmanclient/tests/test_docs.py 2014-03-15 20:43:52 +0000
+++ src/mailmanclient/tests/test_docs.py 2014-11-19 11:04:25 +0000
@@ -17,7 +17,7 @@
"""Test harness for doctests."""
-from __future__ import absolute_import, unicode_literals
+from __future__ import absolute_import, unicode_literals, print_function
__metaclass__ = type
__all__ = [
@@ -33,11 +33,16 @@
import tempfile
import unittest
import subprocess
+from textwrap import dedent
+from mock import patch
# pylint: disable-msg=F0401
from pkg_resources import (
resource_filename, resource_exists, resource_listdir, cleanup_resources)
+from mailman.config import config
+from mailmanclient.tests.utils import FakeMailmanClient, inject_message
+
COMMASPACE = ', '
DOT = '.'
@@ -50,17 +55,17 @@
def dump(results):
if results is None:
- print None
+ print(None)
return
for key in sorted(results):
if key == 'entries':
for i, entry in enumerate(results[key]):
# entry is a dictionary.
- print 'entry %d:' % i
+ print('entry %d:' % i)
for entry_key in sorted(entry):
- print ' {0}: {1}'.format(entry_key, entry[entry_key])
+ print(' {0}: {1}'.format(entry_key, entry[entry_key]))
else:
- print '{0}: {1}'.format(key, results[key])
+ print('{0}: {1}'.format(key, results[key]))
@@ -74,69 +79,33 @@
def setup(testobj):
- """Test setup."""
- # Create a unique database for the running version of Mailman, then start
- # it up. It should not yet be running. This environment variable must be
- # set to find the installation of Mailman we can run. Yes, this should be
- # fixed.
- testobj._bindir = os.environ.get('MAILMAN_TEST_BINDIR')
- if testobj._bindir is None:
- raise RuntimeError('Must set $MAILMAN_TEST_BINDIR to run tests')
- vardir = testobj._vardir = tempfile.mkdtemp()
- cfgfile = testobj._cfgfile = os.path.join(vardir, 'client_test.cfg')
- with open(cfgfile, 'w') as fp:
- print >> fp, """\
-[mailman]
-layout: tmpdir
-[paths.tmpdir]
-var_dir: {vardir}
-log_dir: /tmp/mmclient/logs
-[webservice]
-port: 9001
-[runner.archive]
-start: no
-[runner.bounces]
-start: no
-[runner.command]
-start: yes
-[runner.in]
-start: yes
-[runner.lmtp]
-start: yes
-[runner.news]
-start: no
-[runner.out]
-start: yes
-[runner.pipeline]
-start: no
-[runner.retry]
-start: no
-[runner.virgin]
-start: yes
-[runner.digest]
-start: no
-""".format(vardir=vardir)
- mailman = os.path.join(testobj._bindir, 'mailman')
- subprocess.call([mailman, '-C', cfgfile, 'start', '-q'])
- time.sleep(3)
- # Make sure future statements in our doctests match the Python code. When
- # run with 2to3, the future import gets removed and these names are not
- # defined.
- try:
- testobj.globs['absolute_import'] = absolute_import
- testobj.globs['unicode_literals'] = unicode_literals
- except NameError:
- pass
testobj.globs['stop'] = stop
testobj.globs['dump'] = dump
-
-
+ testobj.globs['inject_message'] = inject_message
+ FakeMailmanClient.setUp()
+ # In unit tests, passwords aren't encrypted. Don't show this in the doctests
+ passlib_cfg = os.path.join(config.VAR_DIR, 'passlib.cfg')
+ with open(passlib_cfg, 'w') as fp:
+ print(dedent("""
+ [passlib]
+ schemes = sha512_crypt
+ """), file=fp)
+ conf_hash_pw = dedent("""
+ [passwords]
+ configuration: {}
+ """.format(passlib_cfg))
+ config.push('conf_hash_pw', conf_hash_pw)
+ # Use the FakeMailmanClient
+ testobj.patcher = patch("mailmanclient.Client", FakeMailmanClient)
+ fmc = testobj.patcher.start()
+
+
+
def teardown(testobj):
"""Test teardown."""
- mailman = os.path.join(testobj._bindir, 'mailman')
- subprocess.call([mailman, '-C', testobj._cfgfile, 'stop', '-q'])
- shutil.rmtree(testobj._vardir)
- time.sleep(3)
+ testobj.patcher.stop()
+ config.pop('conf_hash_pw')
+ FakeMailmanClient.tearDown()
=== added file 'src/mailmanclient/tests/utils.py'
--- src/mailmanclient/tests/utils.py 1970-01-01 00:00:00 +0000
+++ src/mailmanclient/tests/utils.py 2014-11-19 11:04:25 +0000
@@ -0,0 +1,129 @@
+# Copyright (C) 2010-2014 by The Free Software Foundation, Inc.
+#
+# This file is part of mailman.client.
+#
+# mailman.client is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# mailman.client 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 Lesser General Public License
+# for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with mailman.client. If not, see <http://www.gnu.org/licenses/>.
+
+"""Test tools."""
+
+from __future__ import absolute_import, unicode_literals, print_function
+
+import os
+from urllib2 import HTTPError
+from urlparse import urljoin
+
+from zope.component import getUtility
+from webtest import TestApp
+
+from mailman.config import config
+from mailman.core.chains import process
+from mailman.interfaces.listmanager import IListManager
+from mailman.interfaces.usermanager import IUserManager
+from mailman.testing.helpers import specialized_message_from_string
+from mailman.testing.layers import ConfigLayer
+import mailmanclient
+
+__metaclass__ = type
+__all__ = [
+ "inject_message",
+ "FakeMailmanClient",
+ ]
+
+
+
+def inject_message(fqdn_listname, msg):
+ mlist = getUtility(IListManager).get(fqdn_listname)
+ user_manager = getUtility(IUserManager)
+ msg = specialized_message_from_string(msg)
+ for sender in msg.senders:
+ if user_manager.get_address(sender) is None:
+ user_manager.create_address(sender)
+ process(mlist, msg, {})
+
+
+#
+# Mocking mailman client
+#
+
+
+class FakeConnection(mailmanclient._client._Connection):
+ """
+ Looks for information inside a dict instead of making HTTP requests.
+ Also, logs the called URLs as called_paths.
+ Very incomplete at the moment.
+ """
+
+ def __init__(self, baseurl, name=None, password=None):
+ super(FakeConnection, self).__init__(baseurl, name, password)
+ self.called_paths = []
+ from mailman.rest.wsgiapp import make_application
+ self.app = TestApp(make_application())
+ if self.basic_auth:
+ self.app.authorization = ('Basic', (self.name, self.password))
+ super(FakeConnection, self).__init__(baseurl, name, password)
+
+ def call(self, path, data=None, method=None):
+ self.called_paths.append(
+ { "path": path, "data": data, "method": method })
+ if method is None:
+ if data is None:
+ method = 'GET'
+ else:
+ method = 'POST'
+ method_fn = getattr(self.app, method.lower())
+ url = urljoin(self.baseurl, path)
+ try:
+ kw = {"expect_errors": True}
+ if data:
+ kw["params"] = data
+ response = method_fn(url, **kw)
+ headers = response.headers
+ headers["status"] = response.status_int
+ content = unicode(response.body)
+ # If we did not get a 2xx status code, make this look like a
+ # urllib2 exception, for backward compatibility.
+ if response.status_int // 100 != 2:
+ raise HTTPError(url, response.status_int, content, headers, None)
+ if len(content) == 0:
+ return headers, None
+ # XXX Work around for http://bugs.python.org/issue10038
+ return headers, response.json
+ except HTTPError:
+ raise
+ except IOError:
+ raise MailmanConnectionError('Could not connect to Mailman API')
+
+
+
+class FakeMailmanClient(mailmanclient.Client):
+ """
+ Subclass of mailmanclient.Client to instantiate a FakeConnection object
+ instead of the real connection.
+ """
+
+ def __init__(self, baseurl, name=None, password=None):
+ self._connection = FakeConnection(baseurl, name, password)
+
+ @property
+ def called_paths(self):
+ return self._connection.called_paths
+
+ @classmethod
+ def setUp(self):
+ ConfigLayer.setUp()
+
+ @classmethod
+ def tearDown(self):
+ config.create_paths = False # or ConfigLayer.tearDown will create them
+ ConfigLayer.tearDown()
_______________________________________________
Mailman-coders mailing list
[email protected]
https://mail.python.org/mailman/listinfo/mailman-coders