예제 #1
0
파일: tasks.py 프로젝트: dimagi/commcare-hq
def reprocess_submission(submssion_stub_id):
    with CriticalSection(['reprocess_submission_%s' % submssion_stub_id]):
        try:
            stub = UnfinishedSubmissionStub.objects.get(id=submssion_stub_id)
        except UnfinishedSubmissionStub.DoesNotExist:
            return

        reprocess_unfinished_stub(stub)
        datadog_counter('commcare.submission_reprocessing.count')
예제 #2
0
def reprocess_submission(submssion_stub_id):
    with CriticalSection(['reprocess_submission_%s' % submssion_stub_id]):
        try:
            stub = UnfinishedSubmissionStub.objects.get(id=submssion_stub_id)
        except UnfinishedSubmissionStub.DoesNotExist:
            return

        reprocess_unfinished_stub(stub)
        datadog_counter('commcare.submission_reprocessing.count')
예제 #3
0
    def test_reprocess_unfinished_submission_case_create(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id, attrs={'case_type': 'parent', 'create': True})
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        # form that was saved before case error raised
        normal_form_ids = self.formdb.get_all_form_ids_in_domain('XFormInstance')
        self.assertEqual(0, len(normal_form_ids))

        # shows error form (duplicate of form that was saved before case error)
        # this is saved becuase the saving was assumed to be atomic so if there was any error it's assumed
        # the form didn't get saved
        # we don't really care about this form in this test
        error_forms = self.formdb.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))
        self.assertIsNone(error_forms[0].orig_id)
        self.assertEqual(error_forms[0].form_id, stubs[0].xform_id)

        self.assertEqual(0, len(self.casedb.get_case_ids_in_domain()))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))

        case_ids = self.casedb.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        self.assertEqual(case_id, case_ids[0])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
예제 #4
0
    def test_reprocess_unfinished_submission_case_create(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id, attrs={'case_type': 'parent', 'create': True})
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        # form that was saved before case error raised
        normal_form_ids = self.formdb.get_all_form_ids_in_domain('XFormInstance')
        self.assertEqual(0, len(normal_form_ids))

        # shows error form (duplicate of form that was saved before case error)
        # this is saved becuase the saving was assumed to be atomic so if there was any error it's assumed
        # the form didn't get saved
        # we don't really care about this form in this test
        error_forms = self.formdb.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))
        self.assertIsNone(error_forms[0].orig_id)
        self.assertEqual(error_forms[0].form_id, stubs[0].xform_id)

        self.assertEqual(0, len(self.casedb.get_case_ids_in_domain(self.domain)))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))

        case_ids = self.casedb.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        self.assertEqual(case_id, case_ids[0])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
    def handle(self, domain, **options):
        dryrun = options["dryrun"]
        verbose = options["verbose"] or dryrun

        mode = options['mode']
        if mode == 'stats':
            self.print_stats()
            return

        batch_size = {
            'single': 1,
            'batch': options['batch_size'],
            'all': None
        }[mode]

        if verbose and batch_size:
            root_logger = logging.getLogger('')
            root_logger.setLevel(logging.DEBUG)

        if not batch_size:
            if dryrun:
                raise CommandError('Dry run only for single / batch modes')
            total = UnfinishedSubmissionStub.objects.count()
            stub_iterator = with_progress_bar(UnfinishedSubmissionStub.objects.all(), total, oneline=False)
            for stub in stub_iterator:
                reprocess_unfinished_stub(stub)
        else:
            paginator = Paginator(UnfinishedSubmissionStub.objects.all(), batch_size)
            for page_number in paginator.page_range:
                page = paginator.page(page_number)
                for stub in page.object_list:
                    result = reprocess_unfinished_stub(stub, save=not dryrun)
                    if not result:
                        logger.info("Processing skipped")
                    elif result.error:
                        logger.info(f"Form re-processed failed: {stub.xform_id}: {result.error}")
                    else:
                        cases = ', '.join([c.case_id for c in result.cases])
                        ledgers = ', '.join([l.ledger_reference for l in result.ledgers])
                        logger.info("Form re-processed successfully: {}:{} cases={} ledgers={}".format(
                            result.form.domain, result.form.form_id, cases, ledgers
                        ))
                if not page.has_next():
                    print("All forms processed")
                elif not confirm():
                    break
