From 503508fb8e0480cbfdf5a6213ff19ffe281d06ee Mon Sep 17 00:00:00 2001 From: Stephane Debauche Date: Tue, 24 Oct 2023 14:13:08 +0200 Subject: [PATCH] [FIX] base: fix mock of IrMailServer methods Due to current implementation of mocks context is lost when accessing methods 'connect' and '_find_mail_server' of IrMailServer. Indeed self is replaced by an instance of IrMailServer and code relying on context could not be called as planned. This commit aims at doing a custom mock so that we can correctly rely on self and context in those methods. This is necessary for incoming changes in mail notably to allow testing SMTP feature in multi domains environment. Logged information when having failed SMTP check is improved: we now also log found From in order to ease debugging failing tests due to invalid from. Prepares Task-36879 (Mail: Support Multi Domains Aliases) Part-of: odoo/odoo#76734 --- odoo/addons/base/tests/common.py | 39 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/odoo/addons/base/tests/common.py b/odoo/addons/base/tests/common.py index 014a6365bcb..83e972544ee 100644 --- a/odoo/addons/base/tests/common.py +++ b/odoo/addons/base/tests/common.py @@ -4,7 +4,7 @@ import threading from contextlib import contextmanager -from unittest.mock import patch +from unittest.mock import patch, Mock from odoo.tests.common import TransactionCase, HttpCase from odoo import Command @@ -326,16 +326,27 @@ class MockSmtplibCase: self.testing_smtp_session = TestingSMTPSession() IrMailServer = self.env['ir.mail_server'] - connect_origin = IrMailServer.connect - find_mail_server_origin = IrMailServer._find_mail_server + connect_origin = type(IrMailServer).connect + find_mail_server_origin = type(IrMailServer)._find_mail_server + + # custom mock to avoid losing context + def mock_function(func): + mock = Mock() + + def _call(*args, **kwargs): + mock(*args[1:], **kwargs) + return func(*args, **kwargs) + + _call.mock = mock + return _call with patch('smtplib.SMTP_SSL', side_effect=lambda *args, **kwargs: self.testing_smtp_session), \ patch('smtplib.SMTP', side_effect=lambda *args, **kwargs: self.testing_smtp_session), \ patch.object(type(IrMailServer), '_is_test_mode', lambda self: False), \ - patch.object(type(IrMailServer), 'connect', wraps=IrMailServer, side_effect=connect_origin) as connect_mocked, \ - patch.object(type(IrMailServer), '_find_mail_server', side_effect=find_mail_server_origin) as find_mail_server_mocked: - self.connect_mocked = connect_mocked - self.find_mail_server_mocked = find_mail_server_mocked + patch.object(type(IrMailServer), 'connect', mock_function(connect_origin)) as connect_mocked, \ + patch.object(type(IrMailServer), '_find_mail_server', mock_function(find_mail_server_origin)) as find_mail_server_mocked: + self.connect_mocked = connect_mocked.mock + self.find_mail_server_mocked = find_mail_server_mocked.mock yield def _build_email(self, mail_from, return_path=None, **kwargs): @@ -385,15 +396,21 @@ class MockSmtplibCase: debug_info = '' matching_emails_count = len(list(matching_emails)) if matching_emails_count != emails_count: + emails_from = [] + for email in self.emails: + from_found = next(( + line.split('From:')[1].strip() for line in email['message'].splitlines() + if line.startswith('From:')), '') + emails_from.append(from_found) debug_info = '\n'.join( - f"SMTP-From: {email['smtp_from']}, SMTP-To: ({email['smtp_to_list']}) - From_filter {email['from_filter']})" - for email in self.emails + f"SMTP-From: {email['smtp_from']}, SMTP-To: {email['smtp_to_list']}, Msg-From: {email_msg_from}, From_filter: {email['from_filter']})" + for email, email_msg_from in zip(self.emails, emails_from) ) self.assertEqual( matching_emails_count, emails_count, msg=f'Incorrect emails sent: {matching_emails_count} found, {emails_count} expected' - f'\nConditions: SMTP-From {smtp_from}, SMTP-To {smtp_to_list}, Msg-From {message_from}, From_filter {from_filter}' - f'\n{debug_info}' + f'\nConditions\nSMTP-From: {smtp_from}, SMTP-To: {smtp_to_list}, Msg-From: {message_from}, From_filter: {from_filter}' + f'\nNot found in\n{debug_info}' ) @classmethod