Beispiel #1
0
 def setUp(self):
     account = BankAccount(account_number='1234567890/2010', currency='CZK')
     account.save()
     self.account = account
     self.log_handler = LogCapture(
         'django_pain.management.commands.download_payments',
         propagate=False)
Beispiel #2
0
 def setUp(self):
     self.account = BankAccount(account_number='123456/7890',
                                currency='CZK')
     self.account.save()
     self.payment = get_payment(identifier='PAYMENT_1',
                                account=self.account,
                                state=PaymentState.IMPORTED)
     self.payment.save()
Beispiel #3
0
class TestProcessPayments(TestCase):
    """Test process_payments command."""
    def setUp(self):
        self.account = BankAccount(account_number='123456/7890',
                                   currency='CZK')
        self.account.save()
        self.payment = get_payment(identifier='PAYMENT_1',
                                   account=self.account,
                                   state=PaymentState.IMPORTED)
        self.payment.save()

    @override_settings(PAIN_PROCESSORS=[
        'django_pain.tests.commands.test_process_payments.DummyTruePaymentsProcessor'
    ])
    def test_payments_processed(self):
        """Test processed payments."""
        call_command('process_payments')

        self.assertQuerysetEqual(BankPayment.objects.values_list(
            'identifier', 'account',
            'state'), [('PAYMENT_1', self.account.pk, PaymentState.PROCESSED)],
                                 transform=tuple,
                                 ordered=False)

    @override_settings(PAIN_PROCESSORS=[
        'django_pain.tests.commands.test_process_payments.DummyFalsePaymentsProcessor'
    ])
    def test_payments_deferred(self):
        """Test deferred payments."""
        call_command('process_payments')

        self.assertQuerysetEqual(BankPayment.objects.values_list(
            'identifier', 'account',
            'state'), [('PAYMENT_1', self.account.pk, PaymentState.DEFERRED)],
                                 transform=tuple,
                                 ordered=False)

    @override_settings(PAIN_PROCESSORS=[
        'django_pain.tests.commands.test_process_payments.DummyFalsePaymentsProcessor',
        'django_pain.tests.commands.test_process_payments.DummyTruePaymentsProcessor'
    ])
    def test_payments_from_to(self):
        """Test processed payments."""
        call_command('process_payments', '--from', '2017-01-01 00:00', '--to',
                     '2017-01-02 00:00')

        print(self.payment.create_time)

        self.assertQuerysetEqual(BankPayment.objects.values_list(
            'identifier', 'account',
            'state'), [('PAYMENT_1', self.account.pk, PaymentState.IMPORTED)],
                                 transform=tuple,
                                 ordered=False)
Beispiel #4
0
 def setUp(self):
     super().setUp()
     self.tempdir = TempDirectory()
     self.account = BankAccount(account_number='123456/7890',
                                currency='CZK')
     self.account.save()
     payment = get_payment(identifier='PAYMENT_1',
                           account=self.account,
                           state=PaymentState.READY_TO_PROCESS)
     payment.save()
     self.log_handler = LogCapture(
         'django_pain.management.commands.process_payments',
         propagate=False)
 def test_constraint_success(self):
     """Test clean method with not violated constraint."""
     account = BankAccount(account_number='123', currency='USD')
     payment = BankPayment(identifier='PAYMENT',
                           account=account,
                           amount=Money('999.00', 'USD'))
     payment.clean()
Beispiel #6
0
    def parse(self, bank_statement: IO[bytes]) -> Iterator[BankPayment]:
        """Parse XML input."""
        parser = etree.XMLParser(resolve_entities=False)
        tree = etree.parse(bank_statement, parser)

        account_number = self.compose_account_number(tree.find('//*/account_number').text,
                                                     tree.find('//*/account_bank_code').text)
        try:
            account = BankAccount.objects.get(account_number=account_number)
        except BankAccount.DoesNotExist:
            raise BankAccount.DoesNotExist('Bank account %s does not exist.' % account_number)

        for item in tree.findall('//*/*/item'):
            attrs = dict((el.tag, el.text) for el in item.iterchildren())

            payment = BankPayment(
                identifier=attrs['ident'],
                account=account,
                transaction_date=datetime.strptime(attrs['date'], '%Y-%m-%d'),
                counter_account_number=self.compose_account_number(attrs['account_number'], attrs['account_bank_code']),
                counter_account_name=none_to_str(attrs['name']),
                amount=Money(attrs['price'], account.currency),
                description=none_to_str(attrs['memo']),
                constant_symbol=none_to_str(attrs['const_symbol']),
                variable_symbol=none_to_str(attrs['var_symbol']),
                specific_symbol=none_to_str(attrs['spec_symbol']),
            )

            yield payment