예제 #6
0
    def test_reprocess_unfinished_submission_ledger_rebuild(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(
            submit_case_blocks([
                CaseBlock(case_id=case_id, create=True,
                          case_type='shop').as_text(),
                get_single_balance_block(case_id, 'product1', 100),
            ], self.domain)[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 50), self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))
        form_ids.append(stubs[0].xform_id)

        # submit another form afterwards
        form_ids.append(
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 25),
                self.domain)[0].form_id)

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(2, len(ledger_transactions))

        # should rebuild ledger transactions
        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))  # still only 1
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(3, len(ledger_transactions))
        # make sure transactions are in correct order
        self.assertEqual(form_ids,
                         [trans.form_id for trans in ledger_transactions])
        self.assertEqual(100, ledger_transactions[0].updated_balance)
        self.assertEqual(100, ledger_transactions[0].delta)
        self.assertEqual(50, ledger_transactions[1].updated_balance)
        self.assertEqual(-50, ledger_transactions[1].delta)
        self.assertEqual(25, ledger_transactions[2].updated_balance)
        self.assertEqual(-25, ledger_transactions[2].delta)
    def handle(self, domain, **options):
        dryrun = options["dryrun"]
        verbose = options["verbose"] or dryrun

        mode = options['mode']
        if mode == 'stats':
            self.print_stats()
            return

        batch_size = {
            'single': 1,
            'batch': options['batch_size'],
            'all': None
        }[mode]

        if verbose and batch_size:
            root_logger = logging.getLogger('')
            root_logger.setLevel(logging.DEBUG)

        if not batch_size:
            if dryrun:
                raise CommandError('Dry run only for single / batch modes')
            total = UnfinishedSubmissionStub.objects.count()
            stub_iterator = with_progress_bar(UnfinishedSubmissionStub.objects.all(), total, oneline=False)
            for stub in stub_iterator:
                reprocess_unfinished_stub(stub)
        else:
            paginator = Paginator(UnfinishedSubmissionStub.objects.all(), batch_size)
            for page_number in paginator.page_range:
                page = paginator.page(page_number)
                for stub in page.object_list:
                    result = reprocess_unfinished_stub(stub, save=not dryrun)
                    if result:
                        cases = ', '.join([c.case_id for c in result.cases])
                        ledgers = ', '.join([l.ledger_reference for l in result.ledgers])
                        logger.info("Form re-processed successfully: {}:{}".format(
                            result.form.domain, result.form.form_id, cases, ledgers
                        ))
                if not page.has_next():
                    print("All forms processed")
                elif not confirm():
                    break
예제 #8
0
    def test_reprocess_unfinished_submission_ledger_create(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        self.factory.create_or_update_cases([
            CaseStructure(case_id=case_id,
                          attrs={
                              'case_type': 'parent',
                              'create': True
                          })
        ])

        transaction_patch = patch(
            'corehq.form_processor.backends.sql.processor.transaction')
        ledger_save_patch = patch(
            'corehq.form_processor.backends.sql.dbaccessors.LedgerAccessorSQL.save_ledger_values',
            side_effect=InternalError)
        with transaction_patch, ledger_save_patch, self.assertRaises(
                InternalError):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 100),
                self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        ledgers = LedgerAccessorSQL.get_ledger_values_for_case(case_id)
        self.assertEqual(0, len(ledgers))

        # case transaction got saved
        case = CaseAccessorSQL.get_case(case_id)
        self.assertEqual(2, len(case.transactions))
        self.assertTrue(case.transactions[0].is_case_create)
        self.assertTrue(case.transactions[1].is_ledger_transaction)

        ledger_transactions = LedgerAccessorSQL.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(0, len(ledger_transactions))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(0, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = LedgerAccessorSQL.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))

        ledger_transactions = LedgerAccessorSQL.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(1, len(ledger_transactions))

        # case still only has 2 transactions
        case = CaseAccessorSQL.get_case(case_id)
        self.assertEqual(2, len(case.transactions))
