def write_authorized_transactions(self) -> None:
        """
        Método responsável por validar se existe transações não aprovadas antes de abrir e efetuar a escrita no arquivo.
        :return: Não possui
        """
        unaunthorized_transactions = self._transaction_facade.unauthorized_transactions()

        if not unaunthorized_transactions:
            self._logger.info(_('logging.no_transactions_to_write'))
            return

        with open(self.__file_path, 'w') as file:
            self.__write_file(file, unaunthorized_transactions)

        self._logger.info(_('logging.transactions_file_write_success'))
コード例 #2
0
ファイル: base.py プロジェクト: yantonn/transaction-challenge
    def load_data(self, file_name: str = get_file_operations_name()) -> Tuple[List[dict], List[str]]:
        """
        Objetivo do método é efetuar iteração sobre as linhas do arquivo de importação, criando objeto conforme o modelo
        definido pelos fields

        :param file_name: Caminho do arquivo
        :return: Lista de dicionários representando o modelo definido pelos fields
        """
        record_success = []
        record_errors = []

        file_path = f'{get_read_operations_path()}/{file_name}'

        with open(file_path, 'r') as file_data:
            lines = file_data.readlines()

            for line in lines:
                try:
                    data = json.loads(line)

                    for field, value in zip(self._fields.keys(), data.values):
                        data[field] = self._fields[field](value) if value else None

                        data = self.get_default_fields(data)

                        record_success.append(data)

                except ValueError:
                    self.__logger.error(_('logging.loader_field_value_error'), exc_info=True)
                    record_errors.append(line)

            return record_success, record_errors
コード例 #3
0
    def test_authorize_transaction_when_payload_error_then_do_nothing_and_set_unauthorized_transaction(
            self, service_mock, schema_mock: dict):
        # arrange
        first_transaction_index = 0
        loaded = mock.MagicMock()
        loaded.errors = 'teste'

        schema_mock.load.return_value = loaded

        service_mock.do_transaction.return_value = True

        # act
        self._transaction_facade.authorize_transaction(self.payload)

        # assert
        schema_mock.load.assert_called_once_with(self.payload, many=False)
        service_mock.do_transaction.assert_not_called()

        transaction_payload = self._transaction_facade.unauthorized_transactions(
        )[first_transaction_index]

        self.assertEqual(transaction_payload['violations'][0],
                         _('messages.payload_error'))
        self.assertEqual(
            len(self._transaction_facade.unauthorized_transactions()), 1)
コード例 #4
0
 def __validate_transaction_low_score(transaction: Transaction) -> None:
     """
     Método responsável por verificar se uma transação possui um score menor que o mínimo permitido
     :param transaction: Instância de um objeto dataclass com representando uma entidade "transaction"
     :return: Opcional exception "BusinessException" com uma mensagem padrão de erro.
     """
     if transaction.score < MININUM_SCORE_VALUE_PERMITED:
         raise BusinessException(_('messages.low_score'))
コード例 #5
0
 def __validate_transaction_installments(transaction: Transaction) -> None:
     """
     Método responsável por verificar se uma transação possui o mínimo de prestações permitidas
     :param transaction: Instância de um objeto dataclass com representando uma entidade "transaction"
     :return: Opcional exception "BusinessException" com uma mensagem padrão de erro.
     """
     if transaction.installments < MINIMUM_INSTALLMENTS_PERMITED:
         raise BusinessException(_('messages.minimum_installments'))
コード例 #6
0
    def load_data(
        self,
        file_name: str = f'{get_file_operations_name()}.{get_type_file_loader()}'
    ) -> Tuple[List[dict], List[dict]]:
        """
        Objetivo do método é efetuar iteração sobre as linhas do arquivo de importação, criando objeto conforme o modelo
        definido pelos fields

        :param file_name: Caminho do arquivo
        :return: Lista de dicionários representando o modelo definido pelos fields
        """
        record_success = []
        record_errors = []

        file_path = f'{get_read_operations_path()}/{file_name}'

        with open(file_path, 'r') as file_data:
            lines = file_data.readlines()

            for line in lines:
                try:
                    data = json.loads(line)['transaction']

                    for field, value in zip(self._fields.keys(),
                                            data.values()):
                        data[field] = self._fields[field](
                            value) if value is not None else None

                        data = self.get_default_fields(data)

                    record_success.append(data)

                except ValueError as ex:
                    self.__logger.error(_('logging.line_processed_with_error',
                                          line=line),
                                        exc_info=True)

                    record_errors.append({
                        'line_error':
                        line,
                        'validations':
                        [_('messages.line_processed_with_error', line=line)],
                    })

            return record_success, record_errors
