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')
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)
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
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
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))
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' })
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)
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))
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)
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)
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)
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)
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)
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)
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)