Source code for bonzo.testing
"""Unit testing support for asynchronous code."""
import socket
from tornado.escape import utf8
from tornado.iostream import IOStream
from tornado.testing import AsyncTestCase, bind_unused_port
from bonzo.server import SMTPServer
[docs]
class AsyncSMTPTestCase(AsyncTestCase):
"""A test case that starts up an SMTP server.
Subclasses must override :meth:`get_request_callback()`, which returns a
:class:`~.server.SMTPServer` callback to be tested.
"""
[docs]
def setUp(self):
super(AsyncSMTPTestCase, self).setUp()
sock, port = bind_unused_port()
self.__port = port
try:
self._request_callback = self.get_request_callback()
self.smtp_server = self.get_smtp_server()
self.smtp_server.add_sockets([sock])
except Exception:
sock.close()
raise
[docs]
def get_smtp_server(self):
"""Returns an instance of :class:`~.server.SMTPServer` that will be
used in the test case. It's inmediatelly called when the
:class:`~.testing.AsyncSMTPTestCase` is instanced.
"""
return SMTPServer(self._request_callback, **self.get_smtpserver_options())
[docs]
def get_request_callback(self):
"""Should be overridden by subclasses to return a
:class:`~.server.SMTPServer` callback.
"""
raise NotImplementedError()
[docs]
def get_smtpserver_options(self):
"""May be overridden by subclasses to return additional keyword
arguments for the server.
"""
return {}
[docs]
def get_smtp_port(self):
"""Returns the port used by the server.
A new port is chosen for each test.
"""
return self.__port
[docs]
def connect(self, read_response=True):
"""Creates a instance of :class:`~tornado.iostream.IOStream` for
reading and writing bytes to the opened socket on the SMTP server
address.
:arg bool read_response: Reads the response of the server immediately
after to establish connection. Useful to read the response and
discard the welcome message.
"""
self.stream = IOStream(socket.socket())
self.io_loop.run_sync(
lambda: self.stream.connect(('localhost', self.get_smtp_port())))
if read_response:
self.read_response()
[docs]
def read_response(self):
"""Reads the response of the stream.
"""
return self.io_loop.run_sync(lambda: self.stream.read_until(b'\r\n'))
[docs]
def send_mail(self, hostname, mail, rcpt, data):
"""Sends a coherent sequence of command to send a message. Returns the
result from the ``DATA`` command.
"""
self.stream.write(utf8('HELO %s\r\n' % hostname))
self.read_response()
self.stream.write(utf8('MAIL FROM:%s\r\n' % mail))
self.read_response()
for address in rcpt:
self.stream.write(utf8('RCPT TO:%s\r\n' % address))
self.read_response()
self.stream.write(b'DATA\r\n')
self.read_response()
self.stream.write(utf8('%s\r\n.\r\n' % data))
return self.read_response()
[docs]
def close(self):
"""Closes the stream.
"""
self.stream.close()
del self.stream
[docs]
def tearDown(self):
self.smtp_server.stop()
super(AsyncSMTPTestCase, self).tearDown()