From 7c5d340bce24a62fdb96bcf7febe7b831d718cb2 Mon Sep 17 00:00:00 2001 From: Erick Salas Date: Wed, 14 Oct 2026 11:23:08 -0600 Subject: [PATCH 1/4] added edge cases on test suite --- tests/test_email_control_character.py | 9 +++++++++ tests/test_email_ipv6_literal.py | 9 +++++++++ tests/test_email_quoted_at_sign.py | 7 +++++++ tests/test_ipv4_strict_cidr.py | 8 ++++++++ tests/test_ipv6_brackets.py | 10 ++++++++++ 5 files changed, 43 insertions(+) create mode 100644 tests/test_email_control_character.py create mode 100644 tests/test_email_ipv6_literal.py create mode 100644 tests/test_email_quoted_at_sign.py create mode 100644 tests/test_ipv4_strict_cidr.py create mode 100644 tests/test_ipv6_brackets.py diff --git a/tests/test_email_control_character.py b/tests/test_email_control_character.py new file mode 100644 index 0000000..8023bb1 --- /dev/null +++ b/tests/test_email_control_character.py @@ -0,0 +1,9 @@ +"""HU-02: reject SOH in an address intended for SMTP (RFC 5321).""" +import validators + + +def test_email_rejects_soh_control_character(): + # The Python escape introduces the actual ASCII SOH character. + email = '"user\x01name"@example.com' + result = validators.email(email) + assert isinstance(result, validators.ValidationError), repr(result) diff --git a/tests/test_email_ipv6_literal.py b/tests/test_email_ipv6_literal.py new file mode 100644 index 0000000..a2a0391 --- /dev/null +++ b/tests/test_email_ipv6_literal.py @@ -0,0 +1,9 @@ +"""HU-02: accept the IPv6: prefix in an SMTP address literal.""" +import validators + + +def test_email_accepts_ipv6_literal_with_prefix(): + result = validators.email( + "usuario@[IPv6:2001:db8::1]", ipv6_address=True + ) + assert result is True, repr(result) diff --git a/tests/test_email_quoted_at_sign.py b/tests/test_email_quoted_at_sign.py new file mode 100644 index 0000000..15d2867 --- /dev/null +++ b/tests/test_email_quoted_at_sign.py @@ -0,0 +1,7 @@ +"""HU-02: accept @ inside a quoted local part.""" +import validators + + +def test_email_accepts_at_sign_inside_quoted_local_part(): + result = validators.email('"user@name"@example.com') + assert result is True, repr(result) diff --git a/tests/test_ipv4_strict_cidr.py b/tests/test_ipv4_strict_cidr.py new file mode 100644 index 0000000..317c90d --- /dev/null +++ b/tests/test_ipv4_strict_cidr.py @@ -0,0 +1,8 @@ +"""HU-04: require a prefix length in the documented strict CIDR mode.""" +import validators + + +def test_ipv4_strict_cidr_rejects_dotted_netmask(): + # The network is valid, but its representation is not CIDR prefix notation. + result = validators.ipv4("192.168.1.0/255.255.255.0", strict=True) + assert isinstance(result, validators.ValidationError), repr(result) diff --git a/tests/test_ipv6_brackets.py b/tests/test_ipv6_brackets.py new file mode 100644 index 0000000..5c31fee --- /dev/null +++ b/tests/test_ipv6_brackets.py @@ -0,0 +1,10 @@ +"""HU-04: proposed enhancement; brackets are URL host syntax. + +This test expresses a new requirement, not a defect in the current contract. +""" +import validators + + +def test_ipv6_accepts_address_in_brackets(): + result = validators.ipv6("[2001:db8::1]") + assert result is True, repr(result) From a77ed6f86682b287e5f8436faf60dc3e8fb0d369 Mon Sep 17 00:00:00 2001 From: Erick Salas Date: Sun, 18 Oct 2026 08:58:50 -0600 Subject: [PATCH 2/4] added docstrings on 5 tests --- tests/test_email_control_character.py | 15 ++++++++++++--- tests/test_email_ipv6_literal.py | 15 +++++++++++++-- tests/test_email_quoted_at_sign.py | 15 +++++++++++++-- tests/test_ipv4_strict_cidr.py | 16 +++++++++++++--- tests/test_ipv6_brackets.py | 15 +++++++++++++-- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/tests/test_email_control_character.py b/tests/test_email_control_character.py index 8023bb1..542ec6e 100644 --- a/tests/test_email_control_character.py +++ b/tests/test_email_control_character.py @@ -1,9 +1,18 @@ """HU-02: reject SOH in an address intended for SMTP (RFC 5321).""" + +# external +import pytest + import validators -def test_email_rejects_soh_control_character(): - # The Python escape introduces the actual ASCII SOH character. - email = '"user\x01name"@example.com' +@pytest.mark.parametrize( + ("email",), + [ + ('"user\x01name"@example.com', ), + ], +) +def test_email_rejects_soh_control_character(email: str): + """The Python escape introduces the actual ASCII SOH character.""" result = validators.email(email) assert isinstance(result, validators.ValidationError), repr(result) diff --git a/tests/test_email_ipv6_literal.py b/tests/test_email_ipv6_literal.py index a2a0391..a4f208a 100644 --- a/tests/test_email_ipv6_literal.py +++ b/tests/test_email_ipv6_literal.py @@ -1,9 +1,20 @@ """HU-02: accept the IPv6: prefix in an SMTP address literal.""" + +# external +import pytest + import validators -def test_email_accepts_ipv6_literal_with_prefix(): +@pytest.mark.parametrize( + ("email",), + [ + ("usuario@[IPv6:2001:db8::1]", ), + ], +) +def test_email_accepts_ipv6_literal_with_prefix(email: str): + """Email accepts ipv6 literal with prefix.""" result = validators.email( - "usuario@[IPv6:2001:db8::1]", ipv6_address=True + email, ipv6_address=True ) assert result is True, repr(result) diff --git a/tests/test_email_quoted_at_sign.py b/tests/test_email_quoted_at_sign.py index 15d2867..02d7b1f 100644 --- a/tests/test_email_quoted_at_sign.py +++ b/tests/test_email_quoted_at_sign.py @@ -1,7 +1,18 @@ """HU-02: accept @ inside a quoted local part.""" + +# external +import pytest + import validators -def test_email_accepts_at_sign_inside_quoted_local_part(): - result = validators.email('"user@name"@example.com') +@pytest.mark.parametrize( + ("email",), + [ + ('"user@name"@example.com', ), + ], +) +def test_email_accepts_at_sign_inside_quoted_local_part(email: str): + """The email accepts at sign inside quoted local part character.""" + result = validators.email(email) assert result is True, repr(result) diff --git a/tests/test_ipv4_strict_cidr.py b/tests/test_ipv4_strict_cidr.py index 317c90d..68bdf32 100644 --- a/tests/test_ipv4_strict_cidr.py +++ b/tests/test_ipv4_strict_cidr.py @@ -1,8 +1,18 @@ """HU-04: require a prefix length in the documented strict CIDR mode.""" + +# external +import pytest + import validators -def test_ipv4_strict_cidr_rejects_dotted_netmask(): - # The network is valid, but its representation is not CIDR prefix notation. - result = validators.ipv4("192.168.1.0/255.255.255.0", strict=True) +@pytest.mark.parametrize( + ("ip",), + [ + ("192.168.1.0/255.255.255.0", ), + ], +) +def test_ipv4_strict_cidr_rejects_dotted_netmask(ip: str): + """The network is valid, but its representation is not CIDR prefix notation.""" + result = validators.ipv4(ip, strict=True) assert isinstance(result, validators.ValidationError), repr(result) diff --git a/tests/test_ipv6_brackets.py b/tests/test_ipv6_brackets.py index 5c31fee..908cdd4 100644 --- a/tests/test_ipv6_brackets.py +++ b/tests/test_ipv6_brackets.py @@ -2,9 +2,20 @@ This test expresses a new requirement, not a defect in the current contract. """ + +# external +import pytest + import validators -def test_ipv6_accepts_address_in_brackets(): - result = validators.ipv6("[2001:db8::1]") +@pytest.mark.parametrize( + ("ip",), + [ + ("[2001:db8::1]", ), + ], +) +def test_ipv6_accepts_address_in_brackets(ip: str): + """Accept iv6 address in brackets.""" + result = validators.ipv6(ip) assert result is True, repr(result) From 9e2e96c55380505dbf780121704ceef0133da5ff Mon Sep 17 00:00:00 2001 From: LisDiana Rodriguez Date: Sun, 18 Oct 2026 13:18:08 -0600 Subject: [PATCH 3/4] test(email): add parametrized ASCII control character cases --- tests/test_email_control_character.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_email_control_character.py b/tests/test_email_control_character.py index 542ec6e..aebc399 100644 --- a/tests/test_email_control_character.py +++ b/tests/test_email_control_character.py @@ -1,4 +1,4 @@ -"""HU-02: reject SOH in an address intended for SMTP (RFC 5321).""" +"""HU-02: reject ASCII control characters in quoted SMTP local parts.""" # external import pytest @@ -7,12 +7,14 @@ @pytest.mark.parametrize( - ("email",), - [ - ('"user\x01name"@example.com', ), - ], + "control_code", + [1, 2, 7, 11, 12, 14, 27, 31, 127], ) -def test_email_rejects_soh_control_character(email: str): - """The Python escape introduces the actual ASCII SOH character.""" + +def test_email_rejects_control_characters(control_code: int): + """Reject selected ASCII control characters in quoted local parts.""" + email = f'"user{chr(control_code)}name"@example.com' + result = validators.email(email) - assert isinstance(result, validators.ValidationError), repr(result) + + assert isinstance(result, validators.ValidationError), repr(result) \ No newline at end of file From f0eb665cc605b301c4e17bda6eff2f05b4ea435e Mon Sep 17 00:00:00 2001 From: LisDiana Rodriguez Date: Sun, 18 Oct 2026 13:54:37 -0600 Subject: [PATCH 4/4] fix(email): reject ASCII control characters in quoted local parts --- src/validators/email.py | 4 ++-- tests/test_email_control_character.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/validators/email.py b/src/validators/email.py index 46cc594..3b59d8a 100644 --- a/src/validators/email.py +++ b/src/validators/email.py @@ -91,8 +91,8 @@ def email( + r"(\.[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF0-9a-z!#$%&'*+/=?^_`{}|~\-]+)*$" # quoted-string + r'|^"(' - + r"[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF\001-\010\013\014\016-\037" - + r"!#-\[\]-\177]|\\[\011.]" + + r"[\u0100-\u017F\u0180-\u024F\u00A0-\u00FF" + + r"!#-\[\]-~]|\\[\011.]" + r')*")$', username_part, re.IGNORECASE, diff --git a/tests/test_email_control_character.py b/tests/test_email_control_character.py index aebc399..2373d11 100644 --- a/tests/test_email_control_character.py +++ b/tests/test_email_control_character.py @@ -17,4 +17,4 @@ def test_email_rejects_control_characters(control_code: int): result = validators.email(email) - assert isinstance(result, validators.ValidationError), repr(result) \ No newline at end of file + assert isinstance(result, validators.ValidationError), repr(result)