Beispiel #7
0
def get_account(**kwargs: Any) -> BankAccount:
    """Create bank account object."""
    default = {
        'account_number': '123456/0300',
        'account_name': 'Account',
        'currency': 'CZK',
    }
    default.update(kwargs)
    return BankAccount(**default)
    def test_not_empty(self):
        account = BankAccount(account_number='123456789/0123', currency='CZK')
        account.save()
        payment1 = get_payment(identifier='PAYMENT_1', account=account)
        payment1.save()
        payment2 = get_payment(identifier='PAYMENT_2', account=account)
        payment2.save()

        result = self.client.get(reverse('payments-list'))
        self.assertQuerysetEqual(result.context['payments'].values_list(
            'identifier', 'account', 'counter_account_number',
            'transaction_date'), [
                (payment1.identifier, payment1.account.pk,
                 payment1.counter_account_number, payment1.transaction_date),
                (payment2.identifier, payment2.account.pk,
                 payment2.counter_account_number, payment2.transaction_date),
            ],
                                 transform=tuple,
                                 ordered=False)
Beispiel #9
0
 def setUp(self):
     self.tempdir = TempDirectory()
     self.account_in = BankAccount(account_number='123456/7890',
                                   currency='CZK')
     self.account_ex = BankAccount(account_number='987654/3210',
                                   currency='CZK')
     self.account_in.save()
     self.account_ex.save()
     get_payment(identifier='PAYMENT_1',
                 account=self.account_in,
                 state=PaymentState.READY_TO_PROCESS).save()
     get_payment(identifier='PAYMENT_2',
                 account=self.account_ex,
                 state=PaymentState.READY_TO_PROCESS).save()
     self.log_handler = LogCapture(
         'django_pain.management.commands.process_payments',
         propagate=False)
     # Exception in a threads does not fail the test - wee need to collect it somemehow
     self.errors = Queue()  # type: Queue
 def test_constraint_error(self):
     """Test clean method with violated constraint."""
     account = BankAccount(account_number='123', currency='USD')
     payment = BankPayment(identifier='PAYMENT',
                           account=account,
                           amount=Money('999.00', 'CZK'))
     with self.assertRaises(
             ValidationError,
             msg='Bank payment PAYMENT is in different currency (CZK) '
             'than bank account 123 (USD).'):
         payment.clean()
Beispiel #11
0
 def setUp(self):
     self.request_factory = RequestFactory()
     self.admin = User.objects.create_superuser('admin',
                                                '*****@*****.**',
                                                'password')
     account = BankAccount(account_number='123456/7890', currency='CZK')
     account.save()
     get_payment(identifier='PAYMENT_1',
                 account=account,
                 state=PaymentState.INITIALIZED).save()
     get_payment(identifier='PAYMENT_2',
                 account=account,
                 state=PaymentState.READY_TO_PROCESS).save()
     get_payment(identifier='PAYMENT_3',
                 account=account,
                 state=PaymentState.DEFERRED).save()
     get_payment(identifier='PAYMENT_4',
                 account=account,
                 state=PaymentState.PROCESSED).save()
     get_payment(identifier='PAYMENT_5',
                 account=account,
                 state=PaymentState.CANCELED).save()
Beispiel #12
0
    def test_parse(self):
        account = BankAccount(account_number='123456789/0123', currency='CZK')
        account.save()
        parser = TransprocXMLParser()
        payments = list(parser.parse(BytesIO(self.XML_INPUT)))

        payment = {
            'identifier': '111',
            'account': account,
            'transaction_date': datetime(2012, 12, 20, 0, 0),
            'counter_account_number': '123456777/0123',
            'counter_account_name': 'Company Inc.',
            'amount': Money('1000.00', 'CZK'),
            'description': 'See you later',
            'constant_symbol': '0558',
            'variable_symbol': '11111111',
            'specific_symbol': '',
        }

        self.assertEqual(len(payments), 1)

        for field in payment:
            self.assertEqual(getattr(payments[0], field), payment[field])
Beispiel #13
0
    def test_inclusion_in_payment_processing(self):
        """Test including accounts from payment processing"""
        account2 = BankAccount(account_number='987654/3210', currency='CZK')
        account2.save()
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS).save()
        get_payment(identifier='PAYMENT_3',
                    account=account2,
                    state=PaymentState.READY_TO_PROCESS).save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            out = StringIO()
            err = StringIO()
            call_command('process_payments',
                         '--include-accounts',
                         '123456/7890',
                         stdout=out,
                         stderr=err)

            self.assertEqual(out.getvalue(), '')
            self.assertEqual(err.getvalue(), '')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 2 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )
