Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/cnh #5

Merged
merged 3 commits into from
Jun 8, 2024
Merged
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
1 change: 1 addition & 0 deletions pydantic_br_validator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
CEPDigits = str
else:
from .fields.cnpj_field import * # noqa
from .fields.cnh_field import * # noqa
from .fields.cpf_field import * # noqa
from .fields.cep_field import * # noqa
16 changes: 16 additions & 0 deletions pydantic_br_validator/fields/cnh_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from ..validators.cnh_validator import CNHValidator
from .base_field import BaseDigits

__all__ = ["CNH"]


class CNH(BaseDigits):
"""
Only Accepts string of CNH with digits.
Attributes:
number (str): CNH number.
"""

format = "cnh"
Validator = CNHValidator
50 changes: 50 additions & 0 deletions pydantic_br_validator/validators/cnh_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import re

from .base_validator import FieldValidator

__all__ = ["CNHValidator"]


class CNHValidator(FieldValidator):
def __init__(self, cnh: str) -> None:
self.cnh = cnh

def validate(self) -> bool:
cnh = re.sub("[^0-9]", "", str(self.cnh))

if len(set(cnh)) == 1:
return False

if len(cnh) != 11:
return False

first_digit = self._validate_first_digit(cnh)
second_digit = self._validate_second_digit(cnh)
return cnh[9] == first_digit and cnh[10] == second_digit

def _validate_first_digit(self, cnh: str) -> str:
self.dsc = 0
sum = 0

for i in range(9, 0, -1):
sum += int(cnh[9 - i]) * i

first_digit = sum % 11
if first_digit >= 10:
first_digit, self.dsc = 0, 2
return str(first_digit)

def _validate_second_digit(self, cnh: str) -> str:
sum = 0

for i in range(1, 10):
sum += int(cnh[i - 1]) * i

rest = sum % 11

second_digit = rest - self.dsc
if second_digit < 0:
second_digit += 11
if second_digit >= 10:
second_digit = 0
return str(second_digit)
101 changes: 101 additions & 0 deletions tests/test_cnh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from string import ascii_letters

import pytest
from pydantic import BaseModel, ValidationError

from pydantic_br_validator import (
CNH,
FieldDigitError,
FieldInvalidError,
FieldTypeError,
)

cnh_mock = [
"49761142867",
"15706519597",
"18820839790",
"93025633607",
"22255370700",
"74487688509",
"83002264521",
"21671642456",
"36407284795",
"93017746007",
]


@pytest.fixture
def person():
class Person(BaseModel):
cnh: CNH

yield Person


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_be_string(person, cnh):
p1 = person(cnh=cnh)
assert isinstance(p1.cnh, str)


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_accept_only_numbers(person, cnh):
p1 = person(cnh=cnh)
assert p1.cnh == cnh


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_fail_when_use_another_type(person, cnh):
with pytest.raises(ValidationError) as e:
person(cnh=int(cnh))
assert FieldTypeError.msg_template in str(e.value)


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_fail_when_use_invalid_cnh(person, cnh):
with pytest.raises(ValidationError) as e:
invalid_cnh = cnh[:5] + cnh[6:]
person(cnh=invalid_cnh)
assert FieldInvalidError.msg_template in str(e.value)


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_fail_when_use_digits_count_above_cnh(person, cnh):
with pytest.raises(ValidationError) as e:
person(cnh=cnh * 2)
assert FieldInvalidError.msg_template in str(e.value)


@pytest.mark.parametrize("cnh", cnh_mock)
def test_must_fail_when_use_digits_count_below_cnh(person, cnh):
with pytest.raises(ValidationError) as e:
person(cnh=cnh[:5])
assert FieldInvalidError.msg_template in str(e.value)


@pytest.mark.parametrize(
"cnh",
[
"00000000000",
"11111111111",
"22222222222",
"33333333333",
"44444444444",
"55555555555",
"66666666666",
"77777777777",
"88888888888",
"99999999999",
],
)
def test_must_fail_when_use_sequecial_digits(person, cnh):
with pytest.raises(ValidationError) as e:
person(cnh=cnh)
assert FieldInvalidError.msg_template in str(e.value)


def test_must_fail_when_not_use_only_digits(person):
with pytest.raises(ValidationError) as e:
letters = ascii_letters[:11]
person(cnh=letters)
assert FieldDigitError.msg_template in str(e.value)
Loading