diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c72623..57c78ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,8 @@ and this project adheres to ## [Unreleased] +### Added + +- ✨(tools) extract domain from email address #2 + [unreleased]: https://github.com/suitenumerique/django-lasuite/commits/main/ diff --git a/src/lasuite/tools/__init__.py b/src/lasuite/tools/__init__.py new file mode 100644 index 0000000..03ef40e --- /dev/null +++ b/src/lasuite/tools/__init__.py @@ -0,0 +1 @@ +"""Common tools for the LaSuite package or the projects.""" diff --git a/src/lasuite/tools/email.py b/src/lasuite/tools/email.py new file mode 100644 index 0000000..71157d7 --- /dev/null +++ b/src/lasuite/tools/email.py @@ -0,0 +1,19 @@ +"""Email related tools.""" + +from email.errors import HeaderParseError +from email.headerregistry import Address + + +def get_domain_from_email(email: str | None) -> str | None: + """Extract domain from email.""" + try: + address = Address(addr_spec=email) + if len(address.username) > 64 or len(address.domain) > 255: # noqa: PLR2004 + # Simple length validation using the RFC 5321 limits + return None + if not address.domain: + # If the domain is empty, return None + return None + return address.domain + except (ValueError, AttributeError, IndexError, HeaderParseError): + return None diff --git a/tests/tools/__init__.py b/tests/tools/__init__.py new file mode 100644 index 0000000..f421f6a --- /dev/null +++ b/tests/tools/__init__.py @@ -0,0 +1 @@ +"""Tools tests.""" diff --git a/tests/tools/test_email.py b/tests/tools/test_email.py new file mode 100644 index 0000000..038882b --- /dev/null +++ b/tests/tools/test_email.py @@ -0,0 +1,55 @@ +"""Tests for email tools.""" + +import pytest + +from lasuite.tools.email import get_domain_from_email + + +@pytest.mark.parametrize( + ("email", "expected"), + [ + ("user@example.com", "example.com"), + ("test.user@sub.domain.co.uk", "sub.domain.co.uk"), + ("name+tag@gmail.com", "gmail.com"), + ("user@localhost", "localhost"), + ("user@127.0.0.1", "127.0.0.1"), + ], +) +def test_get_domain_from_email_valid(email, expected): + """Test extracting domain from valid email addresses.""" + assert get_domain_from_email(email) == expected + + +@pytest.mark.parametrize( + "invalid_email", + [ + None, + "", + "invalid-email", + "user@", + "@domain.com", + "user@domain@com", + "user@@domain.com", + "user domain.com", + "@example.com", + "user@example.com\n", + "user@example.com;drop table users", + ], +) +def test_get_domain_from_email_invalid(invalid_email): + """Test handling of invalid email addresses.""" + assert get_domain_from_email(invalid_email) is None + + +def test_get_domain_from_email_length_limits(): + """Test handling of extremely long email addresses.""" + # Very long local part (should fail) + long_local = "a" * 65 + "@example.com" + assert get_domain_from_email(long_local) is None + + # Very long domain (valid according to standards) + domain_parts = ".".join(["a" * 64] * 4) + long_domain = f"user@{domain_parts}" + # This might fail depending on email validation implementation + # The test expects None since such domains aren't typically valid in practice + assert get_domain_from_email(long_domain) is None