Beispiel #14
0
    def parse(self, bank_statement: IO[bytes]) -> Iterator[BankPayment]:
        """Parse XML input."""
        parser = etree.XMLParser(resolve_entities=False)
        tree = etree.parse(bank_statement, parser)

        account_number = self.compose_account_number(
            tree.find('//*/account_number').text,
            tree.find('//*/account_bank_code').text)
        try:
            account = BankAccount.objects.get(account_number=account_number)
        except BankAccount.DoesNotExist:
            raise BankAccount.DoesNotExist(
                'Bank account {} does not exist.'.format(account_number))

        for item in tree.findall('//*/*/item'):
            attrs = dict((el.tag, el.text) for el in item.iterchildren())

            if attrs.get('status', '1') == '1' and attrs.get(
                    'code', '1') == '1' and attrs.get('type', '1') == '1':
                # Only import payments with code==1 (normal transaction) and status==1 (realized transfer)
                if SETTINGS.trim_varsym:
                    variable_symbol = none_to_str(
                        attrs['var_symbol']).lstrip('0')
                else:
                    variable_symbol = none_to_str(attrs['var_symbol'])

                payment = BankPayment(
                    identifier=attrs['ident'],
                    account=account,
                    transaction_date=datetime.strptime(attrs['date'],
                                                       '%Y-%m-%d'),
                    counter_account_number=self.compose_account_number(
                        attrs['account_number'], attrs['account_bank_code']),
                    counter_account_name=none_to_str(attrs['name']),
                    amount=Money(attrs['price'], account.currency),
                    description=none_to_str(attrs['memo']),
                    constant_symbol=none_to_str(attrs['const_symbol']),
                    variable_symbol=variable_symbol,
                    specific_symbol=none_to_str(attrs['spec_symbol']),
                )

                yield payment
Beispiel #15
0
 def parse(self, bank_statement):
     raise BankAccount.DoesNotExist('Bank account ACCOUNT does not exist.')
Beispiel #16
0
 def test_trim_varsym(self):
     account = BankAccount(account_number='123456789/0123', currency='CZK')
     account.save()
     parser = TransprocXMLParser()
     payments = list(parser.parse(BytesIO(self.ZERO_IN_VARSYM_XML)))
     self.assertEqual(payments[0].variable_symbol, '700')
Beispiel #17
0
 def setUp(self):
     super().setUp()
     self.account = BankAccount(account_number='123456/7890', currency='CZK')
     self.account.save()
     self.log_handler = LogCapture('django_pain.management.commands.get_card_payments_states', propagate=False)
