Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions fetchmail_attach_from_folder/match_algorithm/email_exact.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
# Copyright - 2013-2024 Therp BV <https://therp.nl>.
# 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 = []
fields = folder.mail_field.split(",")
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. "<user@domain.com>" or "User <user@domain.com>").
return [email_normalize(addr) or addr.lower() for addr in mailaddresses]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the or addr.lower() not superfluous here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fsmw Can you answer the question? It is the one thing I would like answered before approve and merge (maybe after removing addr.lower if indeed not needed).


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)]
Expand Down
30 changes: 29 additions & 1 deletion fetchmail_attach_from_folder/tests/test_match_algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 <email@domain>' should be parsed correctly."""
TEST_DISPLAY_NAME = "Reynaert de Vos <reynaert@dutchsagas.nl>"
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
Expand Down
Loading