コード例 #7
0
    def test_do_transaction_when_has_low_score_then_then_raise_exception(self):
        # arrange
        self.transaction.score = 100
        self.transaction.income = 20000

        # act / assert
        with self.assertRaises(BusinessException) as context:
            self._transaction_service.do_transaction(self.transaction)

        self.assertEqual(_('messages.low_score'), str(context.exception))
コード例 #8
0
    def __before_do_transaction(self, payload: dict) -> Optional[Transaction]:
        """
        Metodo responsável por validar se o payload de uma transação não possui alguma inconsistência a nivel de contrato.
        :param payload: contrato de uma transação
        :return: Retorna uma transação no formato de instância de uma dataclass transaction quando o payload não possui erros.
                 Caso contrário retorna None e adiciona o payload na lista de transações não autorizadas.
        """
        loaded = self._transaction_schema.load(payload, many=False)

        if loaded.errors:
            payload_error = self.__build_payload_violation_error(
                payload, _('messages.payload_error'))

            self.__unauthorized_transactions.append(payload_error)

            self._logger.error(_('logging.payload_error', payload=payload))

            return

        return loaded.data
コード例 #9
0
    def test_do_transaction_when_installments_value_excedeed_minimum_income_value_then_raise_exception(
            self):
        # arrange
        self.transaction.income = 600

        # act / assert
        with self.assertRaises(BusinessException) as context:
            self._transaction_service.do_transaction(self.transaction)

        self.assertEqual(_('messages.compromised_income'),
                         str(context.exception))
コード例 #10
0
    def __validate_committed_transaction_income(
            transaction: Transaction) -> Optional:
        """
        Método responsável por validar se o valor da parcela de uma operação de crédito
        compromete mais que 30% do cliente requerinte do parcelamento
        :param transaction:
        :return: Opcional exception "BusinessException" com uma mensagem padrão de erro.
        """
        minimum_income_committed = transaction.income * MINIMUM_COMMITED_VALUE_PERCENTAGE

        if transaction.installment_value > minimum_income_committed:
            raise BusinessException(_('messages.compromised_income'))
コード例 #11
0
    def test_do_transaction_when_transaction_installments_low_then_minimum_then_raise_exception(
            self):
        # arrange
        self.transaction.score = 900
        self.transaction.installments = 2
        self.transaction.requested_value = 1000
        self.transaction.income = 30000

        # act / assert
        with self.assertRaises(BusinessException) as context:
            self._transaction_service.do_transaction(self.transaction)

        self.assertEqual(_('messages.minimum_installments'),
                         str(context.exception))
コード例 #12
0
    def __validate_transaction_time(self,
                                    transaction: Transaction) -> Optional:
        """
        Método responsável por efetuar validações paralelas com transações já efetuadas
        anteriormente efetuando validações em cima de tempo e dia e se já foi processado ao menos uma transação.
        :param transaction: Opcional exception "BusinessException" com uma mensagem padrão de erro.
        :return:
        """
        if not self.__last_transaction_approved:
            return

        if not self.__is_same_date_transaction(transaction.time):
            return

        if self.__is_transaction_interval_less_than_the_permitted(
                transaction.time):
            raise BusinessException(_('messages.doubled_transactions'))
コード例 #13
0
    def __prepare_unauthorized_transaction(
            self,
            transaction: Transaction,
            message_exception: str = None) -> None:
        """
        Método responsável por preparar e adicionar uma transação não autorizada com um motivo pelo qual não foi autorizada
        :param transaction: Instância do objeto dataclass representando uma transação
        :param message_exception: mensagme pelo qual a transação não foi autorizada
        :return: Não possui, apenas adiciona a transação a lista de transações não autorizadas.
        """
        data = self._transaction_schema.dump(transaction).data

        payload_error = self.__build_payload_violation_error(
            data, message_exception)

        self.__unauthorized_transactions.append(payload_error)

        self._logger.error(
            _('logging.transaction_unauthorized',
              transaction=transaction,
              motive=message_exception))
