Skip to content

Commit

Permalink
Refatoracao do codigo dos utilitarios de cpf (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
camilamaia committed Jun 24, 2023
1 parent 2732a1e commit c741843
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 90 deletions.
115 changes: 44 additions & 71 deletions brutils/cpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,31 @@

def sieve(dirty): # type: (str) -> str
"""
Filters out CPF formatting symbols. Symbols that are not used
in the CPF formatting are left unfiltered on purpose so that
if fails other tests, because their presence indicate that the
input was somehow corrupted.
Filters out CPF formatting symbols.
Symbols that are not used in the CPF formatting are left
unfiltered on purpose so that if fails other tests,
because their presence indicate that the input was somehow corrupted.
"""

return "".join(filter(lambda char: char not in ".-", dirty))


def parse(dirty): # type: (str) -> str
"""
Filters out CPF formatting symbols. Symbols that are not used
in the CPF formatting are left unfiltered on purpose so that
if fails other tests, because their presence indicate that the
input was somehow corrupted.
"""

"""Alias to the function `sieve`. Better naming."""
return sieve(dirty)


def display(cpf): # type: (str) -> str
"""
Will format an adequately formatted numbers-only CPF string,
Format an adequately formatted numbers-only CPF string,
adding in standard formatting visual aid symbols for display.
Backcompatibility for Version 1.0.1.
"""

if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1:
return None

return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


Expand All @@ -46,37 +44,8 @@ def format_cpf(cpf): # type: (str) -> str

if not is_valid(cpf):
return None
return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


# CALCULATORS
#############


def hashdigit(cpf, position): # type: (str, int) -> int
"""
Will compute the given `position` checksum digit for the `cpf`
input. The input needs to contain all elements previous to
`position` else computation will yield the wrong result.
"""
val = (
sum(
int(digit) * weight
for digit, weight in zip(cpf, range(position, 1, -1))
)
% 11
)
return 0 if val < 2 else 11 - val


def checksum(basenum): # type: (str) -> str
"""
Will compute the checksum digits for a given CPF base number.
`basenum` needs to be a digit-string of adequate length.
"""
verifying_digits = str(hashdigit(basenum, 10))
verifying_digits += str(hashdigit(basenum + verifying_digits, 11))
return verifying_digits
return "{}.{}.{}-{}".format(cpf[:3], cpf[3:6], cpf[6:9], cpf[9:])


# OPERATIONS
Expand All @@ -88,54 +57,58 @@ def validate(cpf): # type: (str) -> bool
Returns whether or not the verifying checksum digits of the
given `cpf` match it's base number. Input should be a digit
string of proper length.
Source: https://www.geradorcpf.com/algoritmo_do_cpf.htm
Backcompatibility for Version 1.0.1.
"""

if not cpf.isdigit() or len(cpf) != 11 or len(set(cpf)) == 1:
return False
return all(hashdigit(cpf, i + 10) == int(v) for i, v in enumerate(cpf[9:]))


def generate(): # type: () -> str
"""Generates a random valid CPF digit string."""
base = str(randint(1, 999999998)).zfill(9)
while len(set(base)) == 1:
base = str(randint(1, 999999998)).zfill(9)
return base + checksum(base)
return all(_hashdigit(cpf, i + 10) == int(v) for i, v in enumerate(cpf[9:]))


def is_valid(cpf): # type: (str) -> bool
"""
Returns whether or not a cpf is_valid.
Source: https://www.geradorcpf.com/algoritmo_do_cpf.htm
Evaluates that cpf is String and calls validate.
"""
is_syntax_valid = isinstance(cpf, str) and len(cpf) == 11 and cpf.isdigit()

return is_syntax_valid and _is_semantic_valid(cpf)
return isinstance(cpf, str) and validate(cpf)


def _is_semantic_valid(cpf):
cpf = [int(digit) for digit in cpf]
def generate(): # type: () -> str
"""Generates a random valid CPF digit string."""

constants_tenth_digit = [10, 9, 8, 7, 6, 5, 4, 3, 2]
is_tenth_digit_valid = _is_digit_valid(cpf, constants_tenth_digit, 9)
base = str(randint(1, 999999998)).zfill(9)

constants_eleventh_digit = [11, 10, 9, 8, 7, 6, 5, 4, 3, 2]
is_eleventh_digit_valid = _is_digit_valid(cpf, constants_eleventh_digit, 10)
return base + _checksum(base)

return is_tenth_digit_valid and is_eleventh_digit_valid

def _hashdigit(cpf, position): # type: (str, int) -> int
"""
Will compute the given `position` checksum digit for the `cpf`
input. The input needs to contain all elements previous to
`position` else computation will yield the wrong result.
"""

def _is_digit_valid(cpf, constants, digit_index):
sum = _multiply_and_sum_lists(cpf, constants, digit_index)
rest = sum % 11
digit = cpf[digit_index]
val = (
sum(
int(digit) * weight
for digit, weight in zip(cpf, range(position, 1, -1))
)
% 11
)

return (rest <= 2 and digit == 0) or (rest > 2 and digit == (11 - rest))
return 0 if val < 2 else 11 - val


def _multiply_and_sum_lists(list_1, list_2, max_index):
sum = 0
def _checksum(basenum): # type: (str) -> str
"""
Will compute the checksum digits for a given CPF base number.
`basenum` needs to be a digit-string of adequate length.
"""

for index in range(0, max_index):
sum += list_1[index] * list_2[index]
verifying_digits = str(_hashdigit(basenum, 10))
verifying_digits += str(_hashdigit(basenum + verifying_digits, 11))

return sum
return verifying_digits
39 changes: 20 additions & 19 deletions tests/test_cpf.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
from brutils.cpf import (
sieve,
display,
hashdigit,
checksum,
validate,
generate,
is_valid,
format_cpf,
parse,
_hashdigit,
_checksum,
)
from unittest import TestCase, main

Expand All @@ -33,12 +33,10 @@ def test_sieve(self):
assert sieve("...---...") == ""

def test_parse(self):
assert parse("00000000000") == "00000000000"
assert parse("123.456.789-10") == "12345678910"
assert parse("134..2435.-1892.-") == "13424351892"
assert parse("abc1230916*!*&#") == "abc1230916*!*&#"
assert parse("ab.c1.--.2-309.-1-.6-.*.-!*&#") == "abc1230916*!*&#"
assert parse("...---...") == ""
with patch("brutils.cpf.sieve") as mock_sieve:
# When call parse, it calls sieve
parse("123.456.789-10")
mock_sieve.assert_called()

def test_display(self):
assert display("00000000011") == "000.000.000-11"
Expand All @@ -58,16 +56,6 @@ def test_format_cpf(self):
# When cpf isn't valid, returns None
assert format_cpf("11144477735") is None

def test_hashdigit(self):
assert hashdigit("000000000", 10) == 0
assert hashdigit("0000000000", 11) == 0
assert hashdigit("52513127765", 10) == 6
assert hashdigit("52513127765", 11) == 5

def test_checksum(self):
assert checksum("000000000") == "00"
assert checksum("525131277") == "65"

def test_validate(self):
assert validate("52513127765")
assert validate("52599927765")
Expand All @@ -83,6 +71,9 @@ def test_is_valid(self):
# When cpf does not contain only digits, returns False
assert not is_valid("1112223334-")

# When CPF has only the same digit, returns false
assert not is_valid("11111111111")

# When rest_1 is lt 2 and the 10th digit is not 0, returns False
assert not is_valid("11111111215")

Expand All @@ -100,10 +91,20 @@ def test_is_valid(self):
assert is_valid("11111111200")

def test_generate(self):
for i in range(1000):
for _ in range(10_000):
assert validate(generate())
assert display(generate()) is not None

def test__hashdigit(self):
assert _hashdigit("000000000", 10) == 0
assert _hashdigit("0000000000", 11) == 0
assert _hashdigit("52513127765", 10) == 6
assert _hashdigit("52513127765", 11) == 5

def test_checksum(self):
assert _checksum("000000000") == "00"
assert _checksum("525131277") == "65"


if __name__ == "__main__":
main()

0 comments on commit c741843

Please sign in to comment.