def test_smtp_utf8(self): controller = Controller(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: code, response = client.ehlo('example.com') self.assertEqual(code, 250) self.assertIn(b'SMTPUTF8', response.splitlines())
def test_unexpected_errors_unhandled(self): handler = Sink() handler.error = None controller = ErrorController(handler) controller.start() self.addCleanup(controller.stop) with ExitStack() as resources: # Suppress logging to the console during the tests. Depending on # timing, the exception may or may not be logged. resources.enter_context(patch('aiosmtpd.smtp.log.exception')) client = resources.enter_context( SMTP(controller.hostname, controller.port)) code, response = client.helo('example.com') self.assertEqual(code, 500) self.assertEqual(response, b'Error: (ValueError) test') # handler.error did not change because the handler does not have a # handle_exception() method. self.assertIsNone(handler.error)
def test_normal_situation(self): cont = Controller(Sink()) try: cont.start() catchup_delay() assert cont.smtpd is not None assert cont._thread_exception is None finally: cont.stop()
def test_mail_with_unrequited_smtputf8(self): controller = Controller(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.ehlo('example.com') code, response = client.docmd('MAIL FROM: <*****@*****.**>') self.assertEqual(code, 250) self.assertEqual(response, b'OK')
def test_server_attribute(self): controller = UTF8Controller(Sink()) self.assertIsNone(controller.server) try: controller.start() self.assertIsNotNone(controller.server) finally: controller.stop() self.assertIsNone(controller.server)
def test_nocertreq_chkhost_warn(self, caplog, ssl_context_server): context = ssl_context_server context.verify_mode = ssl.CERT_OPTIONAL context.check_hostname = True _ = Server(Sink(), tls_context=context) assert context.verify_mode == ssl.CERT_OPTIONAL logmsg = caplog.record_tuples[0][-1] assert "tls_context.check_hostname == True" in logmsg assert "might cause client connection problems" in logmsg
async def no_auth(request): class PseudoController(Controller): def factory(self): return AuthSMTP(self.handler) controller = PseudoController(Sink(), hostname="127.0.0.1", port="8025") request.addfinalizer(controller.stop) controller.start() return controller
def test_testconn_raises(self, mocker: MockFixture): mocker.patch("socket.socket.recv", side_effect=RuntimeError("MockError")) cont = Controller(Sink(), hostname="") try: with pytest.raises(RuntimeError, match="MockError"): cont.start() finally: cont.stop()
def test_socket_error_dupe(self, plain_controller, client): contr2 = Controller(Sink(), hostname=Global.SrvAddr.host, port=Global.SrvAddr.port) try: with pytest.raises(socket.error): contr2.start() finally: contr2.stop()
def test_server_attribute(self): controller = Controller(Sink()) assert controller.server is None try: controller.start() assert controller.server is not None finally: controller.stop() assert controller.server is None
def test_custom_greeting(self): controller = CustomIdentController(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP() as client: code, msg = client.connect(controller.hostname, controller.port) self.assertEqual(code, 220) # The hostname prefix is unpredictable. self.assertEqual(msg[-22:], b'Identifying SMTP v2112')
def test_help_after_starttls(self): controller = TLSController(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: # Don't get tricked by smtplib processing of the response. code, response = client.docmd('HELP') self.assertEqual(code, 250) self.assertEqual(response, SUPPORTED_COMMANDS_TLS)
def test_esmtp_no_size_limit(self): controller = SizedController(Sink(), size=None) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: code, response = client.ehlo('example.com') self.assertEqual(code, 250) for line in response.splitlines(): self.assertNotEqual(line[:4], b'SIZE')
def test_default_greeting(self): controller = Controller(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP() as client: code, msg = client.connect(controller.hostname, controller.port) self.assertEqual(code, 220) # The hostname prefix is unpredictable. self.assertEqual(msg[-len(GREETING):], bytes(GREETING, 'utf-8'))
def test_mail_with_incompatible_smtputf8(self): controller = Controller(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.ehlo('example.com') code, response = client.docmd( 'MAIL FROM: <*****@*****.**> SMTPUTF8=YES') self.assertEqual(code, 501) self.assertEqual(response, b'Error: SMTPUTF8 takes no arguments')
def test_ready_timeout(self): cont = SlowStartController(Sink()) expectre = ("SMTP server failed to start within allotted time. " "This might happen if the system is too busy. " "Try increasing the `ready_timeout` parameter.") try: with pytest.raises(TimeoutError, match=expectre): cont.start() finally: cont.stop()
def test_socket_error_dupe(self, plain_controller, client): contr2 = Controller(Sink(), hostname=Global.SrvAddr.host, port=Global.SrvAddr.port) expectedre = r"error while attempting to bind on address" try: with pytest.raises(socket.error, match=expectedre): contr2.start() finally: contr2.stop()
def test_factory_timeout(self): cont = SlowFactoryController(Sink()) expectre = ( r"SMTP server started, but not responding within allotted time. " r"This might happen if the system is too busy. " r"Try increasing the `ready_timeout` parameter.") try: with pytest.raises(TimeoutError, match=expectre): cont.start() finally: cont.stop()
def test_unknown_args_direct(self, silence_event_loop_closed): unknown = "this_is_an_unknown_kwarg" cont = Controller(Sink(), ready_timeout=0.3, **{unknown: True}) expectedre = r"__init__.. got an unexpected keyword argument '" + unknown + r"'" try: with pytest.raises(TypeError, match=expectedre): cont.start() assert cont.smtpd is None assert isinstance(cont._thread_exception, TypeError) finally: cont.stop()
def test_rset_hook_deprecation(self): controller = DeprecatedHookController(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.rset() self.assertEqual(len(controller.smtpd.warnings), 1) self.assertEqual( controller.smtpd.warnings[0], call('Use handler.handle_RSET() instead of .rset_hook()', DeprecationWarning))
def test_mail_invalid_body(self): controller = Controller(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.ehlo('example.com') code, response = client.docmd( 'MAIL FROM: <*****@*****.**> BODY 9BIT') self.assertEqual(code, 501) self.assertEqual(response, b'Error: BODY can only be one of 7BIT, 8BITMIME')
def test_help_after_starttls(self): controller = TLSController(Sink()) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: # Don't get tricked by smtplib processing of the response. code, response = client.docmd('HELP') self.assertEqual(code, 250) self.assertEqual( response, b'Supported commands: DATA EHLO HELO HELP MAIL ' b'NOOP QUIT RCPT RSET STARTTLS VRFY')
def test_serverhostname_arg(self): contsink = partial(Controller, Sink()) controller = contsink() assert "hostname" not in controller.SMTP_kwargs controller = contsink(server_hostname="testhost1") assert controller.SMTP_kwargs["hostname"] == "testhost1" kwargs = dict(hostname="testhost2") controller = contsink(server_kwargs=kwargs) assert controller.SMTP_kwargs["hostname"] == "testhost2" controller = contsink(server_hostname="testhost3", server_kwargs=kwargs) assert controller.SMTP_kwargs["hostname"] == "testhost3"
def test_mail_with_size_too_large(self): controller = SizedController(Sink(), 9999) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.ehlo('example.com') code, response = client.docmd( 'MAIL FROM: <*****@*****.**> SIZE=10000') self.assertEqual(code, 552) self.assertEqual( response, b'Error: message size exceeds fixed maximum message size')
def test_server_creation_ssl(self, safe_socket_dir, ssl_context_server): self.sockfile = safe_socket_dir / "smtp" cont = UnixSocketController(Sink(), unix_socket=self.sockfile, ssl_context=ssl_context_server) try: cont.start() # Allow additional time for SSL to kick in time.sleep(0.1) self._assert_good_server(ssl_context_server) finally: cont.stop()
def test_unknown_args_inkwargs(self, silence_event_loop_closed: bool): unknown = "this_is_an_unknown_kwarg" cont = Controller(Sink(), ready_timeout=0.3, server_kwargs={unknown: True}) expectedre = r"__init__.. got an unexpected keyword argument '" + unknown + r"'" try: with pytest.raises(TypeError, match=expectedre): cont.start() assert cont.smtpd is None finally: cont.stop()
def test_server_creation_ssl(self, safe_socket_dir, ssl_context_server): sockfile = safe_socket_dir / "smtp" cont = UnixSocketController(Sink(), unix_socket=sockfile, ssl_context=ssl_context_server) try: cont.start() # Allow additional time for SSL to kick in catchup_delay() assert_smtp_socket(cont) finally: cont.stop()
def test_too_long_message_body(self): controller = SizedController(Sink(), size=100) controller.start() self.addCleanup(controller.stop) with SMTP(controller.hostname, controller.port) as client: client.helo('example.com') mail = '\r\n'.join(['z' * 20] * 10) with self.assertRaises(SMTPResponseException) as cm: client.sendmail('*****@*****.**', ['*****@*****.**'], mail) self.assertEqual(cm.exception.smtp_code, 552) self.assertEqual(cm.exception.smtp_error, b'Error: Too much mail data')
def test_factory_none(self, mocker: MockFixture, silence_event_loop_closed): # Hypothetical situation where factory() did not raise an Exception # but returned None instead mocker.patch("aiosmtpd.controller.SMTP", return_value=None) cont = Controller(Sink(), ready_timeout=0.3) expectedre = r"factory\(\) returned None" try: with pytest.raises(RuntimeError, match=expectedre): cont.start() assert cont.smtpd is None finally: cont.stop()
def test_inet_contstop(self, temp_event_loop, runner): """ Verify behavior when the controller is stopped before loop is stopped """ cont = UnthreadedController(Sink(), loop=temp_event_loop) cont.begin() # Make sure event loop is not running (will be started in thread) assert temp_event_loop.is_running() is False runner(temp_event_loop) # Make sure event loop is up and running assert temp_event_loop.is_running() is True try: # Check that we can connect with SMTPClient(cont.hostname, cont.port, timeout=AUTOSTOP_DELAY) as client: code, _ = client.helo("example.org") assert code == 250 client.quit() catchup_delay() temp_event_loop.call_soon_threadsafe(cont.end) for _ in range(10): # 10 is arbitrary catchup_delay( ) # effectively yield to other threads/event loop if cont.ended.wait(1.0): break assert temp_event_loop.is_running() is True # Because we've called .end() there, the server listener should've gone # away, so we should end up with a socket.timeout or ConnectionError or # SMTPServerDisconnected (depending on lotsa factors) expect_errs = (socket.timeout, ConnectionError, SMTPServerDisconnected) # noinspection PyTypeChecker with pytest.raises(expect_errs): SMTPClient(cont.hostname, cont.port, timeout=0.1) finally: # Wrap up, or else we'll hang temp_event_loop.call_soon_threadsafe(cont.cancel_tasks) catchup_delay() runner.join() assert runner.is_alive() is False assert temp_event_loop.is_running() is False assert temp_event_loop.is_closed() is False
def test_sink_cli_no_args(self): handler = Sink.from_cli(self.parser) self.assertIsNone(self.parser.message) self.assertIsInstance(handler, Sink)