예제 #9
0
def reprocess_submission(submssion_stub_id):
    with CriticalSection(['reprocess_submission_%s' % submssion_stub_id]):
        try:
            stub = UnfinishedSubmissionStub.objects.get(id=submssion_stub_id)
        except UnfinishedSubmissionStub.DoesNotExist:
            return

        result = reprocess_unfinished_stub(stub)
        if result:
            metrics_counter('commcare.submission_reprocessing.count', tags={
                'domain': stub.domain,
                'status': 'error' if result.error else 'success'
            })
예제 #10
0
    def test_reprocess_unfinished_submission_case_update(self):
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(
            submit_case_blocks(
                CaseBlock(case_id=case_id, create=True,
                          case_type='box').as_string(),
                self.domain)[0].form_id)

        transaction_patch = patch(
            'corehq.form_processor.backends.sql.processor.transaction')
        case_save_patch = patch(
            'corehq.form_processor.backends.sql.dbaccessors.CaseAccessorSQL.save_case',
            side_effect=InternalError)
        with transaction_patch, case_save_patch, self.assertRaises(
                InternalError):
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={
                    'prop': 'a'
                }).as_string(), self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        form_ids.append(stubs[0].xform_id)

        # submit second form with case update
        form_ids.append(
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={
                    'prop': 'b'
                }).as_string(), self.domain)[0].form_id)

        case = CaseAccessorSQL.get_case(case_id)
        self.assertEqual(2, len(case.transactions))
        self.assertEqual('b', case.get_case_property('prop'))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case = CaseAccessorSQL.get_case(case_id)
        self.assertEqual('b', case.get_case_property(
            'prop'))  # should be property value from most recent form
        self.assertEqual(4, len(case.transactions))
        self.assertEqual(form_ids + [None],
                         [trans.form_id for trans in case.transactions])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
예제 #11
0
    def test_processing_skipped_when_migrations_are_in_progress(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id,
                              attrs={
                                  'case_type': 'parent',
                                  'create': True
                              })
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        with patch(
                'corehq.form_processor.reprocess.any_migrations_in_progress',
                return_value=True):
            result = reprocess_unfinished_stub(stubs[0])
            self.assertIsNone(result)

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
예제 #12
0
    def test_reprocess_unfinished_submission_case_create(self):
        case_id = uuid.uuid4().hex
        transaction_patch = patch(
            'corehq.form_processor.backends.sql.processor.transaction')
        case_save_patch = patch(
            'corehq.form_processor.backends.sql.dbaccessors.CaseAccessorSQL.save_case',
            side_effect=InternalError)
        with transaction_patch, case_save_patch, self.assertRaises(
                InternalError):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id,
                              attrs={
                                  'case_type': 'parent',
                                  'create': True
                              })
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        # form that was saved before case error raised
        normal_form_ids = FormAccessorSQL.get_form_ids_in_domain_by_state(
            self.domain, XFormInstanceSQL.NORMAL)
        self.assertEqual(1, len(normal_form_ids))
        self.assertEqual(stubs[0].xform_id, normal_form_ids[0])

        # shows error form (duplicate of form that was saved before case error)
        # this is saved becuase the saving was assumed to be atomic so if there was any error it's assumed
        # the form didn't get saved
        # we don't really care about this form in this test
        error_forms = FormAccessorSQL.get_forms_by_type(
            self.domain, 'XFormError', 10)
        self.assertEqual(1, len(error_forms))
        self.assertEqual(error_forms[0].orig_id, normal_form_ids[0])

        self.assertEqual(
            0, len(CaseAccessorSQL.get_case_ids_in_domain(self.domain)))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case_ids = CaseAccessorSQL.get_case_ids_in_domain(self.domain)
        self.assertEqual(1, len(case_ids))
        self.assertEqual(case_id, case_ids[0])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