コード例 #14
0
    def test_do_transaction_when_transaction_interval_is_less_than_permitted_then_raise_exception(
            self):

        # arrange
        self.transaction_aux = self._transaction_fake.get_fake_transaction()
        self.transaction_aux.time += timedelta(hours=0, minutes=0, seconds=0)

        self.transaction.score = 900
        self.transaction.installments = 7
        self.transaction.income = 30000
        self.transaction.time = self.transaction_aux.time

        self.transaction.time += timedelta(minutes=1, seconds=57)

        self._transaction_service._TransactionService__last_transaction_approved = self.transaction_aux

        # act / assert
        with self.assertRaises(BusinessException) as context:
            self._transaction_service.do_transaction(self.transaction)

        self.assertEqual(_('messages.doubled_transactions'),
                         str(context.exception))
コード例 #15
0
ファイル: main.py プロジェクト: yantonn/transaction-challenge
def run():
    configure_logging()

    logger = logging.getLogger(__name__)
    logger.info(_('logging.app_starting'))

    program_interface = inject.instance(ProgramInterface)
    transaction_facade = inject.instance(TransactionFacade)
    authorized_transaction_file_builder = inject.instance(
        AuthorizedTransactionFileBuilder)

    option = program_interface.get_options()

    if option == STOP_INTERFACE:
        exit()

    success_loaded_operations, error_loaded_operations = program_interface.load_operations_from_file(
    )

    for transaction in success_loaded_operations:
        transaction_facade.authorize_transaction(transaction)

    authorized_transaction_file_builder.write_authorized_transactions()
コード例 #16
0
    def authorize_transaction(self, payload: dict) -> Optional[dict]:
        """
        Autoriza uma transação efetuando algumas validações permitentes ao negócio
        verificando se o payload enviado não possui alguma inconsistência

        :param payload: Contrato para efetuar uma transação
        :return: Retorna a transação no formato de dicionário caso tenha ocorrido sem errors.
                 Caso contrário registra a transação na lista de transações não autorizadas.
        """
        transaction = self.__before_do_transaction(payload)

        if not transaction:
            return

        try:
            self._transaction_service.do_transaction(transaction)

            self._logger.info(
                _('logging.transaction_authorired', transaction=transaction))

            return self._transaction_schema.dump(transaction)

        except BusinessException as ex:
            self.__prepare_unauthorized_transaction(transaction, str(ex))
    def test_authorize_transaction_with_integrated_test_then_validate_mock_result_file(
            self, json_method_mock):
        # arrange
        first_transaction_index = 0
        second_transaction_index = 1
        third_transaction_index = 2
        four_transaction_index = 3

        first_violation_index = 0

        record_data_success, record_data_error = self._file_loader.load_data()

        # act
        with mock.patch('builtins.open', mock.mock_open()) as context:
            file = context()

            for payload in record_data_success:
                self._transaction_facade.authorize_transaction(payload)

            self._authorized_transaction_file_builder.write_authorized_transactions(
            )

            unauthorized_transactions = self._transaction_facade.unauthorized_transactions(
            )

        # assert
        self.assertEqual(len(unauthorized_transactions), 4)

        self.assertEqual(
            unauthorized_transactions[first_transaction_index]['violations']
            [first_violation_index], _('messages.low_score'))

        self.assertEqual(
            unauthorized_transactions[second_transaction_index]['violations']
            [first_violation_index], _('messages.compromised_income'))

        self.assertEqual(
            unauthorized_transactions[third_transaction_index]['violations']
            [first_violation_index], _('messages.minimum_installments'))

        self.assertEqual(
            unauthorized_transactions[four_transaction_index]['violations']
            [first_violation_index], _('messages.doubled_transactions'))

        self.assertEqual(
            json_method_mock.dumps.call_args_list[first_transaction_index],
            mock.call(unauthorized_transactions[first_transaction_index]))

        self.assertEqual(
            json_method_mock.dumps.call_args_list[second_transaction_index],
            mock.call(unauthorized_transactions[second_transaction_index]))

        self.assertEqual(
            json_method_mock.dumps.call_args_list[third_transaction_index],
            mock.call(unauthorized_transactions[third_transaction_index]))

        self.assertEqual(
            json_method_mock.dumps.call_args_list[four_transaction_index],
            mock.call(unauthorized_transactions[four_transaction_index]))

        file.write.assert_called()