From 6e31c5e30c3b79d5c089f127b9ca28c2d2e42c00 Mon Sep 17 00:00:00 2001 From: Rodrigo Castro Date: Thu, 24 Sep 2020 13:32:37 -0300 Subject: [PATCH] =?UTF-8?q?Refatora=C3=A7=C3=A3o=20das=20configura=C3=A7?= =?UTF-8?q?=C3=B5es=20de=20invoice=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refatoração do novo padrão de verificações e novo formato * refatora Configuration * adiciona safetynet no _run_validators * adiciona validador de regex * refatoração de Configurations * refatoração na base! * refatoração em transaction * fmt * adiciona bdd's * fmt * complementa testes * valida datas * altera testes * fmt * altera todo's * string regex * altera nomenclatura * adiciona validação de datas * completa bdd * altera validações de data * altera validações * testa validators da situação especial * docstrings * Update imopay_wrapper/validators.py Co-authored-by: PedroRegisPOAR Co-authored-by: PedroRegisPOAR --- imopay_wrapper/field.py | 46 ++++ imopay_wrapper/models/base.py | 84 ++++-- imopay_wrapper/models/transaction.py | 115 ++++++-- imopay_wrapper/regex.py | 1 + imopay_wrapper/validators.py | 68 +++++ tests/models/test_base.py | 246 ++++++++++++++++- tests/models/transaction/__init__.py | 9 + .../models/transaction/test_configuration.py | 252 +++++++++++++++++- tests/models/transaction/test_invoice.py | 190 +++++++++++-- .../test_invoice_configurations.py | 24 +- .../transaction/test_invoice_tranction.py | 74 ++--- tests/test_regex.py | 10 + tests/utils.py | 32 +++ tests/validators/__init__.py | 3 + .../test_validate_date1_gt_date_2.py | 104 ++++++++ .../test_validate_date_isoformat.py | 185 +++++++++++++ .../test_validate_obj_attr_in_collection.py | 38 --- .../test_validate_obj_attr_regex.py | 42 +++ .../validators/test_validate_obj_attr_type.py | 32 --- tests/wrapper/test_transaction.py | 48 ++-- 20 files changed, 1381 insertions(+), 222 deletions(-) create mode 100644 imopay_wrapper/field.py create mode 100644 imopay_wrapper/regex.py create mode 100644 tests/test_regex.py create mode 100644 tests/validators/test_validate_date1_gt_date_2.py create mode 100644 tests/validators/test_validate_date_isoformat.py create mode 100644 tests/validators/test_validate_obj_attr_regex.py diff --git a/imopay_wrapper/field.py b/imopay_wrapper/field.py new file mode 100644 index 0000000..2facbbf --- /dev/null +++ b/imopay_wrapper/field.py @@ -0,0 +1,46 @@ +from dataclasses import Field, MISSING + + +class ImopayField(Field): + def __init__( + self, opitional, default, default_factory, init, repr, hash, compare, metadata + ): + + self.optional = opitional + + super().__init__(default, default_factory, init, repr, hash, compare, metadata) + + +# This function is used instead of exposing Field creation directly, +# so that a type checker can be told (via overloads) that this is a +# function whose type depends on its parameters. +def field( + *, + optional=False, + default=MISSING, + default_factory=MISSING, + init=True, + repr=True, + hash=None, + compare=True, + metadata=None, +): + """Return an object to identify dataclass fields. + + default is the default value of the field. default_factory is a + 0-argument function called to initialize a field's value. If init + is True, the field will be a parameter to the class's __init__() + function. If repr is True, the field will be included in the + object's repr(). If hash is True, the field will be included in + the object's hash(). If compare is True, the field will be used + in comparison functions. metadata, if specified, must be a + mapping which is stored but not otherwise examined by dataclass. + + It is an error to specify both default and default_factory. + """ + + if default is not MISSING and default_factory is not MISSING: + raise ValueError("cannot specify both default and default_factory") + return ImopayField( + optional, default, default_factory, init, repr, hash, compare, metadata + ) diff --git a/imopay_wrapper/models/base.py b/imopay_wrapper/models/base.py index 0aa2055..06edf73 100644 --- a/imopay_wrapper/models/base.py +++ b/imopay_wrapper/models/base.py @@ -7,6 +7,8 @@ @dataclass class BaseImopayObj: + VALIDATION_METHOD_PATTERN = "_validate" + def __post_init__(self): self.__run_validators() self._init_nested_fields() @@ -15,23 +17,31 @@ def _init_nested_fields(self): pass @classmethod - def get_fields(cls): + def __get_fields(cls): + """ + Método para retornar todos os campos! + """ # noinspection PyUnresolvedReferences return cls.__dataclass_fields__ - def to_dict(self): - data = {} - for field_name, field in self.get_fields().items(): - value = getattr(self, field_name) + def __get_field(self, name): + """ + Método para retornar um campo com base no nome passado! + """ + try: + # noinspection PyUnresolvedReferences + return self.__get_fields()[name] + except KeyError as e: + raise AttributeError(f"Não existe o campo {name} em {self}") from e - if self.is_empty_value(value): - continue + @staticmethod + def __is_field_optional(field): + """ + Método para verificar se um campo é opcional ou não! - if isinstance(value, BaseImopayObj): - data[field_name] = value.to_dict() - else: - data[field_name] = field.type(value) - return data + Influência na validação! + """ + return getattr(field, "optional", False) def __get_validation_methods(self): """ @@ -40,10 +50,24 @@ def __get_validation_methods(self): """ data = inspect.getmembers(self, predicate=inspect.ismethod) - validation_methods = [item[1] for item in data if "_validate" in item[0]] + validation_methods = [ + item[1] for item in data if self.VALIDATION_METHOD_PATTERN in item[0] + ] return validation_methods + @staticmethod + def __get_attr_name_from_method(method): + """ + Método para retornar o nome do atributo/campo a partir de + um método de validação (que siga o padrão `_validate_attr`). + """ + name = method.__name__ + + initial_index = len(BaseImopayObj.VALIDATION_METHOD_PATTERN) + 1 + + return name[initial_index:] + def __run_validators(self): """ Método que executa todos os métodos de validação. @@ -53,20 +77,34 @@ def __run_validators(self): errors = [] for method in validation_methods: + error = None + attr_name = self.__get_attr_name_from_method(method) + field = self.__get_field(attr_name) try: method() except FieldError as e: - errors.append(e) + error = e + except Exception as e: + error = FieldError(method.__name__, str(e)) + finally: + if error is not None and not self.__is_field_optional(field): + errors.append(error) if errors: raise ValidationError(self, errors) + @staticmethod + def __is_empty_value(value): + return value == "" or value is None + @classmethod def from_dict(cls, data: Union[dict, Any]): + if data is None: + data = {} missing_fields = { field_name - for field_name in cls.get_fields().keys() + for field_name in cls.__get_fields().keys() if field_name not in data.keys() } @@ -76,6 +114,16 @@ def from_dict(cls, data: Union[dict, Any]): # noinspection PyArgumentList return cls(**data) - @staticmethod - def is_empty_value(value): - return value == "" or value is None + def to_dict(self): + data = {} + for field_name, field in self.__get_fields().items(): + value = getattr(self, field_name) + + if self.__is_empty_value(value): + continue + + if isinstance(value, BaseImopayObj): + data[field_name] = value.to_dict() + else: + data[field_name] = field.type(value) + return data diff --git a/imopay_wrapper/models/transaction.py b/imopay_wrapper/models/transaction.py index 78413ea..f91f17e 100644 --- a/imopay_wrapper/models/transaction.py +++ b/imopay_wrapper/models/transaction.py @@ -1,7 +1,15 @@ from typing import List -from dataclasses import dataclass, field +from dataclasses import dataclass from .base import BaseImopayObj +from ..exceptions import FieldError +from ..validators import ( + validate_obj_attr_type, + validate_obj_attr_in_collection, + validate_date_isoformat, + validate_date_1_gt_date_2, +) +from ..field import field @dataclass @@ -14,28 +22,77 @@ class BaseTransaction(BaseImopayObj): @dataclass -class Configuration(BaseImopayObj): - value: str - type: str +class BaseConfiguration(BaseImopayObj): + value: int charge_type: str - days: str + + PERCENTAGE = "p" + FIXED = "f" + DAILY_FIXED = "df" + DAILY_PERCENTAGE = "dp" + MONTHLY_PERCENTAGE = "mp" + + VALID_CHARGE_TYPES = {} + + def _validate_value(self): + self.value = int(self.value) + if self.value <= 0: + raise FieldError("value", "O valor é menor do que 1!") + + def _validate_charge_type(self): + validate_obj_attr_in_collection(self, "charge_type", self.VALID_CHARGE_TYPES) + + +@dataclass +class InterestConfiguration(BaseConfiguration): + VALID_CHARGE_TYPES = { + BaseConfiguration.DAILY_FIXED, + BaseConfiguration.DAILY_PERCENTAGE, + BaseConfiguration.MONTHLY_PERCENTAGE, + } + + +@dataclass +class FineConfiguration(BaseConfiguration): + VALID_CHARGE_TYPES = {BaseConfiguration.FIXED, BaseConfiguration.PERCENTAGE} + + +@dataclass +class DiscountConfiguration(FineConfiguration): + date: str + + def _validate_date(self): + validate_date_isoformat(self, "date", past=True, allow_today=True) @dataclass class InvoiceConfigurations(BaseImopayObj): - fine: Configuration - interest: Configuration - discounts: List[Configuration] = field(default=list) - - def __post_init__(self): - if isinstance(self.fine, dict): - self.fine = Configuration.from_dict(self.fine) - if isinstance(self.interest, dict): - self.interest = Configuration.from_dict(self.interest) + fine: FineConfiguration = field(optional=True) + interest: InterestConfiguration = field(optional=True) + discounts: List[DiscountConfiguration] = field(optional=True, default=list) + + def _validate_fine(self): + validate_obj_attr_type(self, "fine", dict) + + def _validate_interest(self): + validate_obj_attr_type(self, "interest", dict) + + def _validate_discounts(self): + validate_obj_attr_type(self, "discounts", list) + + for discount in self.discounts: + validate_obj_attr_type(self, "discounts", dict, value=discount) + + def _init_nested_fields(self): + if self.fine is not None: + self.fine = FineConfiguration.from_dict(self.fine) + + if self.interest is not None: + self.interest = InterestConfiguration.from_dict(self.interest) + if self.discounts: for i, discount in enumerate(self.discounts): - if isinstance(discount, dict): - self.discounts[i] = Configuration.from_dict(discount) + self.discounts[i] = DiscountConfiguration.from_dict(discount) def to_dict(self): """ @@ -60,17 +117,31 @@ def to_dict(self): class Invoice(BaseImopayObj): expiration_date: str limit_date: str - configurations: InvoiceConfigurations = field(default_factory=dict) + configurations: InvoiceConfigurations = field(optional=True, default_factory=dict) - def __post_init__(self): - if isinstance(self.configurations, dict): + def _init_nested_fields(self): + if self.configurations is not None: self.configurations = InvoiceConfigurations.from_dict(self.configurations) + def _validate_configurations(self): + validate_obj_attr_type(self, "configurations", dict) + + def _validate_expiration_date(self): + validate_date_isoformat(self, "expiration_date", future=True, allow_today=True) + + def _validate_limit_date(self): + validate_date_isoformat(self, "limit_date", future=True, allow_today=True) + validate_date_1_gt_date_2( + "limit_date", self.limit_date, self.expiration_date, allow_equal=True + ) + @dataclass class InvoiceTransaction(BaseTransaction): payment_method: Invoice - def __post_init__(self): - if isinstance(self.payment_method, dict): - self.payment_method = Invoice(**self.payment_method) + def _init_nested_fields(self): + self.payment_method = Invoice.from_dict(self.payment_method) + + def _validate_payment_method(self): + validate_obj_attr_type(self, "payment_method", dict) diff --git a/imopay_wrapper/regex.py b/imopay_wrapper/regex.py new file mode 100644 index 0000000..1cebb97 --- /dev/null +++ b/imopay_wrapper/regex.py @@ -0,0 +1 @@ +date_regex = r"^\d{4}-\d{2}-\d{2}$" diff --git a/imopay_wrapper/validators.py b/imopay_wrapper/validators.py index b2af2c4..3cc562a 100644 --- a/imopay_wrapper/validators.py +++ b/imopay_wrapper/validators.py @@ -1,3 +1,7 @@ +import re + +from datetime import date + from .exceptions import FieldError @@ -41,3 +45,67 @@ def validate_obj_attr_in_collection(obj, attr, collection, value=None): if value not in collection: raise FieldError(attr, f"{value} não está na coleção {collection}") + + +def validate_obj_attr_regex(obj, attr, regex, value=None): + """ + Método para validar que o valor de um atributo do objeto + é do formato de um determinado regex. + + Caso o valor não siga o regex, lança o erro! + """ + value = _get_value_from_attr_or_value(obj, attr, value=value) + + result = re.search(regex, value) + if result is None: + raise FieldError(attr, f"{value} não é do formato f{regex}!") + + +def validate_date_1_gt_date_2(attr, d1, d2, allow_equal=False): + """ + Método para validar se data 1 é maior do que data 2. + + Caso allow_equal seja True, data 1 pode ser igual à data 2. + """ + if allow_equal: + if d1 < d2: + raise FieldError(attr, f"{d1} não é igual ou maior do que {d2}") + + elif d1 <= d2: + raise FieldError(attr, f"{d1} não é estritamente maior do que {d2}") + + +def validate_date_isoformat( + obj, attr, future=None, past=None, allow_today=False, value=None +): + """ + Método para validar uma data que siga a iso YYYY-mm-dd + https://en.wikipedia.org/wiki/ISO_8601 + + É possível validar se é uma data futura ou passada também! + + Com o allow_today é possível flexibilizar se é + permitido a data de hoje ou não! + """ + if past and future: + raise ValueError( + "Não se pode verificar se é uma data futura e passada ao mesmo tempo!" + ) + + value = _get_value_from_attr_or_value(obj, attr, value=value) + + d = date.fromisoformat(value) + + today = date.today() + + if past: + try: + validate_date_1_gt_date_2(attr, today, d, allow_equal=allow_today) + except FieldError as e: + raise FieldError(attr, f"{value} não é uma data do passado!") from e + + if future: + try: + validate_date_1_gt_date_2(attr, d, today, allow_equal=allow_today) + except FieldError as e: + raise FieldError(attr, f"{value} não é uma data do futuro!") from e diff --git a/tests/models/test_base.py b/tests/models/test_base.py index af8a348..2376ffe 100644 --- a/tests/models/test_base.py +++ b/tests/models/test_base.py @@ -15,8 +15,15 @@ class CustomClass(BaseImopayObj): self.custom_class = CustomClass + def test_validation_method_pattern(self): + expected = "_validate" + + result = BaseImopayObj.VALIDATION_METHOD_PATTERN + + self.assertEqual(result, expected) + def test_is_empty_value(self): - self.assertTrue(BaseImopayObj.is_empty_value("")) + self.assertTrue(BaseImopayObj._BaseImopayObj__is_empty_value("")) def test_to_dict_1(self): o1 = self.custom_class(required="value") @@ -121,13 +128,14 @@ def test_get_validation_methods_2(self): - essa entrada deve ser um "chamável" """ + @dataclass class CustomClass(BaseImopayObj): foo: str def _validate_foo(self): pass - obj = CustomClass() + obj = CustomClass.from_dict(None) result = obj._BaseImopayObj__get_validation_methods() @@ -137,43 +145,175 @@ def _validate_foo(self): with self.subTest(item): self.assertTrue(callable(item)) + def test_get_attr_name_from_method_1(self): + """ + Dado: + - um método com + __name__="_validate_foo" + Quando: + - for chamado BaseImopayObj._BaseImopayObj__get_attr_name_from_method(method) # noqa + Então: + - o resultado deve ser "foo" + """ + method = MagicMock(__name__="_validate_foo") + + expected = "foo" + + result = BaseImopayObj._BaseImopayObj__get_attr_name_from_method(method) + + self.assertEqual(result, expected) + + def test_get_field_1(self): + """ + Dado: + - um nome de campo field_name 'foo' + - um campo qualquer mocked_field + - um objeto qualquer obj que tenha o campo field_name: mocked_field + Quando: + - for chamado BaseImopayObj._BaseImopayObj__get_field(obj, field_name) + Então: + - o resultado deve ser mocked_field + """ + field_name = "foo" + + mocked_field = MagicMock() + + obj = MagicMock( + _BaseImopayObj__get_fields=MagicMock( + return_value={field_name: mocked_field} + ) + ) + + expected = mocked_field + + result = BaseImopayObj._BaseImopayObj__get_field(obj, field_name) + + self.assertEqual(result, expected) + + def test_get_field_2(self): + """ + Dado: + - um nome de campo field_name 'foo' + - um campo qualquer mocked_field + - um objeto qualquer obj que tenha o campo 'bar': mocked_field + Quando: + - for chamado BaseImopayObj._BaseImopayObj__get_field(obj, name) + Então: + - deve ser lançado um AttributeError + """ + field_name = "foo" + + mocked_field = MagicMock() + + obj = MagicMock( + _BaseImopayObj__get_fields=MagicMock(return_value={"bar": mocked_field}) + ) + + with self.assertRaises(AttributeError): + BaseImopayObj._BaseImopayObj__get_field(obj, field_name) + + def test_is_field_optional_1(self): + """ + Dado: + - um campo qualquer mocked_field optional=True + Quando: + - quando for chamado BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) # noqa + Então: + - o resultado deve ser True + """ + mocked_field = MagicMock(optional=True) + + expected = True + + result = BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) + + self.assertEqual(result, expected) + + def test_is_field_optional_2(self): + """ + Dado: + - um campo qualquer mocked_field optional=False + Quando: + - quando for chamado BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) # noqa + Então: + - o resultado deve ser False + """ + mocked_field = MagicMock(optional=False) + + expected = False + + result = BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) + + self.assertEqual(result, expected) + + def test_is_field_optional_3(self): + """ + Dado: + - um campo qualquer mocked_field que não tenha o atributo optional + Quando: + - quando for chamado BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) # noqa + Então: + - o resultado deve ser False + """ + expected = False + + mocked_field = "" + + result = BaseImopayObj._BaseImopayObj__is_field_optional(mocked_field) + + self.assertEqual(result, expected) + def test_run_validators_1(self): """ Dado: - um objeto obj BaseImopayObj - um método qualquer mocked_validator_method + com __name__="_validate_foo" - BaseImopayObj._BaseImopayObj__get_validation_methods retornando [mocked_validator_method] + - existe o campo 'foo' em obj com optional=False Quando: - for chamado obj._BaseImopayObj__run_validators() Então: - mocked_validator_method deve ter sido chamado uma vez + - BaseImopayObj._BaseImopayObj__get_field('foo') deve + ter sido chamado uma vez """ obj = BaseImopayObj() - mocked_validator_method = MagicMock() + mocked_validator_method = MagicMock(__name__="_validate_foo") with patch( "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_validation_methods" - ) as mocked_get_validation_methods: + ) as mocked_get_validation_methods, patch( + "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_field" + ) as mocked_get_field: + mocked_get_field.return_value = MagicMock(optional=False) + mocked_get_validation_methods.return_value = [mocked_validator_method] obj._BaseImopayObj__run_validators() mocked_validator_method.assert_called_once_with() + mocked_get_field.assert_called_once_with("foo") def test_run_validators_2(self): """ Dado: - um objeto obj BaseImopayObj - um erro er1 FieldError("foo", "bar") - - um método qualquer mocked_validator_method que levante o error + - um método qualquer mocked_validator_method que levante o error e + com __name__="_validate_foo" - BaseImopayObj._BaseImopayObj__get_validation_methods retornando [mocked_validator_method] + - existe o campo 'foo' em obj com optional=False Quando: - for chamado obj._BaseImopayObj__run_validators() Então: + - mocked_validator_method deve ter sido chamado uma vez + - BaseImopayObj._BaseImopayObj__get_field('foo') deve + ter sido chamado uma vez - deve ser lançado um erro er2 ValidationError - o er1 deve estar presente na lista de erros de er2 """ @@ -181,20 +321,112 @@ def test_run_validators_2(self): er1 = FieldError("foo", "bar") - mocked_validator_method = MagicMock(side_effect=er1) + mocked_validator_method = MagicMock(side_effect=er1, __name__="_validate_foo") with patch( "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_validation_methods" - ) as mocked_get_validation_methods: + ) as mocked_get_validation_methods, patch( + "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_field" + ) as mocked_get_field: + mocked_get_field.return_value = MagicMock(optional=False) + mocked_get_validation_methods.return_value = [mocked_validator_method] with self.assertRaises(ValidationError) as ctx: obj._BaseImopayObj__run_validators() mocked_validator_method.assert_called_once_with() + mocked_get_field.assert_called_once_with("foo") er2 = ctx.exception self.assertEqual(len(er2.errors), 1) self.assertEqual(er2.errors[0], er1) + + def test_run_validators_3(self): + """ + Dado: + - um objeto obj BaseImopayObj + - um erro er1 ValueError("bar") + - um método qualquer mocked_validator_method que levante o error e + com __name__="_validate_foo" + - BaseImopayObj._BaseImopayObj__get_validation_methods retornando + [mocked_validator_method] + - existe o campo 'foo' em obj com optional=False + Quando: + - for chamado obj._BaseImopayObj__run_validators() + Então: + - mocked_validator_method deve ter sido chamado uma vez + - BaseImopayObj._BaseImopayObj__get_field('foo') deve + ter sido chamado uma vez + - deve ser lançado um erro er2 ValidationError + - o erro presente na lista de erros de er2 deve estar correto + """ + obj = BaseImopayObj() + + er1 = ValueError("bar") + + mocked_validator_method = MagicMock(side_effect=er1, __name__="_validate_foo") + + with patch( + "imopay_wrapper.models.base.BaseImopayObj." + "_BaseImopayObj__get_validation_methods" + ) as mocked_get_validation_methods, patch( + "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_field" + ) as mocked_get_field: + mocked_get_field.return_value = MagicMock(optional=False) + + mocked_get_validation_methods.return_value = [mocked_validator_method] + + with self.assertRaises(ValidationError) as ctx: + obj._BaseImopayObj__run_validators() + + mocked_validator_method.assert_called_once_with() + mocked_get_field.assert_called_once_with("foo") + + er2 = ctx.exception + + self.assertEqual(len(er2.errors), 1) + + result_error = er2.errors[0] + self.assertEqual(result_error.name, mocked_validator_method.__name__) + self.assertEqual(result_error.reason, str(er1)) + + def test_run_validators_4(self): + """ + Dado: + - um objeto obj BaseImopayObj + - um erro er1 FieldError("foo", "bar") + - um método qualquer mocked_validator_method que levante o error e + com __name__="_validate_foo" + - BaseImopayObj._BaseImopayObj__get_validation_methods retornando + [mocked_validator_method] + - existe o campo 'foo' em obj com optional=True + Quando: + - for chamado obj._BaseImopayObj__run_validators() + Então: + - mocked_validator_method deve ter sido chamado uma vez + - BaseImopayObj._BaseImopayObj__get_field('foo') deve + ter sido chamado uma vez + """ + obj = BaseImopayObj() + + er1 = FieldError("foo", "bar") + + mocked_validator_method = MagicMock(side_effect=er1, __name__="_validate_foo") + + with patch( + "imopay_wrapper.models.base.BaseImopayObj." + "_BaseImopayObj__get_validation_methods" + ) as mocked_get_validation_methods, patch( + "imopay_wrapper.models.base.BaseImopayObj." "_BaseImopayObj__get_field" + ) as mocked_get_field: + mocked_get_field.return_value = MagicMock(optional=True) + + mocked_get_validation_methods.return_value = [mocked_validator_method] + + obj._BaseImopayObj__run_validators() + + mocked_validator_method.assert_called_once_with() + mocked_get_field.assert_called_once_with("foo") diff --git a/tests/models/transaction/__init__.py b/tests/models/transaction/__init__.py index e69de29..42ae4cd 100644 --- a/tests/models/transaction/__init__.py +++ b/tests/models/transaction/__init__.py @@ -0,0 +1,9 @@ +from .test_invoice import InvoiceTestCase # noqa +from .test_configuration import ( # noqa + BaseConfigurationTestCase, + InterestConfigurationTestCase, + FineConfigurationTestCase, + DiscountConfigurationTestCase, +) +from .test_invoice_configurations import InvoiceConfigurationsTestCase # noqa +from .test_transaction import BaseTransactionTestCase # noqa diff --git a/tests/models/transaction/test_configuration.py b/tests/models/transaction/test_configuration.py index 5200be4..96b8aab 100644 --- a/tests/models/transaction/test_configuration.py +++ b/tests/models/transaction/test_configuration.py @@ -1,14 +1,248 @@ from unittest import TestCase +from unittest.mock import MagicMock, patch -from imopay_wrapper.models.transaction import Configuration +from ...utils import today, yesterday +from imopay_wrapper.models.transaction import ( + BaseConfiguration, + InterestConfiguration, + FineConfiguration, + DiscountConfiguration, +) +from imopay_wrapper.exceptions import FieldError -class ConfigurationTestCase(TestCase): - def test_1(self): - t = Configuration.from_dict( - {"value": 1, "type": "foo", "charge_type": "foo", "days": 0} +class BaseConfigurationTestCase(TestCase): + def test_initial_0(self): + """ + Dado: + - um value válido 1 + - um charge_type válido BaseConfiguration.PERCENTAGE + (BaseConfiguration.VALID_CHARGE_TYPES mocado) + - um data + {"value": 1, "charge_type": BaseConfiguration.PERCENTAGE} + Quando: + - for criado um Configuration t a partir do data + Então: + - t.value tem que ser igual a value + - t.charge_type tem que ser igual a charge_type + """ + value = 1 + charge_type = BaseConfiguration.PERCENTAGE + + data = {"value": value, "charge_type": charge_type} + with patch( + "imopay_wrapper.models.transaction.BaseConfiguration.VALID_CHARGE_TYPES", + {BaseConfiguration.PERCENTAGE}, + ): + t = BaseConfiguration.from_dict(data) + self.assertEqual(t.value, value) + self.assertEqual(t.charge_type, charge_type) + + def test_validate_value_1(self): + """ + Dado: + - uma lista de valores válidos + - uma instância qualquer + Quando: + - for chamado Configuration._validate_value(instance) para cada valor + com o valor estando na instância + Então: + - N/A + """ + valid_values = ["1", 3, "15", "124123", 19247736471] + + instance = MagicMock() + + for value in valid_values: + with self.subTest(value): + instance.value = value + + # noinspection PyCallByClass + BaseConfiguration._validate_value(instance) + + def test_validate_value_2(self): + """ + Dado: + - um mapeamento invalid_values_by_error de vários erros + para listas de valores inválidos + { + FieldError: ['-1', 0], + TypeError: [[], None, {}], + ValueError: ['a'] + } + - uma instância qualquer + Quando: + - for chamado Configuration._validate_value(instance) para cada erro e valor + com o valor estando na instância + Então: + - deve ser lançado o erro específico para cada valor + """ + invalid_values_by_error = { + FieldError: ["-1", 0], + TypeError: [[], None, {}], + ValueError: ["a"], + } + + instance = MagicMock() + + for error, values in invalid_values_by_error.items(): + with self.subTest(values): + for value in values: + with self.subTest(value), self.assertRaises(error): + instance.value = value + + # noinspection PyCallByClass + BaseConfiguration._validate_value(instance) + + def test_validate_charge_value_1(self): + """ + Dado: + - uma coleção de valores válidos + - uma instância qualquer + que tenha o VALID_CHARGE_TYPES + Quando: + - for chamado Configuration._validate_charge_type(instance) para cada valor + com o valor estando na instância + Então: + - N/A + """ + valid_values = { + BaseConfiguration.PERCENTAGE, + BaseConfiguration.DAILY_PERCENTAGE, + } + + instance = MagicMock(VALID_CHARGE_TYPES=valid_values) + + for value in valid_values: + instance.charge_type = value + + # noinspection PyCallByClass + BaseConfiguration._validate_charge_type(instance) + + def test_validate_charge_value_2(self): + """ + Dado: + - uma lista de valores inválidos + - uma instância qualquer + que tenha o VALID_CHARGE_TYPES + Quando: + - for chamado Configuration._validate_charge_type(instance) para cada valor + com o valor estando na instância + Então: + - deve ser lançado um FieldError para cada valor inválido + """ + invalid_values = [ + "foo", + 1, + None, + "a", + "-1", + BaseConfiguration.PERCENTAGE, + BaseConfiguration.FIXED, + BaseConfiguration.DAILY_FIXED, + ] + + instance = MagicMock( + VALID_CHARGE_TYPES={ + BaseConfiguration.DAILY_PERCENTAGE, + BaseConfiguration.MONTHLY_PERCENTAGE, + } ) - self.assertEqual(t.value, 1) - self.assertEqual(t.type, "foo") - self.assertEqual(t.charge_type, "foo") - self.assertEqual(t.days, 0) + + for value in invalid_values: + instance.charge_type = value + + with self.assertRaises(FieldError): + # noinspection PyCallByClass + BaseConfiguration._validate_charge_type(instance) + + +class InterestConfigurationTestCase(TestCase): + def test_valid_charge_types(self): + expected = { + BaseConfiguration.DAILY_FIXED, + BaseConfiguration.DAILY_PERCENTAGE, + BaseConfiguration.MONTHLY_PERCENTAGE, + } + + result = InterestConfiguration.VALID_CHARGE_TYPES + + self.assertEqual(result, expected) + + +class FineConfigurationTestCase(TestCase): + def test_valid_charge_types(self): + expected = { + BaseConfiguration.PERCENTAGE, + BaseConfiguration.FIXED, + } + + result = FineConfiguration.VALID_CHARGE_TYPES + + self.assertEqual(result, expected) + + +class DiscountConfigurationTestCase(TestCase): + def test_valid_charge_types(self): + expected = { + BaseConfiguration.PERCENTAGE, + BaseConfiguration.FIXED, + } + + result = DiscountConfiguration.VALID_CHARGE_TYPES + + self.assertEqual(result, expected) + + def test_validate_date_1(self): + """ + Dado: + - uma lista de valores válidos + - uma instância qualquer + Quando: + - for chamado Configuration._validate_date(instance) para cada valor + com o valor estando na instância + Então: + - N/A + """ + valid_values = [today(), yesterday()] + + instance = MagicMock() + + for value in valid_values: + with self.subTest(value): + instance.date = value + # noinspection PyCallByClass + DiscountConfiguration._validate_date(instance) + + def test_validate_date_2(self): + """ + Dado: + - um mapeamento invalid_values_by_error de vários erros para + listas de valores inválidos + { + FieldError: ['-1', 0], + TypeError: [[], None, {}], + ValueError: ['a'] + } + - uma instância qualquer + Quando: + - for chamado Configuration._validate_value(instance) para cada erro e valor + com o valor estando na instância + Então: + - deve ser lançado o erro específico para cada valor + """ + invalid_values_by_error = { + ValueError: ["-1", "a"], + TypeError: [0, {}, [], None], + } + + instance = MagicMock() + + for error, values in invalid_values_by_error.items(): + with self.subTest(values): + for value in values: + with self.subTest(value), self.assertRaises(error): + instance.date = value + + # noinspection PyCallByClass + DiscountConfiguration._validate_date(instance) diff --git a/tests/models/transaction/test_invoice.py b/tests/models/transaction/test_invoice.py index db4c577..331e66c 100644 --- a/tests/models/transaction/test_invoice.py +++ b/tests/models/transaction/test_invoice.py @@ -1,31 +1,175 @@ from unittest import TestCase +from unittest.mock import MagicMock -from imopay_wrapper.models.transaction import Invoice +from ...utils import today, tomorrow +from imopay_wrapper.models.transaction import Invoice, BaseConfiguration +from imopay_wrapper.exceptions import FieldError class InvoiceTestCase(TestCase): - def test_1(self): - t = Invoice.from_dict( - { - "configurations": { - "fine": { - "value": 1, - "type": "foo", - "charge_type": "foo", - "days": 0, - }, - "interest": { - "value": 1, - "type": "foo", - "charge_type": "foo", - "days": 0, - }, - "discounts": [ - {"value": 1, "type": "foo", "charge_type": "foo", "days": 0} - ], + def test_validate_configurations_1(self): + """ + Dado: + - uma instância qualquer com configurations={} + Quando: + - for chamado Invoice._validate_configurations(instance) + Então: + - N/A + """ + instance = MagicMock(configurations={}) + + Invoice._validate_configurations(instance) + + def test_validate_configurations_2(self): + """ + Dado: + - uma lista de valores inválidos de configurations + Quando: + - for chamado InvoiceTransaction._validate_configurations(instance) + com o payment_method para cada valor inválido + Então: + - deve ser levantado um FieldError para cada valor + """ + invalid_values = ["1", 3, [], None, Invoice] + instance = MagicMock() + + for value in invalid_values: + with self.subTest(value), self.assertRaises(FieldError): + instance.configurations = value + Invoice._validate_configurations(instance) + + def test_validate_expiration_date_1(self): + """ + Dado: + - uma lista de valores válidos + - uma instância qualquer + Quando: + - for chamado Invoice._validate_expiration_date(instance) para cada valor + com o valor estando na instância + Então: + - N/A + """ + valid_values = [today(), tomorrow()] + + instance = MagicMock() + + for value in valid_values: + with self.subTest(value): + instance.expiration_date = value + # noinspection PyCallByClass + Invoice._validate_expiration_date(instance) + + def test_validate_expiration_date_2(self): + """ + Dado: + - um mapeamento invalid_values_by_error de vários erros + para listas de valores inválidos + { + FieldError: ['-1', 0], + TypeError: [[], None, {}], + ValueError: ['a'] + } + - uma instância qualquer + Quando: + - for chamado Invoice._validate_expiration_date(instance) para + cada erro e valor com o valor estando na instância + Então: + - deve ser lançado o erro específico para cada valor + """ + invalid_values_by_error = { + ValueError: ["-1", "a"], + TypeError: [0, {}, [], None], + } + + instance = MagicMock() + + for error, values in invalid_values_by_error.items(): + with self.subTest(values): + for value in values: + with self.subTest(value), self.assertRaises(error): + instance.expiration_date = value + + # noinspection PyCallByClass + Invoice._validate_expiration_date(instance) + + def test_validate_limit_date_1(self): + """ + Dado: + - uma lista de valores válidos + - uma instância qualquer + Quando: + - for chamado Invoice._validate_limit_date(instance) para cada valor + com o valor estando na instância + Então: + - N/A + """ + valid_values = [today(), tomorrow()] + + instance = MagicMock() + + for value in valid_values: + with self.subTest(value): + instance.expiration_date = value + instance.limit_date = value + # noinspection PyCallByClass + Invoice._validate_limit_date(instance) + + def test_validate_limit_date_2(self): + """ + Dado: + - um mapeamento invalid_values_by_error de vários erros para + listas de valores inválidos + { + FieldError: ['-1', 0], + TypeError: [[], None, {}], + ValueError: ['a'] } + - uma instância qualquer + Quando: + - for chamado Invoice._validate_limit_date(instance) para cada erro e valor + com o valor estando na instância + Então: + - deve ser lançado o erro específico para cada valor + """ + invalid_values_by_error = { + ValueError: ["-1", "a"], + TypeError: [0, {}, [], None], + } + + instance = MagicMock() + + for error, values in invalid_values_by_error.items(): + with self.subTest(values): + for value in values: + with self.subTest(value), self.assertRaises(error): + instance.limit_date = value + + # noinspection PyCallByClass + Invoice._validate_limit_date(instance) + + def test_init_nested_fields_1(self): + instance = MagicMock( + configurations={ + "fine": { + "value": 1, + "charge_type": BaseConfiguration.FIXED, + }, + "interest": { + "value": 1, + "charge_type": BaseConfiguration.DAILY_FIXED, + }, + "discounts": [ + { + "value": 1, + "charge_type": BaseConfiguration.FIXED, + "date": "2020-08-28", + } + ], } ) - self.assertEqual(t.configurations.fine.value, 1) - self.assertEqual(t.configurations.interest.value, 1) - self.assertEqual(t.configurations.discounts[0].value, 1) + + Invoice._init_nested_fields(instance) + + self.assertEqual(instance.configurations.fine.value, 1) + self.assertEqual(instance.configurations.interest.value, 1) + self.assertEqual(instance.configurations.discounts[0].value, 1) diff --git a/tests/models/transaction/test_invoice_configurations.py b/tests/models/transaction/test_invoice_configurations.py index 83c0a50..9d84088 100644 --- a/tests/models/transaction/test_invoice_configurations.py +++ b/tests/models/transaction/test_invoice_configurations.py @@ -1,31 +1,31 @@ from unittest import TestCase -from imopay_wrapper.models.transaction import InvoiceConfigurations, Configuration +from imopay_wrapper.models.transaction import InvoiceConfigurations, BaseConfiguration class InvoiceConfigurationsTestCase(TestCase): def test_1(self): t = InvoiceConfigurations.from_dict( - { - "fine": Configuration.from_dict( - {"value": 1, "type": "foo", "charge_type": "foo", "days": 0} - ) - } + {"fine": {"value": 1, "charge_type": BaseConfiguration.PERCENTAGE}} ) self.assertEqual(t.fine.value, 1) def test_2(self): t = InvoiceConfigurations.from_dict( - {"fine": {"value": 1, "type": "foo", "charge_type": "foo", "days": 0}} + {"interest": {"value": 1, "charge_type": BaseConfiguration.DAILY_FIXED}} ) - self.assertEqual(t.fine.value, 1) + self.assertEqual(t.interest.value, 1) def test_3(self): t = InvoiceConfigurations.from_dict( { - "fine": Configuration.from_dict( - {"value": 1, "type": "foo", "charge_type": "foo", "days": 0} - ) + "discounts": [ + { + "value": 1, + "charge_type": BaseConfiguration.FIXED, + "date": "2020-08-28", + } + ] } ) - t.to_dict() + self.assertEqual(t.discounts[0].value, 1) diff --git a/tests/models/transaction/test_invoice_tranction.py b/tests/models/transaction/test_invoice_tranction.py index 9bcc9de..3463804 100644 --- a/tests/models/transaction/test_invoice_tranction.py +++ b/tests/models/transaction/test_invoice_tranction.py @@ -1,46 +1,50 @@ from unittest import TestCase +from unittest.mock import MagicMock -from imopay_wrapper.models.transaction import InvoiceTransaction, BaseTransaction +from ...utils import today, tomorrow +from imopay_wrapper.models.transaction import InvoiceTransaction, Invoice +from imopay_wrapper.exceptions import FieldError class InvoiceTransactionTestCase(TestCase): - def test_0(self): - expected = set(BaseTransaction.get_fields().keys()) + def test_validate_payment_method_1(self): + """ + Dado: + - uma instância qualquer com payment_method={} + Quando: + - for chamado InvoiceTransaction._validate_payment_method(instance) + Então: + - N/A + """ + instance = MagicMock(payment_method={}) - result = set(InvoiceTransaction.get_fields().keys()) + InvoiceTransaction._validate_payment_method(instance) - for item in expected: - with self.subTest(item): - self.assertIn(item, result) + def test_validate_payment_method_2(self): + """ + Dado: + - uma lista de valores inválidos de payment_method + Quando: + - for chamado InvoiceTransaction._validate_payment_method(instance) + com o payment_method para cada valor inválido + Então: + - deve ser levantado um FieldError para cada valor + """ + invalid_values = ["1", 3, [], None, InvoiceTransaction] + instance = MagicMock() - def test_1(self): - t = InvoiceTransaction.from_dict({}) - self.assertEqual(t.payer, None) + for value in invalid_values: + with self.subTest(value), self.assertRaises(FieldError): + instance.payment_method = value + InvoiceTransaction._validate_payment_method(instance) def test_2(self): - t = InvoiceTransaction.from_dict( - { - "payment_method": { - "expiration_date": "1", - "limit_date": "2", - "configurations": { - "fine": { - "value": 1, - "type": "foo", - "charge_type": "foo", - "days": 0, - }, - "interest": { - "value": 1, - "type": "foo", - "charge_type": "foo", - "days": 0, - }, - "discounts": [ - {"value": 1, "type": "foo", "charge_type": "foo", "days": 0} - ], - }, - } - } + instance: InvoiceTransaction = MagicMock( + payment_method={"expiration_date": today(), "limit_date": tomorrow()} ) - self.assertEqual(t.payment_method.configurations.fine.value, 1) + + InvoiceTransaction._init_nested_fields(instance) + + self.assertIsInstance(instance.payment_method, Invoice) + self.assertEqual(instance.payment_method.expiration_date, today()) + self.assertEqual(instance.payment_method.limit_date, tomorrow()) diff --git a/tests/test_regex.py b/tests/test_regex.py new file mode 100644 index 0000000..662f489 --- /dev/null +++ b/tests/test_regex.py @@ -0,0 +1,10 @@ +from unittest import TestCase + + +class RegexTestCase(TestCase): + def test_date_regex(self): + from imopay_wrapper.regex import date_regex + + expected = r"^\d{4}-\d{2}-\d{2}$" + + self.assertEqual(date_regex, expected) diff --git a/tests/utils.py b/tests/utils.py index 528ca92..a2f2f2d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,7 +1,39 @@ +from datetime import date, timedelta from unittest import TestCase from unittest.mock import patch, PropertyMock +def today(): + """ + Retorna a data de hoje no formado YYYY-mm-dd. + + https://strftime.org/ + """ + return date.today().strftime("%Y-%m-%d") + + +def tomorrow(): + """ + Retorna a data de amanhã no formado YYYY-mm-dd. + + https://strftime.org/ + """ + d = date.today() + d += timedelta(days=1) + return d.strftime("%Y-%m-%d") + + +def yesterday(): + """ + Retorna a data de ontem no formado YYYY-mm-dd. + + https://strftime.org/ + """ + d = date.today() + d -= timedelta(days=1) + return d.strftime("%Y-%m-%d") + + class LocalImopayTestCase(TestCase): def setUp(self): self.base_url_patcher = patch( diff --git a/tests/validators/__init__.py b/tests/validators/__init__.py index 2033bb6..7292965 100644 --- a/tests/validators/__init__.py +++ b/tests/validators/__init__.py @@ -3,3 +3,6 @@ from .test_validate_obj_attr_in_collection import ( # noqa ValidateObjAttrInCollectionTestCase, ) +from .test_validate_obj_attr_regex import ValidateObjAttrRegexTestCase # noqa +from .test_validate_date_isoformat import ValidateDateIsoformatTestCase # noqa +from .test_validate_date1_gt_date_2 import ValidateDate1gtDate2TestCase # noqa diff --git a/tests/validators/test_validate_date1_gt_date_2.py b/tests/validators/test_validate_date1_gt_date_2.py new file mode 100644 index 0000000..3f85ce6 --- /dev/null +++ b/tests/validators/test_validate_date1_gt_date_2.py @@ -0,0 +1,104 @@ +from unittest import TestCase + +from imopay_wrapper.validators import validate_date_1_gt_date_2 +from imopay_wrapper.exceptions import FieldError + + +class ValidateDate1gtDate2TestCase(TestCase): + def test_1(self): + """ + Dado: + - uma data válida d1 "2020-08-29" + - uma data válida d2 "2020-08-28" + - um nome de atributo qualquer "foo" + Quando: + - for chamado validate_date_1_gt_date_2("foo", d1, d2) + Então: + - N/A + """ + d1 = "2020-08-29" + d2 = "2020-08-28" + validate_date_1_gt_date_2("foo", d1, d2) + + def test_2(self): + """ + Dado: + - uma data válida d1 "2020-08-28" + - uma data válida d2 "2020-08-29" + - um nome de atributo qualquer "foo" + Quando: + - for chamado validate_date_1_gt_date_2("foo", d1, d2) + Então: + - deve ser lançado um FieldError + - o texto do erro deve ser correto + """ + d1 = "2020-08-28" + d2 = "2020-08-29" + + with self.assertRaises(FieldError) as ctx: + validate_date_1_gt_date_2("foo", d1, d2) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn( + f"{d1} não é estritamente maior do que {d2}", ctx.exception.reason + ) + + def test_3(self): + """ + Dado: + - uma data válida d1 "2020-08-28" + - uma data válida d2 "2020-08-29" + - um nome de atributo qualquer "foo" + Quando: + - for chamado validate_date_1_gt_date_2("foo", d1, d2, allow_equal=True) + Então: + - deve ser lançado um FieldError + - o texto do erro deve ser correto + """ + d1 = "2020-08-28" + d2 = "2020-08-29" + + with self.assertRaises(FieldError) as ctx: + validate_date_1_gt_date_2("foo", d1, d2, allow_equal=True) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn(f"{d1} não é igual ou maior do que {d2}", ctx.exception.reason) + + def test_4(self): + """ + Dado: + - uma data válida d1 "2020-08-28" + - uma data válida d2 "2020-08-28" + - um nome de atributo qualquer "foo" + Quando: + - for chamado validate_date_1_gt_date_2("foo", d1, d2) + Então: + - deve ser lançado um FieldError + - o texto do erro deve ser correto + """ + d1 = "2020-08-28" + d2 = "2020-08-28" + + with self.assertRaises(FieldError) as ctx: + validate_date_1_gt_date_2("foo", d1, d2) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn( + f"{d1} não é estritamente maior do que {d2}", ctx.exception.reason + ) + + def test_5(self): + """ + Dado: + - uma data válida d1 "2020-08-28" + - uma data válida d2 "2020-08-28" + - um nome de atributo qualquer "foo" + Quando: + - for chamado validate_date_1_gt_date_2("foo", d1, d2, allow_equal=True) + Então: + - N/A + """ + d1 = "2020-08-28" + d2 = "2020-08-28" + + validate_date_1_gt_date_2("foo", d1, d2, allow_equal=True) diff --git a/tests/validators/test_validate_date_isoformat.py b/tests/validators/test_validate_date_isoformat.py new file mode 100644 index 0000000..250f5c7 --- /dev/null +++ b/tests/validators/test_validate_date_isoformat.py @@ -0,0 +1,185 @@ +from unittest import TestCase +from unittest.mock import MagicMock + +from ..utils import today, tomorrow, yesterday + +from imopay_wrapper.validators import validate_date_isoformat +from imopay_wrapper.exceptions import FieldError + + +class ValidateDateIsoformatTestCase(TestCase): + def test_1(self): + """ + Dado: + - um objeto obj qualquer que tenha foo="2020-08-28" + Quando: + - for chamado validate_date_isoformat(obj, "foo") + Então: + - N/A + """ + obj = MagicMock(foo="2020-08-28") + + validate_date_isoformat(obj, "foo") + + def test_2(self): + """ + Dado: + - um mapeamento de erros para uma lista de valores inválidos + invalid_mapping_error_values = { + ValueError: ["28/08/2020", "abc"], + TypeError: [1, None, {}] + } + - um objeto qualquer + Quando: + - for chamado validate_date_isoformat(obj, "foo") para cada erro e valor + com o valor estando na instância + Então: + - deve ser lançado o erro específico para cada valor + """ + invalid_mapping_error_values = { + ValueError: ["28/08/2020", "abc"], + TypeError: [1, None, {}], + } + + obj = MagicMock() + + for error, values in invalid_mapping_error_values.items(): + for value in values: + obj.foo = value + with self.subTest(value), self.assertRaises(error): + validate_date_isoformat(obj, "foo") + + def test_3(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de amanhã + Quando: + - for chamado validate_date_isoformat(obj, "foo", future=True) + Então: + - N/A + """ + obj = MagicMock(foo=tomorrow()) + + validate_date_isoformat(obj, "foo", future=True) + + def test_4(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de hoje + Quando: + - for chamado validate_date_isoformat(obj, "foo", future=True, allow_today=True) # noqa + Então: + - N/A + """ + obj = MagicMock(foo=today()) + + validate_date_isoformat(obj, "foo", future=True, allow_today=True) + + def test_5(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de hoje + Quando: + - for chamado validate_date_isoformat(obj, "foo", future=True, allow_today=False) # noqa + Então: + - deve ser lançado um FieldError + - o texto do erro deve ser correto + """ + obj = MagicMock(foo=today()) + + with self.assertRaises(FieldError) as ctx: + validate_date_isoformat(obj, "foo", future=True, allow_today=False) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn(f"{obj.foo} não é uma data do futuro!", ctx.exception.reason) + + def test_6(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de ontem + Quando: + - for chamado validate_date_isoformat(obj, "foo", future=True) + Então: + - deve ser lançado um FieldError + - o texto da exceção deve ser correto + """ + obj = MagicMock(foo=yesterday()) + + with self.assertRaises(FieldError) as ctx: + validate_date_isoformat(obj, "foo", future=True) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn(f"{obj.foo} não é uma data do futuro!", ctx.exception.reason) + + def test_7(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de ontem + Quando: + - for chamado validate_date_isoformat(obj, "foo", past=True) + Então: + - N/A + """ + obj = MagicMock(foo=yesterday()) + + validate_date_isoformat(obj, "foo", past=True) + + def test_8(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de hoje + Quando: + - for chamado validate_date_isoformat(obj, "foo", past=True, allow_today=True) # noqa + Então: + - N/A + """ + obj = MagicMock(foo=today()) + + validate_date_isoformat(obj, "foo", past=True, allow_today=True) + + def test_9(self): + """ + Dado: + - um objeto obj qualquer que tenha foo com a data de hoje + Quando: + - for chamado validate_date_isoformat(obj, "foo", past=True, allow_today=False) # noqa + Então: + - deve ser lançado um FieldError + - o texto da exceção deve ser correto + """ + obj = MagicMock(foo=today()) + + with self.assertRaises(FieldError) as ctx: + validate_date_isoformat(obj, "foo", past=True, allow_today=False) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn(f"{obj.foo} não é uma data do passado!", ctx.exception.reason) + + def test_10(self): + """ + Dado: + - uma data de amanhã + - um objeto obj qualquer que tenha foo com a data de amanhã + Quando: + - for chamado validate_date_isoformat(obj, "foo", past=True) + Então: + - deve ser lançado um FieldError + - o texto da exceção deve ser correto + """ + obj = MagicMock(foo=tomorrow()) + + with self.assertRaises(FieldError) as ctx: + validate_date_isoformat(obj, "foo", past=True) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn(f"{obj.foo} não é uma data do passado!", ctx.exception.reason) + + def test_11(self): + + with self.assertRaises(ValueError) as ctx: + validate_date_isoformat("", "", future=True, past=True) + + self.assertIn( + "Não se pode verificar se é uma data futura e passada ao mesmo tempo!", + str(ctx.exception), + ) diff --git a/tests/validators/test_validate_obj_attr_in_collection.py b/tests/validators/test_validate_obj_attr_in_collection.py index 64e8875..a33f91e 100644 --- a/tests/validators/test_validate_obj_attr_in_collection.py +++ b/tests/validators/test_validate_obj_attr_in_collection.py @@ -23,22 +23,6 @@ def test_1(self): validate_obj_attr_in_collection(obj, "foo", collection) def test_2(self): - """ - Dado: - - um objeto obj qualquer que não tenha foo="bar" - - uma coleção collection ["bar"] - Quando: - - for chamado validate_obj_attr_in_collection(obj, "foo", collection, value="bar") # noqa - Então: - - N/A - """ - obj = MagicMock() - - collection = ["bar"] - - validate_obj_attr_in_collection(obj, "foo", collection, value="bar") - - def test_3(self): """ Dado: - um objeto obj qualquer que tenha foo="bar" @@ -59,25 +43,3 @@ def test_3(self): self.assertEqual(ctx.exception.name, "foo") self.assertIn("bar não está na coleção", ctx.exception.reason) self.assertIn(str(collection), ctx.exception.reason) - - def test_4(self): - """ - Dado: - - um objeto obj qualquer que não tenha foo="bar" - - uma coleção collection [1] - Quando: - - for chamado validate_obj_attr_in_collection(obj, "foo", collection, value="bar") # noqa - Então: - - deve ser lançado um FieldError - - o texto do erro lançado deve estar correto! - """ - obj = MagicMock() - - collection = [1] - - with self.assertRaises(FieldError) as ctx: - validate_obj_attr_in_collection(obj, "foo", collection, value="bar") - - self.assertEqual(ctx.exception.name, "foo") - self.assertIn("bar não está na coleção", ctx.exception.reason) - self.assertIn(str(collection), ctx.exception.reason) diff --git a/tests/validators/test_validate_obj_attr_regex.py b/tests/validators/test_validate_obj_attr_regex.py new file mode 100644 index 0000000..39e64dd --- /dev/null +++ b/tests/validators/test_validate_obj_attr_regex.py @@ -0,0 +1,42 @@ +from unittest import TestCase +from unittest.mock import MagicMock + +from imopay_wrapper.validators import validate_obj_attr_regex +from imopay_wrapper.exceptions import FieldError +from imopay_wrapper.regex import date_regex + + +class ValidateObjAttrRegexTestCase(TestCase): + def test_1(self): + """ + Dado: + - um objeto obj qualquer que tenha foo="2020-08-20" + - um regex de data + Quando: + - for chamado validate_obj_attr_regex(obj, "foo", date_regex) + Então: + - N/A + """ + obj = MagicMock(foo="2020-08-20") + + validate_obj_attr_regex(obj, "foo", date_regex) + + def test_2(self): + """ + Dado: + - um objeto obj qualquer que tenha foo="bar" + - um regex de data + Quando: + - for chamado validate_obj_attr_regex(obj, "foo", date_regex) + Então: + - deve ser lançado um FieldError + - o texto do erro deve estar correto + """ + obj = MagicMock(foo="bar") + + with self.assertRaises(FieldError) as ctx: + validate_obj_attr_regex(obj, "foo", date_regex) + + self.assertEqual(ctx.exception.name, "foo") + self.assertIn("bar não é do formato", ctx.exception.reason) + self.assertIn(date_regex, ctx.exception.reason) diff --git a/tests/validators/test_validate_obj_attr_type.py b/tests/validators/test_validate_obj_attr_type.py index 065f227..4c36cc9 100644 --- a/tests/validators/test_validate_obj_attr_type.py +++ b/tests/validators/test_validate_obj_attr_type.py @@ -20,19 +20,6 @@ def test_1(self): validate_obj_attr_type(obj, "foo", str) def test_2(self): - """ - Dado: - - um objeto obj qualquer que não tenha foo="bar" - Quando: - - for chamado validate_obj_attr_type(obj, "foo", str, value="bar") - Então: - - N/A - """ - obj = MagicMock() - - validate_obj_attr_type(obj, "foo", str, value="bar") - - def test_3(self): """ Dado: - um objeto obj qualquer que tenha foo="bar" @@ -50,22 +37,3 @@ def test_3(self): self.assertEqual(ctx.exception.name, "foo") self.assertIn("bar não é do tipo", ctx.exception.reason) self.assertIn(str(int), ctx.exception.reason) - - def test_4(self): - """ - Dado: - - um objeto obj qualquer que não tenha foo="bar" - Quando: - - for chamado validate_obj_attr_type(obj, "foo", int, value="bar") - Então: - - deve ser lançado um FieldError - - o texto do erro deve estar correto - """ - obj = MagicMock() - - with self.assertRaises(FieldError) as ctx: - validate_obj_attr_type(obj, "foo", int, value="bar") - - self.assertEqual(ctx.exception.name, "foo") - self.assertIn("bar não é do tipo", ctx.exception.reason) - self.assertIn(str(int), ctx.exception.reason) diff --git a/tests/wrapper/test_transaction.py b/tests/wrapper/test_transaction.py index d0e1f55..de87311 100644 --- a/tests/wrapper/test_transaction.py +++ b/tests/wrapper/test_transaction.py @@ -1,7 +1,8 @@ from unittest.mock import patch -from ..utils import LocalImopayTestCase +from ..utils import LocalImopayTestCase, today, tomorrow from imopay_wrapper import ImopayWrapper +from imopay_wrapper.models.transaction import BaseConfiguration class TransactionWrapperTestCase(LocalImopayTestCase): @@ -19,23 +20,23 @@ def test_create_invoice(self): "amount": 4, "description": "5", "payment_method": { - "expiration_date": "6", - "limit_date": "7", + "expiration_date": today(), + "limit_date": tomorrow(), "configurations": { "fine": { - "type": "8", - "charge_type": "9", - "value": "10", - "days": "11", + "value": 1, + "charge_type": BaseConfiguration.FIXED, }, "interest": { - "type": "12", - "charge_type": "13", - "value": "14", - "days": "15", + "value": 1, + "charge_type": BaseConfiguration.DAILY_FIXED, }, "discounts": [ - {"type": "16", "charge_type": "17", "value": "18", "days": "19"} + { + "value": 1, + "charge_type": BaseConfiguration.FIXED, + "date": today(), + } ], }, }, @@ -56,27 +57,22 @@ def test_create_invoice(self): "amount": "4", "description": "5", "payment_method": { - "expiration_date": "6", - "limit_date": "7", + "expiration_date": today(), + "limit_date": tomorrow(), "configurations": { "fine": { - "type": "8", - "charge_type": "9", - "value": "10", - "days": "11", + "value": 1, + "charge_type": BaseConfiguration.FIXED, }, "interest": { - "type": "12", - "charge_type": "13", - "value": "14", - "days": "15", + "value": 1, + "charge_type": BaseConfiguration.DAILY_FIXED, }, "discounts": [ { - "type": "16", - "charge_type": "17", - "value": "18", - "days": "19", + "value": 1, + "charge_type": BaseConfiguration.FIXED, + "date": today(), } ], },