diff --git a/fetchmail_attach_from_folder/match_algorithm/email_exact.py b/fetchmail_attach_from_folder/match_algorithm/email_exact.py index 899289407da..da941fe664e 100644 --- a/fetchmail_attach_from_folder/match_algorithm/email_exact.py +++ b/fetchmail_attach_from_folder/match_algorithm/email_exact.py @@ -1,11 +1,11 @@ # Copyright - 2013-2024 Therp BV . # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -from odoo.tools.mail import email_split +from odoo.tools.mail import email_normalize, email_split from odoo.tools.safe_eval import safe_eval class EmailExact: - """Search for exactly the mailadress as noted in the email""" + """Search for exactly the email address as noted in the email.""" def _get_mailaddresses(self, folder, message_dict): mailaddresses = [] @@ -13,11 +13,24 @@ def _get_mailaddresses(self, folder, message_dict): for field in fields: if field in message_dict: mailaddresses += email_split(message_dict[field]) - return [addr.lower() for addr in mailaddresses] + # Normalize using email_normalize for consistent matching. + # This strips display names, lowercases the address, and handles + # edge cases (e.g. "" or "User "). + return [email_normalize(addr) or addr.lower() for addr in mailaddresses] def _get_mailaddress_search_domain( - self, folder, message_dict, operator="=", values=None + self, folder, message_dict, operator="=ilike", values=None ): + """Build search domain for email matching. + + We use ``=ilike`` (case-insensitive exact match) instead of ``=`` + so that uppercase email variants (e.g. ``Name.SURNAME@Domain.com``) + also match partners whose email is stored in mixed case. + + ``=ilike`` is safe here because there are no ``%`` wildcards in the + search values, so it behaves exactly like a case-insensitive ``=`` + (PostgreSQL: ``LOWER(field) = LOWER(value)``). + """ mailaddresses = values or self._get_mailaddresses(folder, message_dict) if not mailaddresses: return [(0, "=", 1)] diff --git a/fetchmail_attach_from_folder/tests/test_match_algorithms.py b/fetchmail_attach_from_folder/tests/test_match_algorithms.py index 02969a4f4fd..92885dbdd4b 100644 --- a/fetchmail_attach_from_folder/tests/test_match_algorithms.py +++ b/fetchmail_attach_from_folder/tests/test_match_algorithms.py @@ -8,7 +8,7 @@ from odoo.fields import Command from odoo.tests.common import TransactionCase -from ..match_algorithm import email_domain +from ..match_algorithm import email_domain, email_exact TEST_EMAIL = "reynaert@dutchsagas.nl" TEST_SUBJECT = "Test subject" @@ -144,6 +144,34 @@ def setUpClass(cls): } ) + def test_email_exact_case_insensitive(self): + """A message to REYN@dom.ain should match partner with reyn@dom.ain.""" + MAIL_MESSAGE["from"] = TEST_EMAIL.upper() + self.folder.match_algorithm = "email_exact" + matcher = email_exact.EmailExact() + matches = matcher.search_matches(self.folder, MAIL_MESSAGE) + self.assertEqual(matches, self.test_partner) + + def test_email_exact_case_insensitive_partner_uppercase(self): + """A message to reyn@dom.ain should match partner with REYN@dom.ain.""" + self.test_partner.email = TEST_EMAIL.upper() + MAIL_MESSAGE["from"] = TEST_EMAIL + self.folder.match_algorithm = "email_exact" + matcher = email_exact.EmailExact() + matches = matcher.search_matches(self.folder, MAIL_MESSAGE) + self.assertEqual(matches, self.test_partner) + # restore original partner email for subsequent tests + self.test_partner.email = TEST_EMAIL + + def test_email_exact_display_name(self): + """A message in the form 'Name ' should be parsed correctly.""" + TEST_DISPLAY_NAME = "Reynaert de Vos " + MAIL_MESSAGE["from"] = TEST_DISPLAY_NAME + self.folder.match_algorithm = "email_exact" + matcher = email_exact.EmailExact() + matches = matcher.search_matches(self.folder, MAIL_MESSAGE) + self.assertEqual(matches, self.test_partner) + def test_email_exact(self): """A message to ronald@acme.com should be linked to partner with that email.""" MAIL_MESSAGE["from"] = TEST_EMAIL