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
6 changes: 5 additions & 1 deletion src/validators/i18n/ru.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Russia."""

# standard
import re

# local
from validators.utils import validator


Expand Down Expand Up @@ -30,7 +34,7 @@ def ru_inn(value: str):
- For 10-digit INN: checks 10th control digit
- For 12-digit INN: checks both 11th and 12th control digits
"""
if not value:
if not value or not re.fullmatch(r"[0-9]+", value):
return False

try:
Expand Down
20 changes: 20 additions & 0 deletions tests/i18n/test_ru.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,23 @@ def test_individual_inn_requires_both_check_digits(value: str):
def test_individual_inn_check_digit_remainders(value: str):
"""Accept both ordinary remainders and remainder 10 mapped to zero."""
assert ru_inn(value) is True


@pytest.mark.parametrize("value", ["7830002293", "500100732259"])
@pytest.mark.parametrize("zero", ["\u0660", "\uff10", "\u0966"])
def test_rejects_non_ascii_inn_digits(value: str, zero: str):
"""Reject digit lookalikes in company and individual INNs."""
translated = value.translate(
str.maketrans("0123456789", "".join(chr(ord(zero) + n) for n in range(10)))
)
assert isinstance(ru_inn(translated), ValidationError)
assert ru_inn(value) is True


@pytest.mark.parametrize("value", ["7830002293", "500100732259"])
@pytest.mark.parametrize("position", [0, -1])
def test_rejects_mixed_ascii_and_non_ascii_inn_digits(value: str, position: int):
"""A single non-ASCII digit must not be normalized by int()."""
digits = list(value)
digits[position] = chr(ord("\u0660") + int(digits[position]))
assert isinstance(ru_inn("".join(digits)), ValidationError)