Beispiel #18
0
class TestProcessPayments(CacheResetMixin, TestCase):
    """Test process_payments command."""
    def setUp(self):
        super().setUp()
        self.tempdir = TempDirectory()
        self.account = BankAccount(account_number='123456/7890',
                                   currency='CZK')
        self.account.save()
        payment = get_payment(identifier='PAYMENT_1',
                              account=self.account,
                              state=PaymentState.READY_TO_PROCESS)
        payment.save()
        self.log_handler = LogCapture(
            'django_pain.management.commands.process_payments',
            propagate=False)

    def tearDown(self):
        self.log_handler.uninstall()
        self.tempdir.cleanup()

    def _test_non_existing_account(self, param_name):
        """
        Test non existing account.

        param_name should contain either '--include-accounts' or '--exclude-accounts'
        """
        BankAccount.objects.create(account_number='987654/3210',
                                   currency='CZK')
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            out = StringIO()
            err = StringIO()
            with self.assertRaises(CommandError):
                call_command('process_payments',
                             param_name,
                             'xxxxxx/xxxx,yyyyyy/yyyy,987654/3210',
                             stdout=out,
                             stderr=err)

            self.assertEqual(out.getvalue(), '')
            self.assertEqual(err.getvalue(), '')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'ERROR',
                 'Following accounts do not exist: xxxxxx/xxxx, yyyyyy/yyyy. Terminating.'
                 ),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_payments_processed(self):
        """Test processed payments."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')

            self.assertQuerysetEqual(BankPayment.objects.values_list(
                'identifier', 'account', 'state', 'processor'),
                                     [('PAYMENT_1', self.account.pk,
                                       PaymentState.PROCESSED, 'dummy')],
                                     transform=tuple,
                                     ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'True objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 1 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyFalsePaymentProcessor'
        })
    def test_payments_deferred(self):
        """Test deferred payments."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')

            self.assertQuerysetEqual(
                BankPayment.objects.values_list('identifier', 'account',
                                                'state', 'processor'),
                [('PAYMENT_1', self.account.pk, PaymentState.DEFERRED, '')],
                transform=tuple,
                ordered=False)
            self.assertEqual(BankPayment.objects.first().objective, '')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 1 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 1 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTrueErrorPaymentProcessor'
        })
    def test_payments_processed_with_error(self):
        """Test processed payments with processing error."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')

            self.assertQuerysetEqual(
                BankPayment.objects.values_list('identifier', 'account',
                                                'state', 'processor',
                                                'processing_error'),
                [('PAYMENT_1', self.account.pk, PaymentState.PROCESSED,
                  'dummy', PaymentProcessingError.DUPLICITY)],
                transform=tuple,
                ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'Dummy objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 1 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyFalseErrorPaymentProcessor'
        })
    def test_payments_deferred_with_error(self):
        """Test deferred payments with processing error."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')

            self.assertQuerysetEqual(
                BankPayment.objects.values_list('identifier', 'account',
                                                'state', 'processor',
                                                'processing_error'),
                [('PAYMENT_1', self.account.pk, PaymentState.DEFERRED, 'dummy',
                  PaymentProcessingError.DUPLICITY)],
                transform=tuple,
                ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'Dummy objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 1 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Saving payment %s as DEFERRED with error PaymentProcessingError.DUPLICITY.'
                 % BankPayment.objects.first().uuid),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(PAIN_PROCESSORS=OrderedDict([
        ('dummy_false',
         'django_pain.tests.commands.test_process_payments.DummyFalsePaymentProcessor'
         ),
        ('dummy_true',
         'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
         ),
    ]))
    def test_payments_from_to(self):
        """Test processed payments."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments', '--from', '2017-01-01 00:00',
                         '--to', '2017-01-02 00:00')

            self.assertQuerysetEqual(BankPayment.objects.values_list(
                'identifier', 'account', 'state', 'processor'),
                                     [('PAYMENT_1', self.account.pk,
                                       PaymentState.READY_TO_PROCESS, '')],
                                     transform=tuple,
                                     ordered=False)
            self.assertEqual(BankPayment.objects.first().objective, '')

    def test_invalid_date_raises_exception(self):
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            with self.assertRaises(CommandError):
                call_command('process_payments', '--from', '2017-01-32 00:00',
                             '--to', '2017-02-01 00:00')
            with self.assertRaises(CommandError):
                call_command('process_payments', '--from', 'not a date',
                             '--to', '2017-02-01 00:00')
            with self.assertRaises(CommandError):
                call_command('process_payments', '--from', '2017-01-01 00:00',
                             '--to', '2017-01-32 00:00')
            with self.assertRaises(CommandError):
                call_command('process_payments', '--from', '2017-01-01 00:00',
                             '--to', 'not a date')

    def test_lock(self):
        """Test process payments lock."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            lock = open(SETTINGS.process_payments_lock_file, 'a')
            fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
            out = StringIO()
            err = StringIO()
            call_command('process_payments',
                         '--no-color',
                         stdout=out,
                         stderr=err)
            self.assertEqual(out.getvalue(), '')
            self.assertEqual(
                err.getvalue(),
                'Command process_payments is already running. Terminating.\n')
            self.assertQuerysetEqual(BankPayment.objects.values_list(
                'identifier', 'account', 'state', 'processor'),
                                     [('PAYMENT_1', self.account.pk,
                                       PaymentState.READY_TO_PROCESS, '')],
                                     transform=tuple,
                                     ordered=False)
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'WARNING',
                 'Command already running. Terminating.'))

    def test_invalid_lock(self):
        """Test process payments with invalid lock file."""
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            os.mkdir(SETTINGS.process_payments_lock_file, mode=0o0)
            out = StringIO()
            err = StringIO()
            with self.assertRaisesRegexp(
                    CommandError,
                    r'^Error occured while opening lockfile .*/test.lock:.*Is a '
                    r'directory: .*\. Terminating\.$'):
                call_command('process_payments',
                             '--no-color',
                             stdout=out,
                             stderr=err)
            self.assertEqual(out.getvalue(), '')
            self.assertEqual(err.getvalue(), '')
            self.assertEqual(len(self.log_handler.actual()), 2)
            self.assertEqual(
                self.log_handler.actual()[0],
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
            )
            self.assertEqual(
                self.log_handler.actual()[1][:2],
                ('django_pain.management.commands.process_payments', 'ERROR'))
            self.assertRegex(
                self.log_handler.actual()[1][2],
                r'^Error occured while opening lockfile .*/test.lock:.*Is a directory.*Terminating\.$'
            )
            os.chmod(SETTINGS.process_payments_lock_file, 0o755)

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_exclusion_in_payment_processing(self):
        """Test excluding accounts from payment processing"""
        account2 = BankAccount(account_number='987654/3210', currency='CZK')
        account2.save()
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS).save()
        get_payment(identifier='PAYMENT_3',
                    account=account2,
                    state=PaymentState.READY_TO_PROCESS).save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            out = StringIO()
            err = StringIO()
            call_command('process_payments',
                         '--exclude-accounts',
                         '987654/3210',
                         stdout=out,
                         stderr=err)

            self.assertEqual(out.getvalue(), '')
            self.assertEqual(err.getvalue(), '')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 2 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_exclusion_of_non_existing_account_in_payment_processing(self):
        """Test excluding non-existing accounts from payment processing"""
        self._test_non_existing_account('--exclude-accounts')

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_inclusion_in_payment_processing(self):
        """Test including accounts from payment processing"""
        account2 = BankAccount(account_number='987654/3210', currency='CZK')
        account2.save()
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS).save()
        get_payment(identifier='PAYMENT_3',
                    account=account2,
                    state=PaymentState.READY_TO_PROCESS).save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            out = StringIO()
            err = StringIO()
            call_command('process_payments',
                         '--include-accounts',
                         '123456/7890',
                         stdout=out,
                         stderr=err)

            self.assertEqual(out.getvalue(), '')
            self.assertEqual(err.getvalue(), '')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 2 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_inclusion_of_non_existing_account_in_payment_processing(self):
        """Test including non-existing accounts from payment processing"""
        self._test_non_existing_account('--include-accounts')

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_card_payments_processed(self):
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy').save()
        get_payment(identifier='PAYMENT_3',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy').save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')
            self.assertQuerysetEqual(BankPayment.objects.values_list(
                'identifier', 'state',
                'processor'), [('PAYMENT_1', PaymentState.PROCESSED, 'dummy'),
                               ('PAYMENT_2', PaymentState.PROCESSED, 'dummy'),
                               ('PAYMENT_3', PaymentState.PROCESSED, 'dummy')],
                                     transform=tuple,
                                     ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'True objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 3 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(PAIN_PROCESSORS=OrderedDict([
        ('dummy',
         'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
         ),
        ('dummy_false',
         'django_pain.tests.commands.test_process_payments.DummyFalsePaymentProcessor'
         ),
    ]))
    def test_card_payments_unprocessed(self):
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy_false',
                    transaction_date=date(2018, 5, 10)).save()
        get_payment(identifier='PAYMENT_3',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy',
                    transaction_date=date(2018, 5, 11)).save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')
            self.assertQuerysetEqual(
                BankPayment.objects.values_list('identifier', 'state',
                                                'processor'),
                [('PAYMENT_1', PaymentState.PROCESSED, 'dummy'),
                 ('PAYMENT_2', PaymentState.DEFERRED, 'dummy_false'),
                 ('PAYMENT_3', PaymentState.PROCESSED, 'dummy')],
                transform=tuple,
                ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'True objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 3 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments with processor dummy_false.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Saving payment %s as DEFERRED with error None.' %
                 BankPayment.objects.get(identifier='PAYMENT_2').uuid),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )

    @override_settings(PAIN_PROCESSORS=OrderedDict([
        ('dummy',
         'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
         ),
        ('dummy_error',
         'django_pain.tests.commands.test_process_payments.DummyFalseErrorPaymentProcessor'
         ),
    ]))
    def test_card_payments_defferred(self):
        get_payment(identifier='PAYMENT_2',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy_error',
                    transaction_date=date(2018, 5, 10)).save()
        get_payment(identifier='PAYMENT_3',
                    account=self.account,
                    state=PaymentState.READY_TO_PROCESS,
                    payment_type=PaymentType.CARD_PAYMENT,
                    counter_account_number='',
                    processor='dummy',
                    transaction_date=date(2018, 5, 11)).save()
        with override_settings(PAIN_PROCESS_PAYMENTS_LOCK_FILE=os.path.join(
                self.tempdir.path, 'test.lock')):
            call_command('process_payments')
            self.assertQuerysetEqual(
                BankPayment.objects.values_list('identifier', 'state',
                                                'processor'),
                [('PAYMENT_1', PaymentState.PROCESSED, 'dummy'),
                 ('PAYMENT_2', PaymentState.DEFERRED, 'dummy_error'),
                 ('PAYMENT_3', PaymentState.PROCESSED, 'dummy')],
                transform=tuple,
                ordered=False)
            self.assertEqual(BankPayment.objects.first().objective,
                             'True objective')
            self.log_handler.check(
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments started.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Lock acquired.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing 3 unprocessed payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing card payments with processor dummy_error.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Saving payment %s as DEFERRED with error PaymentProcessingError.DUPLICITY.'
                 % BankPayment.objects.get(identifier='PAYMENT_2').uuid),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Processing payments with processor dummy.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Marking 0 unprocessed payments as DEFERRED.'),
                ('django_pain.management.commands.process_payments', 'INFO',
                 'Command process_payments finished.'),
            )