예제 #13
0
    def test_reprocess_unfinished_submission_case_update(self):
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(
            submit_case_blocks(
                CaseBlock.deprecated_init(case_id=case_id,
                                          create=True,
                                          case_type='box').as_text(),
                self.domain)[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                CaseBlock.deprecated_init(case_id=case_id,
                                          update={
                                              'prop': 'a'
                                          }).as_text(), self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        form_ids.append(stubs[0].xform_id)

        # submit second form with case update
        form_ids.append(
            submit_case_blocks(
                CaseBlock.deprecated_init(case_id=case_id,
                                          update={
                                              'prop': 'b'
                                          }).as_text(),
                self.domain)[0].form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        self.assertEqual('b', case.get_case_property('prop'))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual('b', case.get_case_property(
            'prop'))  # should be property value from most recent form
        self.assertEqual(3, len(case.xform_ids))
        self.assertEqual(form_ids, case.xform_ids)

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
예제 #14
0
    def test_reprocess_unfinished_submission_ledger_create(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        self.factory.create_or_update_cases([
            CaseStructure(case_id=case_id,
                          attrs={
                              'case_type': 'parent',
                              'create': True
                          })
        ])

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 100),
                self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(0, len(ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual(1, len(case.xform_ids))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(0, len(ledger_transactions))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(1, len(ledger_transactions))

        # case still only has 2 transactions
        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        if should_use_sql_backend(self.domain):
            self.assertTrue(case.actions[1].is_ledger_transaction)
예제 #15
0
    def test_processing_retuns_error_for_missing_form(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id,
                              attrs={
                                  'case_type': 'parent',
                                  'create': True
                              })
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain)
        with self.assertRaises(XFormNotFound):
            self.formdb.get_form(stubs[0].xform_id)

        result = reprocess_unfinished_stub(stubs[0])
        self.assertIsNotNone(result.error)
예제 #16
0
    def test_reprocess_unfinished_submission_case_update(self):
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(submit_case_blocks(
            CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'),
            self.domain
        )[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={'prop': 'a'}).as_string().decode('utf-8'),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        form_ids.append(stubs[0].xform_id)

        # submit second form with case update
        form_ids.append(submit_case_blocks(
            CaseBlock(case_id=case_id, update={'prop': 'b'}).as_string().decode('utf-8'),
            self.domain
        )[0].form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        self.assertEqual('b', case.get_case_property('prop'))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual('b', case.get_case_property('prop'))  # should be property value from most recent form
        self.assertEqual(3, len(case.xform_ids))
        self.assertEqual(form_ids, case.xform_ids)

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)
예제 #17
0
    def test_reprocess_unfinished_submission_ledger_create(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        self.factory.create_or_update_cases([
            CaseStructure(case_id=case_id, attrs={'case_type': 'parent', 'create': True})
        ])

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 100),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(0, len(ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual(1, len(case.xform_ids))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        self.assertEqual(0, len(ledger_transactions))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        self.assertEqual(1, len(ledger_transactions))

        # case still only has 2 transactions
        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        if should_use_sql_backend(self.domain):
            self.assertTrue(case.actions[1].is_ledger_transaction)
예제 #18
0
    def test_reprocess_unfinished_submission_ledger_rebuild(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(submit_case_blocks(
            [
                CaseBlock(case_id=case_id, create=True, case_type='shop').as_string().decode('utf-8'),
                get_single_balance_block(case_id, 'product1', 100),
            ],
            self.domain
        )[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 50),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))
        form_ids.append(stubs[0].xform_id)

        # submit another form afterwards
        form_ids.append(submit_case_blocks(
            get_single_balance_block(case_id, 'product1', 25),
            self.domain
        )[0].form_id)

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(2, len(ledger_transactions))
        else:
            # includes extra consumption transaction
            self.assertEqual(3, len(ledger_transactions))

        # should rebuild ledger transactions
        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))  # still only 1
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(3, len(ledger_transactions))
            # make sure transactions are in correct order
            self.assertEqual(form_ids, [trans.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].updated_balance)
            self.assertEqual(100, ledger_transactions[0].delta)
            self.assertEqual(50, ledger_transactions[1].updated_balance)
            self.assertEqual(-50, ledger_transactions[1].delta)
            self.assertEqual(25, ledger_transactions[2].updated_balance)
            self.assertEqual(-25, ledger_transactions[2].delta)

        else:
            self.assertEqual(3, len(ledger_transactions))
            self.assertEqual(form_ids, [trans.report.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].stock_on_hand)
            self.assertEqual(50, ledger_transactions[1].stock_on_hand)
            self.assertEqual(25, ledger_transactions[2].stock_on_hand)