Beispiel #19
0
class TestGetPaymentsStates(TestCase):
    """Test get_payments_states command."""

    def setUp(self):
        super().setUp()
        self.account = BankAccount(account_number='123456/7890', currency='CZK')
        self.account.save()
        self.log_handler = LogCapture('django_pain.management.commands.get_card_payments_states', propagate=False)

    def tearDown(self):
        self.log_handler.uninstall()

    def test_no_payments(self):
        call_command('get_card_payments_states')

        self.log_handler.check(
            ('django_pain.management.commands.get_card_payments_states', 'INFO',
             'Command get_card_payments_states started.'),
            ('django_pain.management.commands.get_card_payments_states', 'INFO', 'No payments to update state.')
        )

    def test_normal_run(self):
        payment = get_payment(identifier='PAYMENT_1', account=self.account, state=PaymentState.INITIALIZED,
                              card_handler='dummy')
        payment.save()
        payment2 = get_payment(identifier='PAYMENT_2', account=self.account, state=PaymentState.READY_TO_PROCESS,
                               card_handler='dummy')
        payment2.save()

        call_command('get_card_payments_states')

        self.log_handler.check(
            ('django_pain.management.commands.get_card_payments_states', 'INFO',
             'Command get_card_payments_states started.'),
            ('django_pain.management.commands.get_card_payments_states', 'INFO', 'Getting state of 1 payment(s).'),
        )
        self.assertQuerysetEqual(BankPayment.objects.values_list('identifier', 'state').order_by('identifier'),
                                 [('PAYMENT_1', PaymentState.READY_TO_PROCESS.value),
                                  ('PAYMENT_2', PaymentState.READY_TO_PROCESS.value)],
                                 transform=tuple)

    @override_settings(PAIN_CARD_PAYMENT_HANDLERS={
        'dummy_exc': 'django_pain.tests.utils.DummyCardPaymentHandlerExc'}
    )
    def test_error(self):
        payment = get_payment(identifier='PAYMENT_1', account=self.account, state=PaymentState.INITIALIZED,
                              card_handler='dummy_exc')
        payment.save()

        call_command('get_card_payments_states')
        self.log_handler.check(
            ('django_pain.management.commands.get_card_payments_states', 'INFO',
             'Command get_card_payments_states started.'),
            ('django_pain.management.commands.get_card_payments_states', 'INFO', 'Getting state of 1 payment(s).'),
            ('django_pain.management.commands.get_card_payments_states', 'ERROR',
             'Error while updating state of payment identifier=PAYMENT_1')
        )

    @override_settings(PAIN_CARD_PAYMENT_HANDLERS={
        'dummy_cexc': 'django_pain.tests.utils.DummyCardPaymentHandlerConnExc'}
    )
    def test_connection_error(self):
        payment = get_payment(identifier='PAYMENT_1', account=self.account, state=PaymentState.INITIALIZED,
                              card_handler='dummy_cexc')
        payment.save()

        call_command('get_card_payments_states')
        self.log_handler.check(
            ('django_pain.management.commands.get_card_payments_states', 'INFO',
             'Command get_card_payments_states started.'),
            ('django_pain.management.commands.get_card_payments_states', 'INFO', 'Getting state of 1 payment(s).'),
            ('django_pain.management.commands.get_card_payments_states', 'ERROR',
             'Connection error while updating state of payment identifier=PAYMENT_1')
        )

    def test_payments_from_to(self):
        """Test from/to parameters."""
        payment = get_payment(identifier='PAYMENT_1', account=self.account, state=PaymentState.INITIALIZED,
                              card_handler='dummy')
        payment.save()
        payment2 = get_payment(identifier='PAYMENT_2', account=self.account, state=PaymentState.INITIALIZED,
                               card_handler='dummy', create_time=datetime.date(2010, 1, 1))
        payment2.save()
        payment3 = get_payment(identifier='PAYMENT_3', account=self.account, state=PaymentState.INITIALIZED,
                               card_handler='dummy')
        payment3.save()

        # auto_add fields must be changed after first save:
        payment.create_time = datetime.date(2000, 1, 1)
        payment.save()
        payment2.create_time = datetime.date(2010, 1, 1)
        payment2.save()
        payment3.create_time = datetime.date(2020, 1, 1)
        payment3.save()

        call_command('get_card_payments_states', '--from', '2009-01-01 00:00', '--to', '2017-01-02 00:00')

        self.assertQuerysetEqual(BankPayment.objects.values_list('identifier', 'state').order_by('identifier'),
                                 [('PAYMENT_1', PaymentState.INITIALIZED.value),
                                  ('PAYMENT_2', PaymentState.READY_TO_PROCESS.value),
                                  ('PAYMENT_3', PaymentState.INITIALIZED.value)],
                                 transform=tuple)

    def test_invalid_from_to_raises_exception(self):
        with self.assertRaises(CommandError):
            call_command('get_card_payments_states', '--from', '2009-01-32 00:00', '--to', '2017-02-01 00:00')
        with self.assertRaises(CommandError):
            call_command('get_card_payments_states', '--from', 'not a date', '--to', '2017-01-02 00:00')
        with self.assertRaises(CommandError):
            call_command('get_card_payments_states', '--from', '2009-01-01 00:00', '--to', '2017-01-32 00:00')
        with self.assertRaises(CommandError):
            call_command('get_card_payments_states', '--from', '2009-01-01 00:00', '--to', 'not a date')
Beispiel #20
0
 def setUp(self):
     self.account = BankAccount(account_number='123456/7890', currency='CZK')
     self.account.save()
     self.log_handler = LogCapture('django_pain.management.commands.get_card_payments_states', propagate=False)
     # Exception in a threads does not fail the test - wee need to collect it somemehow
     self.errors = Queue()  # type: Queue
Beispiel #21
0
class TestGetPaymentsStatesLocking(TransactionTestCase):

    def setUp(self):
        self.account = BankAccount(account_number='123456/7890', currency='CZK')
        self.account.save()
        self.log_handler = LogCapture('django_pain.management.commands.get_card_payments_states', propagate=False)
        # Exception in a threads does not fail the test - wee need to collect it somemehow
        self.errors = Queue()  # type: Queue

    def tearDown(self):
        self.log_handler.uninstall()

    def test_processing_does_not_overwrite_locked_rows(self):
        get_payment(identifier='PAYMENT_1', transaction_date=datetime.date(2018, 5, 2), account=self.account,
                    state=PaymentState.INITIALIZED, card_handler='dummy').save()
        get_payment(identifier='PAYMENT_2', transaction_date=datetime.date(2018, 4, 1), account=self.account,
                    state=PaymentState.INITIALIZED, card_handler='dummy').save()

        processing_finished = threading.Event()
        query_finished = threading.Event()

        def target_processing():
            query_finished.wait()
            try:
                call_command('get_card_payments_states')
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                processing_finished.set()
                close_old_connections()

        def target_query():
            try:
                with transaction.atomic():
                    BankPayment.objects.select_for_update().filter(identifier='PAYMENT_1').get()
                    query_finished.set()
                    processing_finished.wait()
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                query_finished.set()
                close_old_connections()

        threads = [threading.Thread(target=target_processing), threading.Thread(target=target_query)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        self.assertTrue(self.errors.empty())
        self.assertQuerysetEqual(BankPayment.objects.values_list('identifier', 'state').order_by('identifier'),
                                 [('PAYMENT_1', PaymentState.INITIALIZED.value),
                                  ('PAYMENT_2', PaymentState.READY_TO_PROCESS.value)],
                                 transform=tuple)

    def test_processed_rows_not_overwritten(self):
        processing_started = threading.Event()
        query_finished = threading.Event()

        p1 = get_payment(identifier='PAYMENT_1', account=self.account, state=PaymentState.INITIALIZED,
                         card_handler='dummy')
        p2 = get_payment(identifier='PAYMENT_2', account=self.account, state=PaymentState.INITIALIZED,
                         card_handler='dummy')
        p1.save()
        p2.save()

        p1.create_time = datetime.datetime(2018, 5, 2)
        p2.create_time = datetime.datetime(2018, 4, 1)
        p1.save()
        p2.save()

        def mock_update_state(payment):
            processing_started.set()
            query_finished.wait()
            payment.state = PaymentState.READY_TO_PROCESS
            payment.save()

        def target_processing():
            try:
                # cache may prevent mocking
                get_card_payment_handler_instance.cache_clear()
                get_card_payment_handler_class.cache_clear()
                with patch('django_pain.tests.utils.DummyCardPaymentHandler') as MockClass:
                    instance = MockClass.return_value
                    instance.update_payments_state = mock_update_state
                    call_command('get_card_payments_states', '--from', datetime.datetime(2018, 5, 1))
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                processing_started.set()
                close_old_connections()
                # mock might be cached
                get_card_payment_handler_instance.cache_clear()
                get_card_payment_handler_class.cache_clear()

        def target_query():
            processing_started.wait()
            try:
                with transaction.atomic():
                    payments = BankPayment.objects.select_for_update(skip_locked=True).all()
                    for p in payments:
                        p.state = PaymentState.PROCESSED
                        p.save()
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                query_finished.set()
                close_old_connections()

        threads = [threading.Thread(target=target_processing), threading.Thread(target=target_query)]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        self.assertTrue(self.errors.empty())
        self.assertQuerysetEqual(BankPayment.objects.values_list('identifier', 'state').order_by('identifier'),
                                 [('PAYMENT_1', PaymentState.READY_TO_PROCESS.value),
                                  ('PAYMENT_2', PaymentState.PROCESSED.value)],
                                 transform=tuple)
Beispiel #22
0
 def setUp(self):
     account = BankAccount(account_number='123456/7890', currency='CZK')
     account.save()
     self.account = account
Beispiel #23
0
class TestProcessPaymentsLocks(CacheResetMixin, TransactionTestCase):
    def setUp(self):
        self.tempdir = TempDirectory()
        self.account_in = BankAccount(account_number='123456/7890',
                                      currency='CZK')
        self.account_ex = BankAccount(account_number='987654/3210',
                                      currency='CZK')
        self.account_in.save()
        self.account_ex.save()
        get_payment(identifier='PAYMENT_1',
                    account=self.account_in,
                    state=PaymentState.READY_TO_PROCESS).save()
        get_payment(identifier='PAYMENT_2',
                    account=self.account_ex,
                    state=PaymentState.READY_TO_PROCESS).save()
        self.log_handler = LogCapture(
            'django_pain.management.commands.process_payments',
            propagate=False)
        # Exception in a threads does not fail the test - wee need to collect it somemehow
        self.errors = Queue()  # type: Queue

    def tearDown(self):
        self.log_handler.uninstall()
        self.tempdir.cleanup()

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_processing_does_not_overwrite_locked_rows(self):
        processing_finished = threading.Event()
        query_finished = threading.Event()

        def target_processing():
            query_finished.wait()
            try:
                call_command('process_payments')
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                processing_finished.set()
                close_old_connections()

        def target_query():
            try:
                with transaction.atomic():
                    payment = BankPayment.objects.select_for_update().filter(
                        identifier='PAYMENT_2').get()
                    payment.processor = 'manual'
                    payment.save()
                    query_finished.set()
                    processing_finished.wait()
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                query_finished.set()
                close_old_connections()

        threads = [
            threading.Thread(target=target_processing),
            threading.Thread(target=target_query)
        ]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        self.assertTrue(self.errors.empty())
        self.assertQuerysetEqual(BankPayment.objects.values_list(
            'identifier', 'account', 'state', 'processor'),
                                 [('PAYMENT_1', self.account_in.pk,
                                   PaymentState.PROCESSED, 'dummy'),
                                  ('PAYMENT_2', self.account_ex.pk,
                                   PaymentState.READY_TO_PROCESS, 'manual')],
                                 transform=tuple,
                                 ordered=False)

    @override_settings(
        PAIN_PROCESSORS={
            'dummy':
            'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
        })
    def test_processed_rows_not_overwritten(self):
        processing_started = threading.Event()
        query_finished = threading.Event()

        def mock_process_payments(payments):
            processing_started.set()
            query_finished.wait()
            return [ProcessPaymentResult(result=True) for p in payments]

        def target_processing():
            try:
                # cache may prevent mocking
                get_processor_instance.cache_clear()
                get_processor_class.cache_clear()
                with patch(
                        'django_pain.tests.commands.test_process_payments.DummyTruePaymentProcessor'
                ) as MockClass:
                    instance = MockClass.return_value
                    instance.process_payments = mock_process_payments
                    call_command('process_payments', '--exclude-accounts',
                                 self.account_ex.account_number)
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                processing_started.set()
                # mock might be cached
                get_processor_instance.cache_clear()
                get_processor_class.cache_clear()
                close_old_connections()

        def target_query():
            processing_started.wait()
            try:
                with transaction.atomic():
                    payments = BankPayment.objects.select_for_update(
                        skip_locked=True).all()
                    for p in payments:
                        p.state = PaymentState.PROCESSED
                        p.processor = 'manual'
                        p.save()
            except Exception as e:  # pragma: no cover
                self.errors.put(e)
                raise e
            finally:
                query_finished.set()
                close_old_connections()

        threads = [
            threading.Thread(target=target_processing),
            threading.Thread(target=target_query)
        ]
        for t in threads:
            t.start()
        for t in threads:
            t.join()

        self.assertTrue(self.errors.empty())
        self.assertQuerysetEqual(BankPayment.objects.values_list(
            'identifier', 'account', 'state', 'processor'),
                                 [('PAYMENT_1', self.account_in.pk,
                                   PaymentState.PROCESSED, 'dummy'),
                                  ('PAYMENT_2', self.account_ex.pk,
                                   PaymentState.PROCESSED, 'manual')],
                                 transform=tuple,
                                 ordered=False)