def test_soft_delete(self): meta = TestFormMetadata(domain=DOMAIN) get_simple_wrapped_form('f1', metadata=meta) f2 = get_simple_wrapped_form('f2', metadata=meta) f2.archive() get_simple_wrapped_form('f3', metadata=meta) accessors = FormAccessors(DOMAIN) # delete num = accessors.soft_delete_forms(['f1', 'f2'], deletion_id='123') self.assertEqual(num, 2) for form_id in ['f1', 'f2']: form = accessors.get_form(form_id) self.assertTrue(form.is_deleted) self.assertEqual(form.deletion_id, '123') form = accessors.get_form('f3') self.assertFalse(form.is_deleted) for form_id in ['f1', 'f2']: form = FormAccessors(DOMAIN).get_form(form_id) if isinstance(form, PartitionedModel): form.delete(using=get_db_alias_for_partitioned_doc(form.form_id)) else: form.delete()
class TestHardDelete(TestCase): def setUp(self): self.casedb = CaseAccessors(TEST_DOMAIN_NAME) self.formdb = FormAccessors(TEST_DOMAIN_NAME) @run_with_all_backends def test_simple_delete(self): factory = CaseFactory() case = factory.create_case() [case] = factory.create_or_update_case( CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}}) ) self.assertIsNotNone(self.casedb.get_case(case.case_id)) self.assertEqual(2, len(case.xform_ids)) for form_id in case.xform_ids: self.assertIsNotNone(self.formdb.get_form(form_id)) safe_hard_delete(case) with self.assertRaises(CaseNotFound): self.casedb.get_case(case.case_id) for form_id in case.xform_ids: with self.assertRaises(XFormNotFound): self.formdb.get_form(form_id) @run_with_all_backends def test_delete_with_related(self): factory = CaseFactory() parent = factory.create_case() [child] = factory.create_or_update_case( CaseStructure(attrs={'create': True}, walk_related=False, indices=[ CaseIndex(CaseStructure(case_id=parent.case_id)) ]), ) # deleting the parent should not be allowed because the child still references it with self.assertRaises(CommCareCaseError): safe_hard_delete(parent) # deleting the child is ok safe_hard_delete(child) self.assertIsNotNone(self.casedb.get_case(parent.case_id)) with self.assertRaises(CaseNotFound): self.casedb.get_case(child.case_id) @run_with_all_backends def test_delete_sharing_form(self): factory = CaseFactory() c1, c2 = factory.create_or_update_cases([ CaseStructure(attrs={'create': True}), CaseStructure(attrs={'create': True}), ]) with self.assertRaises(CommCareCaseError): safe_hard_delete(c1) with self.assertRaises(CommCareCaseError): safe_hard_delete(c2) self.assertIsNotNone(self.casedb.get_case(c1.case_id)) self.assertIsNotNone(self.casedb.get_case(c2.case_id))
def test_submit_deprecated_form_with_attachments(self): def list_attachments(form): return sorted( (att.name, att.open().read()) for att in form.get_attachments() if att.name != "form.xml" ) self._submit('simple_form.xml', attachments={ "image": BytesIO(b"fake image"), "file": BytesIO(b"text file"), }) response = self._submit( 'simple_form_edited.xml', attachments={"image": BytesIO(b"other fake image")}, url=reverse("receiver_secure_post", args=[self.domain]), ) acc = FormAccessors(self.domain.name) new_form = acc.get_form(response['X-CommCareHQ-FormID']) old_form = acc.get_form(new_form.deprecated_form_id) self.assertIn(b"<bop>bang</bop>", old_form.get_xml()) self.assertIn(b"<bop>bong</bop>", new_form.get_xml()) self.assertEqual(list_attachments(old_form), [("file", b"text file"), ("image", b"fake image")]) self.assertEqual(list_attachments(new_form), [("file", b"text file"), ("image", b"other fake image")])
def test_submit_deprecated_form_with_attachments(self): def list_attachments(form): return sorted((att.name, att.open().read()) for att in form.get_attachments() if att.name != "form.xml") self._submit('simple_form.xml', attachments={ "image": BytesIO(b"fake image"), "file": BytesIO(b"text file"), }) response = self._submit( 'simple_form_edited.xml', attachments={"image": BytesIO(b"other fake image")}, url=reverse("receiver_secure_post", args=[self.domain]), ) acc = FormAccessors(self.domain.name) new_form = acc.get_form(response['X-CommCareHQ-FormID']) old_form = acc.get_form(new_form.deprecated_form_id) self.assertIn(b"<bop>bang</bop>", old_form.get_xml()) self.assertIn(b"<bop>bong</bop>", new_form.get_xml()) self.assertEqual(list_attachments(old_form), [("file", b"text file"), ("image", b"fake image")]) self.assertEqual(list_attachments(new_form), [("file", b"text file"), ("image", b"other fake image")])
def test_soft_delete(self): meta = TestFormMetadata(domain=DOMAIN) get_simple_wrapped_form('f1', metadata=meta) f2 = get_simple_wrapped_form('f2', metadata=meta) f2.archive() get_simple_wrapped_form('f3', metadata=meta) accessors = FormAccessors(DOMAIN) # delete num = accessors.soft_delete_forms(['f1', 'f2'], deletion_id='123') self.assertEqual(num, 2) for form_id in ['f1', 'f2']: form = accessors.get_form(form_id) self.assertTrue(form.is_deleted) self.assertEqual(form.deletion_id, '123') form = accessors.get_form('f3') self.assertFalse(form.is_deleted) for form_id in ['f1', 'f2']: form = FormAccessors(DOMAIN).get_form(form_id) if isinstance(form, DisabledDbMixin): super(DisabledDbMixin, form).delete() else: form.delete()
class FormDocumentStore(DocumentStore): def __init__(self, domain, xmlns=None): self.domain = domain self.form_accessors = FormAccessors(domain=domain) self.xmlns = xmlns def get_document(self, doc_id): try: form = self.form_accessors.get_form(doc_id) return self._to_json(form) except (XFormNotFound, BlobError) as e: raise DocumentNotFoundError(e) @staticmethod def _to_json(form): if isinstance(form, XFormInstanceSQL): return form.to_json(include_attachments=True) else: return form.to_json() def iter_document_ids(self): return iter(self.form_accessors.iter_form_ids_by_xmlns(self.xmlns)) def iter_documents(self, ids): for wrapped_form in self.form_accessors.iter_forms(ids): try: yield self._to_json(wrapped_form) except (DocumentNotFoundError, MissingFormXml): pass
def test_reprocess_xform_error(self): case_id = uuid.uuid4().hex parent_case_id = uuid.uuid4().hex case = CaseBlock( create=True, case_id=case_id, user_id='user1', owner_id='user1', case_type='demo', case_name='child', index={'parent': ('parent_type', parent_case_id)} ) post_case_blocks([case.as_xml()], domain=self.domain) form_accessors = FormAccessors(self.domain) error_forms = form_accessors.get_forms_by_type('XFormError', 10) self.assertEqual(1, len(error_forms)) form = error_forms[0] reprocess_xform_error(form) error_forms = form_accessors.get_forms_by_type('XFormError', 10) self.assertEqual(1, len(error_forms)) case = CaseBlock( create=True, case_id=parent_case_id, user_id='user1', owner_id='user1', case_type='parent_type', case_name='parent', ) post_case_blocks([case.as_xml()], domain=self.domain) reprocess_xform_error(form_accessors.get_form(form.form_id)) form = form_accessors.get_form(form.form_id) # self.assertTrue(form.initial_processing_complete) Can't change this with SQL forms at the moment self.assertTrue(form.is_normal) self.assertIsNone(form.problem) case = CaseAccessors(self.domain).get_case(case_id) self.assertEqual(1, len(case.indices)) self.assertEqual(case.indices[0].referenced_id, parent_case_id) self._validate_case(case)
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.form_accessors = FormAccessors(domain=domain) def get_document(self, doc_id): return self.form_accessors.get_form(doc_id).to_json()
def test_form_archiving(self): now = datetime.utcnow() # make sure we timestamp everything so they have the right order case_id = _post_util(create=True, p1='p1-1', p2='p2-1', form_extras={'received_on': now}) _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2', form_extras={'received_on': now + timedelta(seconds=1)}) _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True, form_extras={'received_on': now + timedelta(seconds=2)}) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) closed_by = case.closed_by closed_on = case.closed_on self.assertNotEqual('', closed_by) self.assertNotEqual(None, closed_on) def _check_initial_state(case): self.assertTrue(case.closed) self.assertEqual(closed_by, case.closed_by) self.assertEqual(closed_on, case.closed_on) self.assertEqual(case.get_case_property('p1'), 'p1-1') # original self.assertEqual(case.get_case_property('p2'), 'p2-2') # updated in second post self.assertEqual(case.get_case_property('p3'), 'p3-2') # new in second post self.assertEqual(case.get_case_property('p4'), 'p4-3') # updated in third post self.assertEqual(case.get_case_property('p5'), 'p5-3') # new in third post if should_use_sql_backend(REBUILD_TEST_DOMAIN): # SQL stores one transaction per form self.assertEqual(3, len(primary_actions(case))) # create + update + close else: self.assertEqual(5, len(primary_actions(case))) # create + 3 updates + close _check_initial_state(case) # verify xform/action states [f1, f2, f3] = case.xform_ids if should_use_sql_backend(REBUILD_TEST_DOMAIN): [create, update, close] = case.actions self.assertEqual(f1, create.form_id) self.assertEqual(f2, update.form_id) self.assertEqual(f3, close.form_id) else: [create, u1, u2, u3, close] = case.actions self.assertEqual(f1, create.form_id) self.assertEqual(f1, u1.form_id) self.assertEqual(f2, u2.form_id) self.assertEqual(f3, u3.form_id) # todo: should this be the behavior for archiving the create form? form_acessors = FormAccessors(REBUILD_TEST_DOMAIN) f1_doc = form_acessors.get_form(f1) with capture_kafka_changes_context(topics.CASE_SQL) as change_context: f1_doc.archive() if should_use_sql_backend(case.domain): self.assertEqual([case.case_id], [change.id for change in change_context.changes])
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.form_accessors = FormAccessors(domain=domain) def get_document(self, doc_id): try: return self.form_accessors.get_form(doc_id).to_json() except (XFormNotFound, BlobError) as e: raise DocumentNotFoundError(e)
def perform_resave_on_xforms(domain): xform_ids_missing_in_es, _ = compare_xforms(domain, 'XFormInstance') print("%s Ids found for xforms missing in ES." % len(xform_ids_missing_in_es)) print(xform_ids_missing_in_es) ok = input("Type 'ok' to continue: ") if ok != "ok": print("No changes made") return form_accessor = FormAccessors(domain) for xform_id in with_progress_bar(xform_ids_missing_in_es): xform = form_accessor.get_form(xform_id) if xform: resave_form(domain, xform) else: print("form not found %s" % xform_id)
def get_version_from_app_object(self, item, app_version_from_app_string): domain_name = item.get('domain') form_accessor = FormAccessors(domain_name) form_id = item.get('form').get('meta').get('instanceID') form = form_accessor.get_form(form_id) app_build = get_build_by_version(domain_name, form.app_id, app_version_from_app_string) if app_build is None: return app_version_from_app_string elif app_build.get('value').get('doc_type') == 'LinkedApplication': return app_build.get('value').get('upstream_version') else: return app_build.get('value').get('version')
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.form_accessors = FormAccessors(domain=domain) def get_document(self, doc_id): try: return add_couch_properties_to_sql_form_json(self.form_accessors.get_form(doc_id).to_json()) except (XFormNotFound, BlobError) as e: raise DocumentNotFoundError(e) def iter_document_ids(self, last_id=None): # todo: support last_id return iter(self.form_accessors.get_all_form_ids_in_domain()) def iter_documents(self, ids): for wrapped_form in self.form_accessors.iter_forms(ids): yield add_couch_properties_to_sql_form_json(wrapped_form.to_json())
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.form_accessors = FormAccessors(domain=domain) def get_document(self, doc_id): try: return self.form_accessors.get_form(doc_id).to_json(include_attachments=True) except (XFormNotFound, BlobError) as e: raise DocumentNotFoundError(e) def iter_document_ids(self, last_id=None): # todo: support last_id return iter(self.form_accessors.get_all_form_ids_in_domain()) def iter_documents(self, ids): for wrapped_form in self.form_accessors.iter_forms(ids): yield wrapped_form.to_json()
def _reopen_or_create_supply_point(location): from .helpers import update_supply_point_from_location from .dbaccessors import get_supply_point_by_location_id supply_point = get_supply_point_by_location_id(location.domain, location.location_id) if supply_point: if supply_point and supply_point.closed: form_ids = CaseAccessors(supply_point.domain).get_case_xform_ids(supply_point.case_id) form_accessor = FormAccessors(supply_point.domain) for form_id in form_ids: form = form_accessor.get_form(form_id) closes_case = any(map( lambda case_update: case_update.closes_case(), get_case_updates(form), )) if closes_case: form.archive(user_id=const.COMMTRACK_USERNAME) update_supply_point_from_location(supply_point, location) return supply_point else: return SupplyInterface.create_from_location(location.domain, location)
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain, xmlns=None): self.domain = domain self.form_accessors = FormAccessors(domain=domain) self.xmlns = xmlns def get_document(self, doc_id): try: form = self.form_accessors.get_form(doc_id) if isinstance(form, XFormInstanceSQL): return form.to_json(include_attachments=True) else: return form.to_json() except (XFormNotFound, BlobError) as e: raise DocumentNotFoundError(e) def iter_document_ids(self, last_id=None): # todo: support last_id return iter(self.form_accessors.iter_form_ids_by_xmlns(self.xmlns)) def iter_documents(self, ids): for wrapped_form in self.form_accessors.iter_forms(ids): yield wrapped_form.to_json()
class EditFormTest(TestCase, TestFileMixin): ID = '7H46J37FGH3' domain = 'test-form-edits' file_path = ('data', 'deprecation') root = os.path.dirname(__file__) def setUp(self): self.interface = FormProcessorInterface(self.domain) self.casedb = CaseAccessors(self.domain) self.formdb = FormAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_xforms(self.domain) FormProcessorTestUtils.delete_all_cases(self.domain) UnfinishedSubmissionStub.objects.all().delete() @run_with_all_backends def test_basic_edit(self): original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') xform = post_xform(original_xml, domain=self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("", xform.form_data['vitals']['height']) self.assertEqual("other", xform.form_data['assessment']['categories']) xform = post_xform(edit_xml, domain=self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("100", xform.form_data['vitals']['height']) self.assertEqual("Edited Baby!", xform.form_data['assessment']['categories']) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertEqual(self.ID, deprecated_xform.orig_id) self.assertNotEqual(self.ID, deprecated_xform.form_id) self.assertTrue(deprecated_xform.is_deprecated) self.assertEqual("", deprecated_xform.form_data['vitals']['height']) self.assertEqual("other", deprecated_xform.form_data['assessment']['categories']) self.assertEqual(xform.received_on, deprecated_xform.received_on) self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id) self.assertTrue(xform.edited_on > deprecated_xform.received_on) self.assertEqual( deprecated_xform.get_xml(), original_xml ) self.assertEqual(xform.get_xml(), edit_xml) @run_with_all_backends def test_broken_save(self): """ Test that if the second form submission terminates unexpectedly and the main form isn't saved, then there are no side effects such as the original having been marked as deprecated. """ original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') _, xform, _ = submit_form_locally(original_xml, self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual(self.domain, xform.domain) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 0 ) with patch.object(self.interface.processor, 'save_processed_models', side_effect=RequestFailed): with self.assertRaises(RequestFailed): submit_form_locally(edit_xml, self.domain) # it didn't go through, so make sure there are no edits still self.assertIsNone(getattr(xform, 'deprecated_form_id', None)) xform = self.formdb.get_form(self.ID) self.assertIsNotNone(xform) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID, saved=False).count(), 1 ) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 1 ) @run_with_all_backends def test_case_management(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex case_block = CaseBlock( create=True, case_id=case_id, case_type='person', owner_id=owner_id, update={ 'property': 'original value' } ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # validate some assumptions case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'person') self.assertEqual(case.dynamic_case_properties()['property'], 'original value') self.assertEqual([form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) # submit a new form with a different case update case_block = CaseBlock( create=True, case_id=case_id, case_type='newtype', owner_id=owner_id, update={ 'property': 'edited value' } ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'newtype') self.assertEqual(case.dynamic_case_properties()['property'], 'edited value') self.assertEqual([form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) @run_with_all_backends def test_second_edit_fails(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex case_block = CaseBlock( create=True, case_id=case_id, case_type='person', ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # submit an edit form with a bad case update (for example a bad ID) case_block = CaseBlock( create=True, case_id='', case_type='person', ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) xform = self.formdb.get_form(form_id) self.assertTrue(xform.is_error) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertTrue(deprecated_xform.is_deprecated) @run_with_all_backends def test_case_management_ordering(self): case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex # create a case case_block = CaseBlock( create=True, case_id=case_id, case_type='person', owner_id=owner_id, ).as_string() create_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual([create_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual([create_form_id], [a.xform_id for a in case.actions]) for a in case.actions: self.assertEqual(create_form_id, a.xform_id) edit_date = datetime.utcnow() # set some property value case_block = CaseBlock( create=False, case_id=case_id, date_modified=edit_date, update={ 'property': 'first value', } ).as_string() edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'first value') self.assertEqual([create_form_id, edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual([create_form_id, edit_form_id], [a.xform_id for a in case.actions]) # submit a second (new) form updating the value case_block = CaseBlock( create=False, case_id=case_id, update={ 'property': 'final value', } ).as_string() second_edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual( [create_form_id, edit_form_id, second_edit_form_id], [a.xform_id for a in case.actions] ) # deprecate the middle edit case_block = CaseBlock( create=False, case_id=case_id, date_modified=edit_date, # need to use the previous edit date for action sort comparisons update={ 'property': 'edited value', 'added_property': 'added value', } ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=edit_form_id) # ensure that the middle edit stays in the right place and is applied # before the final one case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual(case.dynamic_case_properties()['added_property'], 'added value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual( [create_form_id, edit_form_id, second_edit_form_id], [a.xform_id for a in case.actions] )
def test_form_archiving(self): now = datetime.utcnow() # make sure we timestamp everything so they have the right order case_id = _post_util(create=True, p1='p1-1', p2='p2-1', form_extras={'received_on': now}) _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2', form_extras={'received_on': now + timedelta(seconds=1)}) _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True, form_extras={'received_on': now + timedelta(seconds=2)}) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) closed_by = case.closed_by closed_on = case.closed_on self.assertNotEqual('', closed_by) self.assertNotEqual(None, closed_on) def _check_initial_state(case): self.assertTrue(case.closed) self.assertEqual(closed_by, case.closed_by) self.assertEqual(closed_on, case.closed_on) self.assertEqual(case.get_case_property('p1'), 'p1-1') # original self.assertEqual(case.get_case_property('p2'), 'p2-2') # updated in second post self.assertEqual(case.get_case_property('p3'), 'p3-2') # new in second post self.assertEqual(case.get_case_property('p4'), 'p4-3') # updated in third post self.assertEqual(case.get_case_property('p5'), 'p5-3') # new in third post if should_use_sql_backend(REBUILD_TEST_DOMAIN): # SQL stores one transaction per form self.assertEqual(3, len(primary_actions(case))) # create + update + close else: self.assertEqual(5, len(primary_actions(case))) # create + 3 updates + close _check_initial_state(case) # verify xform/action states [f1, f2, f3] = case.xform_ids if should_use_sql_backend(REBUILD_TEST_DOMAIN): [create, update, close] = case.actions self.assertEqual(f1, create.form_id) self.assertEqual(f2, update.form_id) self.assertEqual(f3, close.form_id) else: [create, u1, u2, u3, close] = case.actions self.assertEqual(f1, create.form_id) self.assertEqual(f1, u1.form_id) self.assertEqual(f2, u2.form_id) self.assertEqual(f3, u3.form_id) # todo: should this be the behavior for archiving the create form? form_acessors = FormAccessors(REBUILD_TEST_DOMAIN) f1_doc = form_acessors.get_form(f1) with capture_kafka_changes_context(topics.CASE_SQL) as change_context: f1_doc.archive() if should_use_sql_backend(case.domain): self.assertEqual([case.case_id], [change.id for change in change_context.changes]) case = case_accessors.get_case(case_id) if should_use_sql_backend(REBUILD_TEST_DOMAIN): self.assertEqual(2, len(primary_actions(case))) else: self.assertEqual(3, len(primary_actions(case))) [u2, u3] = case.xform_ids self.assertEqual(f2, u2) self.assertEqual(f3, u3) self.assertTrue(case.closed) # no change self.assertFalse('p1' in case.dynamic_case_properties()) # should disappear entirely self.assertEqual(case.get_case_property('p2'), 'p2-2') # no change self.assertEqual(case.get_case_property('p3'), 'p3-2') # no change self.assertEqual(case.get_case_property('p4'), 'p4-3') # no change self.assertEqual(case.get_case_property('p5'), 'p5-3') # no change def _reset(form_id): form_doc = form_acessors.get_form(form_id) form_doc.unarchive() case = case_accessors.get_case(case_id) _check_initial_state(case) _reset(f1) f2_doc = form_acessors.get_form(f2) f2_doc.archive() case = case_accessors.get_case(case_id) if should_use_sql_backend(REBUILD_TEST_DOMAIN): self.assertEqual(2, len(primary_actions(case))) else: self.assertEqual(4, len(primary_actions(case))) [u1, u3] = case.xform_ids self.assertEqual(f1, u1) self.assertEqual(f3, u3) self.assertTrue(case.closed) # no change self.assertEqual(case.get_case_property('p1'), 'p1-1') # original self.assertEqual(case.get_case_property('p2'), 'p2-1') # loses second form update self.assertFalse('p3' in case.dynamic_case_properties()) # should disappear entirely self.assertEqual(case.get_case_property('p4'), 'p4-3') # no change self.assertEqual(case.get_case_property('p5'), 'p5-3') # no change _reset(f2) f3_doc = form_acessors.get_form(f3) f3_doc.archive() case = case_accessors.get_case(case_id) if should_use_sql_backend(REBUILD_TEST_DOMAIN): self.assertEqual(2, len(primary_actions(case))) else: self.assertEqual(3, len(primary_actions(case))) [u1, u2] = case.xform_ids self.assertEqual(f1, u1) self.assertEqual(f2, u2) self.assertFalse(case.closed) # reopened! self.assertEqual('', case.closed_by) self.assertEqual(None, case.closed_on) self.assertEqual(case.get_case_property('p1'), 'p1-1') # original self.assertEqual(case.get_case_property('p2'), 'p2-2') # original self.assertEqual(case.get_case_property('p3'), 'p3-2') # new in second post self.assertEqual(case.get_case_property('p4'), 'p4-2') # loses third form update self.assertFalse('p5' in case.dynamic_case_properties()) # should disappear entirely _reset(f3)
class BaseCaseMultimediaTest(TestCase, TestFileMixin): file_path = ('data', 'multimedia') root = os.path.dirname(__file__) def setUp(self): super(BaseCaseMultimediaTest, self).setUp() self.formdb = FormAccessors() FormProcessorTestUtils.delete_all_cases() FormProcessorTestUtils.delete_all_xforms() def _formatXForm(self, doc_id, raw_xml, attachment_block, date=None): if date is None: date = datetime.utcnow() final_xml = Template(raw_xml).render( Context({ "attachments": attachment_block, "time_start": json_format_datetime(date - timedelta(minutes=4)), "time_end": json_format_datetime(date), "date_modified": json_format_datetime(date), "doc_id": doc_id })) return final_xml def _prepAttachments(self, new_attachments, removes=[]): """ Returns: attachment_block - An XML representation of the attachment dict_attachments - A key-value dict where the key is the name and the value is a Stream of the attachment """ attachment_block = ''.join( [self._singleAttachBlock(x) for x in new_attachments] + [self._singleAttachRemoveBlock(x) for x in removes]) dict_attachments = dict( (MEDIA_FILES[attach_name], self._attachmentFileStream(attach_name)) for attach_name in new_attachments) return attachment_block, dict_attachments def _singleAttachBlock(self, key): return '<n0:%s src="%s" from="local"/>' % (key, MEDIA_FILES[key]) def _singleAttachRemoveBlock(self, key): return '<n0:%s />' % key def _attachmentFileStream(self, key): attachment_path = MEDIA_FILES[key] attachment = open(attachment_path, 'rb') return NoClose(UploadedFile(attachment, key)) def _calc_file_hash(self, key): with open(MEDIA_FILES[key], 'rb') as attach: return hashlib.md5(attach.read()).hexdigest() def _do_submit(self, xml_data, dict_attachments, sync_token=None, date=None): """ RequestFactory submitter - simulates direct submission to server directly (no need to call process case after fact) """ with flag_enabled('MM_CASE_PROPERTIES'): result = submit_form_locally(xml_data, TEST_DOMAIN_NAME, attachments=dict_attachments, last_sync_token=sync_token, received_on=date) xform = result.xform attachments = xform.attachments self.assertEqual(set(dict_attachments.keys()), set(attachments.keys())) self.assertEqual(result.case.case_id, TEST_CASE_ID) return result.response, self.formdb.get_form( xform.form_id), result.cases def _submit_and_verify(self, doc_id, xml_data, dict_attachments, sync_token=None, date=None): response, form, [case] = self._do_submit(xml_data, dict_attachments, sync_token, date=date) attachments = form.attachments self.assertEqual(len(dict_attachments), len(attachments)) for k, vstream in dict_attachments.items(): fileback = form.get_attachment(k) # rewind the pointer before comparing orig_attachment = vstream orig_attachment.seek(0) self.assertEqual( hashlib.md5(fileback).hexdigest(), hashlib.md5(orig_attachment.read()).hexdigest()) case = CaseAccessors(TEST_DOMAIN_NAME).get_case( case.case_id) # re-fetch case return form, case def _doCreateCaseWithMultimedia(self, attachments=['fruity_file']): xml_data = self.get_xml('multimedia_create') attachment_block, dict_attachments = self._prepAttachments(attachments) final_xml = self._formatXForm(CREATE_XFORM_ID, xml_data, attachment_block) return self._submit_and_verify(CREATE_XFORM_ID, final_xml, dict_attachments) def _doSubmitUpdateWithMultimedia(self, new_attachments=None, removes=None, sync_token=None, date=None): new_attachments = new_attachments if new_attachments is not None \ else ['commcare_logo_file', 'dimagi_logo_file'] removes = removes if removes is not None else ['fruity_file'] attachment_block, dict_attachments = self._prepAttachments( new_attachments, removes=removes) raw_xform = self.get_xml('multimedia_update') doc_id = uuid.uuid4().hex final_xform = self._formatXForm(doc_id, raw_xform, attachment_block, date) return self._submit_and_verify(doc_id, final_xform, dict_attachments, sync_token, date=date)
class ReprocessSubmissionStubTests(TestCase): @classmethod def setUpClass(cls): super(ReprocessSubmissionStubTests, cls).setUpClass() cls.domain = uuid.uuid4().hex cls.product = SQLProduct.objects.create(domain=cls.domain, product_id='product1', name='product1') @classmethod def tearDownClass(cls): cls.product.delete() super(ReprocessSubmissionStubTests, cls).tearDownClass() def setUp(self): super(ReprocessSubmissionStubTests, self).setUp() self.factory = CaseFactory(domain=self.domain) self.formdb = FormAccessors(self.domain) self.casedb = CaseAccessors(self.domain) self.ledgerdb = LedgerAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) super(ReprocessSubmissionStubTests, self).tearDown() 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 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) def test_fire_signals(self): from corehq.apps.receiverwrapper.tests.test_submit_errors import failing_signal_handler case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex with failing_signal_handler('signal death'): submit_case_blocks( CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'), self.domain, form_id=form_id ) form = self.formdb.get_form(form_id) with catch_signal(successful_form_received) as form_handler, catch_signal(case_post_save) as case_handler: submit_form_locally( instance=form.get_xml(), domain=self.domain, ) case = self.casedb.get_case(case_id) if should_use_sql_backend(self.domain): self.assertEqual(form, form_handler.call_args[1]['xform']) self.assertEqual(case, case_handler.call_args[1]['case']) else: signal_form = form_handler.call_args[1]['xform'] self.assertEqual(form.form_id, signal_form.form_id) self.assertEqual(form.get_rev, signal_form.get_rev) signal_case = case_handler.call_args[1]['case'] self.assertEqual(case.case_id, signal_case.case_id) self.assertEqual(case.get_rev, signal_case.get_rev)
class TestReprocessDuringSubmission(TestCase): @classmethod def setUpClass(cls): super(TestReprocessDuringSubmission, cls).setUpClass() cls.domain = uuid.uuid4().hex def setUp(self): super(TestReprocessDuringSubmission, self).setUp() self.factory = CaseFactory(domain=self.domain) self.formdb = FormAccessors(self.domain) self.casedb = CaseAccessors(self.domain) self.ledgerdb = LedgerAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) super(TestReprocessDuringSubmission, self).tearDown() def test_error_saving(self): case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex with _patch_save_to_raise_error(self): submit_case_blocks( CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'), self.domain, form_id=form_id ) stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all() self.assertEqual(1, len(stubs)) form = self.formdb.get_form(form_id) self.assertTrue(form.is_error) with self.assertRaises(CaseNotFound): self.casedb.get_case(case_id) result = submit_form_locally( instance=form.get_xml(), domain=self.domain, ) duplicate_form = result.xform self.assertTrue(duplicate_form.is_duplicate) case = self.casedb.get_case(case_id) self.assertIsNotNone(case) form = self.formdb.get_form(form_id) self.assertTrue(form.is_normal) self.assertIsNone(getattr(form, 'problem', None)) self.assertEqual(duplicate_form.orig_id, form.form_id) def test_processing_error(self): case_id = uuid.uuid4().hex parent_case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex form, _ = submit_case_blocks( CaseBlock( case_id=case_id, create=True, case_type='box', index={'cupboard': ('cupboard', parent_case_id)}, ).as_string().decode('utf-8'), self.domain, form_id=form_id ) self.assertTrue(form.is_error) self.assertTrue('InvalidCaseIndex' in form.problem) self.assertEqual(form.form_id, form_id) with self.assertRaises(CaseNotFound): self.casedb.get_case(case_id) stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all() self.assertEqual(0, len(stubs)) # create parent case submit_case_blocks( CaseBlock(case_id=parent_case_id, create=True, case_type='cupboard').as_string().decode('utf-8'), self.domain, ) # re-submit form result = submit_form_locally( instance=form.get_xml(), domain=self.domain, ) duplicate_form = result.xform self.assertTrue(duplicate_form.is_duplicate) case = self.casedb.get_case(case_id) self.assertIsNotNone(case) form = self.formdb.get_form(form_id) self.assertTrue(form.is_normal) self.assertIsNone(getattr(form, 'problem', None)) self.assertEqual(duplicate_form.orig_id, form.form_id)
class EditFormTest(TestCase, TestFileMixin): ID = '7H46J37FGH3' domain = 'test-form-edits' file_path = ('data', 'deprecation') root = os.path.dirname(__file__) def setUp(self): super(EditFormTest, self).setUp() self.interface = FormProcessorInterface(self.domain) self.casedb = CaseAccessors(self.domain) self.formdb = FormAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_xforms(self.domain) FormProcessorTestUtils.delete_all_cases(self.domain) UnfinishedSubmissionStub.objects.all().delete() super(EditFormTest, self).tearDown() @run_with_all_backends def test_basic_edit(self): original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') xform = post_xform(original_xml, domain=self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("", xform.form_data['vitals']['height']) self.assertEqual("other", xform.form_data['assessment']['categories']) xform = post_xform(edit_xml, domain=self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("100", xform.form_data['vitals']['height']) self.assertEqual("Edited Baby!", xform.form_data['assessment']['categories']) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertEqual(self.ID, deprecated_xform.orig_id) self.assertNotEqual(self.ID, deprecated_xform.form_id) self.assertTrue(deprecated_xform.is_deprecated) self.assertEqual("", deprecated_xform.form_data['vitals']['height']) self.assertEqual( "other", deprecated_xform.form_data['assessment']['categories']) self.assertEqual(xform.received_on, deprecated_xform.received_on) self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id) self.assertTrue(xform.edited_on > deprecated_xform.received_on) self.assertEqual(deprecated_xform.get_xml(), original_xml) self.assertEqual(xform.get_xml(), edit_xml) @run_with_all_backends def test_broken_save(self): """ Test that if the second form submission terminates unexpectedly and the main form isn't saved, then there are no side effects such as the original having been marked as deprecated. """ original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') _, xform, _ = submit_form_locally(original_xml, self.domain) self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual(self.domain, xform.domain) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 0) with patch.object(self.interface.processor, 'save_processed_models', side_effect=RequestFailed): with self.assertRaises(RequestFailed): submit_form_locally(edit_xml, self.domain) # it didn't go through, so make sure there are no edits still self.assertIsNone(getattr(xform, 'deprecated_form_id', None)) xform = self.formdb.get_form(self.ID) self.assertIsNotNone(xform) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID, saved=False).count(), 1) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 1) @run_with_all_backends def test_case_management(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex case_block = CaseBlock(create=True, case_id=case_id, case_type='person', owner_id=owner_id, update={ 'property': 'original value' }).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # validate some assumptions case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'person') self.assertEqual(case.dynamic_case_properties()['property'], 'original value') self.assertEqual([form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) # submit a new form with a different case update case_block = CaseBlock(create=True, case_id=case_id, case_type='newtype', owner_id=owner_id, update={ 'property': 'edited value' }).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'newtype') self.assertEqual(case.dynamic_case_properties()['property'], 'edited value') self.assertEqual([form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) @run_with_all_backends def test_second_edit_fails(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex case_block = CaseBlock( create=True, case_id=case_id, case_type='person', ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # submit an edit form with a bad case update (for example a bad ID) case_block = CaseBlock( create=True, case_id='', case_type='person', ).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) xform = self.formdb.get_form(form_id) self.assertTrue(xform.is_error) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertTrue(deprecated_xform.is_deprecated) @run_with_all_backends def test_case_management_ordering(self): case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex # create a case case_block = CaseBlock( create=True, case_id=case_id, case_type='person', owner_id=owner_id, ).as_string() create_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual([create_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertTrue( create_form_id in [a.xform_id for a in case.actions]) for a in case.actions: self.assertEqual(create_form_id, a.xform_id) edit_date = datetime.utcnow() # set some property value case_block = CaseBlock(create=False, case_id=case_id, date_modified=edit_date, update={ 'property': 'first value', }).as_string() edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'first value') self.assertEqual([create_form_id, edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertTrue( all(form_id in [a.xform_id for a in case.actions] for form_id in [create_form_id, edit_form_id])) # submit a second (new) form updating the value case_block = CaseBlock(create=False, case_id=case_id, update={ 'property': 'final value', }).as_string() second_edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertTrue( all(form_id in [a.xform_id for a in case.actions] for form_id in [create_form_id, edit_form_id, second_edit_form_id])) # deprecate the middle edit case_block = CaseBlock( create=False, case_id=case_id, date_modified= edit_date, # need to use the previous edit date for action sort comparisons update={ 'property': 'edited value', 'added_property': 'added value', }).as_string() submit_case_blocks(case_block, domain=self.domain, form_id=edit_form_id) # ensure that the middle edit stays in the right place and is applied # before the final one case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual(case.dynamic_case_properties()['added_property'], 'added value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertEqual([ create_form_id, create_form_id, edit_form_id, second_edit_form_id ], [a.xform_id for a in case.actions]) def test_couch_blob_migration_edit(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex case_block = CaseBlock(create=True, case_id=case_id).as_string() xform = submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # explicitly convert to old-style couch attachments to test the migration workflow form_xml = xform.get_xml() xform.delete_attachment('form.xml') xform.get_db().put_attachment(xform.to_json(), form_xml, 'form.xml') # make sure that worked updated_form_xml = XFormInstance.get(xform._id).get_xml() self.assertEqual(form_xml, updated_form_xml) # this call was previously failing updated_form = submit_case_blocks(case_block, domain=self.domain, form_id=form_id) self.assertEqual( form_xml, XFormInstance.get(updated_form.deprecated_form_id).get_xml())
class TestFormArchiving(TestCase, TestFileMixin): file_path = ('data', 'sample_xforms') root = os.path.dirname(__file__) def setUp(self): super(TestFormArchiving, self).setUp() self.casedb = CaseAccessors('test-domain') self.formdb = FormAccessors('test-domain') def tearDown(self): FormProcessorTestUtils.delete_all_xforms() FormProcessorTestUtils.delete_all_cases() super(TestFormArchiving, self).tearDown() def testArchive(self): case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.archive(user_id='mr. librarian') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_archived) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.xform_ids, []) [archival] = xform.history self.assertTrue(lower_bound <= archival.date <= upper_bound) self.assertEqual('archive', archival.operation) self.assertEqual('mr. librarian', archival.user) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.unarchive(user_id='mr. researcher') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_normal) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(case.xform_ids, [xform.form_id]) [archival, restoration] = xform.history self.assertTrue(lower_bound <= restoration.date <= upper_bound) self.assertEqual('unarchive', restoration.operation) self.assertEqual('mr. researcher', restoration.user) def testUnfinishedArchiveStub(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the archive function throwing an error with mock.patch('couchforms.signals.xform_archived.send') as mock_send: try: mock_send.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # Get the form with the updated history, it should be archived xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case associated with the form should still exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # There should be a stub for the unfinished archive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The case and stub should both be deleted now case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnfinishedUnarchiveStub(self): # Test running the celery task reprocess_archive_stubs on an existing unarchive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the unarchive function throwing an error with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send: try: mock_send.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # Make sure the history only has an archive and an unarchive xform = self.formdb.get_form(xform.form_id) self.assertEqual(2, len(xform.history)) self.assertFalse(xform.is_archived) self.assertEqual('archive', xform.history[0].operation) self.assertEqual('librarian', xform.history[0].user) self.assertEqual('unarchive', xform.history[1].operation) self.assertEqual('librarian', xform.history[1].user) # The case should not exist because the unarchived form was not rebuilt case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # There should be a stub for the unfinished unarchive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The case should be back, and the stub should be deleted now case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnarchivingWithArchiveStub(self): # Test a user-initiated unarchive with an existing archive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the archive function throwing an error with mock.patch('couchforms.signals.xform_archived.send') as mock_send: try: mock_send.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # There should be a stub for the unfinished archive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Call an unarchive xform.unarchive(user_id='librarian') # The unfinished archive stub should be deleted unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 0) # The case should exist because the case close was unarchived case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the case still exists (to double check that the archive stub was deleted) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) def testArchivingWithUnarchiveStub(self): # Test a user-initiated archive with an existing unarchive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the unarchive function throwing an error with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send: try: mock_send.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # There should be a stub for the unfinished unarchive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Call an archive xform.archive(user_id='librarian') # The unfinished archive stub should be deleted unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 0) # The case should not exist because the case close was archived case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The history should not have been added to, make sure that it still only has one entry # Make sure the case still does not exist (to double check that the unarchive stub was deleted) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) def testUnfinishedArchiveStubErrorAddingHistory(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive # initially failed on updating the history case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the couch and sql archive function throwing an error (so that this test works for both) with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.' 'archive_form') \ as mock_operation_sql: with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch: try: mock_operation_sql.side_effect = Exception mock_operation_couch.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # Get the form with the updated history, make sure it has not been archived yet xform = self.formdb.get_form(xform.form_id) self.assertEqual(0, len(xform.history)) self.assertFalse(xform.is_archived) # The case associated with the form should still exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # There should be a stub for the unfinished archive, and the history should not be updated yet unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, False) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the history shows an archive now xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case and stub should both be deleted now case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnfinishedUnarchiveStubErrorAddingHistory(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive # initially failed on updating the history case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the couch and sql archive function throwing an error (so that this test works for both) with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.' 'unarchive_form') \ as mock_operation_sql: with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch: try: mock_operation_sql.side_effect = Exception mock_operation_couch.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # Get the form with the updated history, make sure it only has one entry (the archive) xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case associated with the form should not exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # There should be a stub for the unfinished archive, and the history should not be updated yet unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, False) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the history shows an archive and an unarchive now xform = self.formdb.get_form(xform.form_id) self.assertEqual(2, len(xform.history)) self.assertFalse(xform.is_archived) self.assertEqual('archive', xform.history[0].operation) self.assertEqual('librarian', xform.history[0].user) self.assertEqual('unarchive', xform.history[1].operation) self.assertEqual('librarian', xform.history[1].user) # The case should be back, and the stub should be deleted now case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testSignal(self): global archive_counter, restore_counter archive_counter = 0 restore_counter = 0 def count_archive(**kwargs): global archive_counter archive_counter += 1 def count_unarchive(**kwargs): global restore_counter restore_counter += 1 xform_archived.connect(count_archive) xform_unarchived.connect(count_unarchive) xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) self.assertEqual(0, archive_counter) self.assertEqual(0, restore_counter) result.xform.archive() self.assertEqual(1, archive_counter) self.assertEqual(0, restore_counter) xform = self.formdb.get_form(result.xform.form_id) xform.unarchive() self.assertEqual(1, archive_counter) self.assertEqual(1, restore_counter)
class TestFormArchiving(TestCase, TestFileMixin): file_path = ('data', 'sample_xforms') root = os.path.dirname(__file__) def setUp(self): super(TestFormArchiving, self).setUp() self.casedb = CaseAccessors('test-domain') self.formdb = FormAccessors('test-domain') def tearDown(self): FormProcessorTestUtils.delete_all_xforms() FormProcessorTestUtils.delete_all_cases() super(TestFormArchiving, self).tearDown() @run_with_all_backends def testArchive(self): case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.archive(user_id='mr. librarian') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_archived) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.xform_ids, []) [archival] = xform.history self.assertTrue(lower_bound <= archival.date <= upper_bound) self.assertEqual('archive', archival.operation) self.assertEqual('mr. librarian', archival.user) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.unarchive(user_id='mr. researcher') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_normal) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(case.xform_ids, [xform.form_id]) [archival, restoration] = xform.history self.assertTrue(lower_bound <= restoration.date <= upper_bound) self.assertEqual('unarchive', restoration.operation) self.assertEqual('mr. researcher', restoration.user) @run_with_all_backends def testSignal(self): global archive_counter, restore_counter archive_counter = 0 restore_counter = 0 def count_archive(**kwargs): global archive_counter archive_counter += 1 def count_unarchive(**kwargs): global restore_counter restore_counter += 1 xform_archived.connect(count_archive) xform_unarchived.connect(count_unarchive) xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) self.assertEqual(0, archive_counter) self.assertEqual(0, restore_counter) xform.archive() self.assertEqual(1, archive_counter) self.assertEqual(0, restore_counter) xform = self.formdb.get_form(xform.form_id) xform.unarchive() self.assertEqual(1, archive_counter) self.assertEqual(1, restore_counter) @override_settings(TESTS_SHOULD_USE_SQL_BACKEND=True) def testPublishChanges(self): xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) with capture_kafka_changes_context(topics.FORM_SQL) as change_context: with drop_connected_signals(xform_archived): xform.archive() self.assertEqual(1, len(change_context.changes)) self.assertEqual(change_context.changes[0].id, xform.form_id) xform = self.formdb.get_form(xform.form_id) with capture_kafka_changes_context(topics.FORM_SQL) as change_context: with drop_connected_signals(xform_unarchived): xform.unarchive() self.assertEqual(1, len(change_context.changes)) self.assertEqual(change_context.changes[0].id, xform.form_id)
class TestHardDelete(TestCase): def setUp(self): self.casedb = CaseAccessors(TEST_DOMAIN_NAME) self.formdb = FormAccessors(TEST_DOMAIN_NAME) @run_with_all_backends def test_simple_delete(self): factory = CaseFactory() case = factory.create_case() [case] = factory.create_or_update_case( CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}}) ) self.assertIsNotNone(self.casedb.get_case(case.case_id)) self.assertEqual(2, len(case.xform_ids)) for form_id in case.xform_ids: self.assertIsNotNone(self.formdb.get_form(form_id)) with capture_kafka_changes_context(topics.FORM_SQL, topics.CASE_SQL) as change_context: safe_hard_delete(case) if should_use_sql_backend(case.domain): self.assertEqual(3, len(change_context.changes)) expected_ids = {case.case_id} | set(case.xform_ids) self.assertEqual(expected_ids, {change.id for change in change_context.changes}) for change in change_context.changes: self.assertTrue(change.deleted) with self.assertRaises(CaseNotFound): self.casedb.get_case(case.case_id) for form_id in case.xform_ids: with self.assertRaises(XFormNotFound): self.formdb.get_form(form_id) @run_with_all_backends def test_delete_with_related(self): factory = CaseFactory() parent = factory.create_case() [child] = factory.create_or_update_case( CaseStructure(attrs={'create': True}, walk_related=False, indices=[ CaseIndex(CaseStructure(case_id=parent.case_id)) ]), ) # deleting the parent should not be allowed because the child still references it with self.assertRaises(CommCareCaseError): safe_hard_delete(parent) # deleting the child is ok safe_hard_delete(child) self.assertIsNotNone(self.casedb.get_case(parent.case_id)) with self.assertRaises(CaseNotFound): self.casedb.get_case(child.case_id) @run_with_all_backends def test_delete_sharing_form(self): factory = CaseFactory() c1, c2 = factory.create_or_update_cases([ CaseStructure(attrs={'create': True}), CaseStructure(attrs={'create': True}), ]) with self.assertRaises(CommCareCaseError): safe_hard_delete(c1) with self.assertRaises(CommCareCaseError): safe_hard_delete(c2) self.assertIsNotNone(self.casedb.get_case(c1.case_id)) self.assertIsNotNone(self.casedb.get_case(c2.case_id))
class TestReprocessDuringSubmission(TestCase): @classmethod def setUpClass(cls): super(TestReprocessDuringSubmission, cls).setUpClass() cls.domain = uuid.uuid4().hex def setUp(self): super(TestReprocessDuringSubmission, self).setUp() self.factory = CaseFactory(domain=self.domain) self.formdb = FormAccessors(self.domain) self.casedb = CaseAccessors(self.domain) self.ledgerdb = LedgerAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) super(TestReprocessDuringSubmission, self).tearDown() def test_error_saving(self): case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex with _patch_save_to_raise_error(self): submit_case_blocks(CaseBlock(case_id=case_id, create=True, case_type='box').as_string(), self.domain, form_id=form_id) stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all() self.assertEqual(1, len(stubs)) form = self.formdb.get_form(form_id) self.assertTrue(form.is_error) with self.assertRaises(CaseNotFound): self.casedb.get_case(case_id) result = submit_form_locally( instance=form.get_xml(), domain=self.domain, ) duplicate_form = result.xform self.assertTrue(duplicate_form.is_duplicate) case = self.casedb.get_case(case_id) self.assertIsNotNone(case) form = self.formdb.get_form(form_id) self.assertTrue(form.is_normal) self.assertIsNone(getattr(form, 'problem', None)) self.assertEqual(duplicate_form.orig_id, form.form_id) def test_processing_error(self): case_id = uuid.uuid4().hex parent_case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex form, _ = submit_case_blocks(CaseBlock( case_id=case_id, create=True, case_type='box', index={ 'cupboard': ('cupboard', parent_case_id) }, ).as_string(), self.domain, form_id=form_id) self.assertTrue(form.is_error) self.assertTrue('InvalidCaseIndex' in form.problem) self.assertEqual(form.form_id, form_id) with self.assertRaises(CaseNotFound): self.casedb.get_case(case_id) stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all() self.assertEqual(0, len(stubs)) # create parent case submit_case_blocks( CaseBlock(case_id=parent_case_id, create=True, case_type='cupboard').as_string(), self.domain, ) # re-submit form result = submit_form_locally( instance=form.get_xml(), domain=self.domain, ) duplicate_form = result.xform self.assertTrue(duplicate_form.is_duplicate) case = self.casedb.get_case(case_id) self.assertIsNotNone(case) form = self.formdb.get_form(form_id) self.assertTrue(form.is_normal) self.assertIsNone(getattr(form, 'problem', None)) self.assertEqual(duplicate_form.orig_id, form.form_id)
class TestFormArchiving(TestCase, TestFileMixin): file_path = ('data', 'sample_xforms') root = os.path.dirname(__file__) def setUp(self): super(TestFormArchiving, self).setUp() self.casedb = CaseAccessors('test-domain') self.formdb = FormAccessors('test-domain') def tearDown(self): FormProcessorTestUtils.delete_all_xforms() FormProcessorTestUtils.delete_all_cases() super(TestFormArchiving, self).tearDown() def testArchive(self): case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.archive(user_id='mr. librarian') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_archived) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.xform_ids, []) [archival] = xform.history self.assertTrue(lower_bound <= archival.date <= upper_bound) self.assertEqual('archive', archival.operation) self.assertEqual('mr. librarian', archival.user) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.unarchive(user_id='mr. researcher') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_normal) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(case.xform_ids, [xform.form_id]) [archival, restoration] = xform.history self.assertTrue(lower_bound <= restoration.date <= upper_bound) self.assertEqual('unarchive', restoration.operation) self.assertEqual('mr. researcher', restoration.user) def testSignal(self): global archive_counter, restore_counter archive_counter = 0 restore_counter = 0 def count_archive(**kwargs): global archive_counter archive_counter += 1 def count_unarchive(**kwargs): global restore_counter restore_counter += 1 xform_archived.connect(count_archive) xform_unarchived.connect(count_unarchive) xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) self.assertEqual(0, archive_counter) self.assertEqual(0, restore_counter) result.xform.archive() self.assertEqual(1, archive_counter) self.assertEqual(0, restore_counter) xform = self.formdb.get_form(result.xform.form_id) xform.unarchive() self.assertEqual(1, archive_counter) self.assertEqual(1, restore_counter)
class EditFormTest(TestCase, TestFileMixin): ID = '7H46J37FGH3' domain = 'test-form-edits' file_path = ('data', 'deprecation') root = os.path.dirname(__file__) def setUp(self): super(EditFormTest, self).setUp() self.interface = FormProcessorInterface(self.domain) self.casedb = CaseAccessors(self.domain) self.formdb = FormAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_xforms(self.domain) FormProcessorTestUtils.delete_all_cases(self.domain) UnfinishedSubmissionStub.objects.all().delete() super(EditFormTest, self).tearDown() def test_basic_edit(self): original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') xform = submit_form_locally(original_xml, self.domain).xform self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("", xform.form_data['vitals']['height']) self.assertEqual("other", xform.form_data['assessment']['categories']) xform = submit_form_locally(edit_xml, self.domain).xform self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual("100", xform.form_data['vitals']['height']) self.assertEqual("Edited Baby!", xform.form_data['assessment']['categories']) self.assertEqual(1, len(xform.history)) self.assertEqual('edit', xform.history[0].operation) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertEqual(self.ID, deprecated_xform.orig_id) self.assertNotEqual(self.ID, deprecated_xform.form_id) self.assertTrue(deprecated_xform.is_deprecated) self.assertEqual("", deprecated_xform.form_data['vitals']['height']) self.assertEqual( "other", deprecated_xform.form_data['assessment']['categories']) self.assertEqual(xform.received_on, deprecated_xform.received_on) self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id) self.assertTrue(xform.edited_on > deprecated_xform.received_on) self.assertEqual(deprecated_xform.get_xml().decode('utf-8'), original_xml.decode('utf-8')) self.assertEqual(xform.get_xml().decode('utf-8'), edit_xml.decode('utf-8')) def test_edit_form_with_attachments(self): attachment_source = './corehq/ex-submodules/casexml/apps/case/tests/data/attachments/fruity.jpg' attachment_file = open(attachment_source, 'rb') attachments = { 'fruity_file': UploadedFile(attachment_file, 'fruity_file', content_type='image/jpeg') } def _get_xml(date, form_id): return """<?xml version='1.0' ?> <data uiVersion="1" version="1" name="" xmlns="http://openrosa.org/formdesigner/123"> <name>fgg</name> <date>2011-06-07</date> <n1:meta xmlns:n1="http://openrosa.org/jr/xforms"> <n1:deviceID>354957031935664</n1:deviceID> <n1:timeStart>{date}</n1:timeStart> <n1:timeEnd>{date}</n1:timeEnd> <n1:username>bcdemo</n1:username> <n1:userID>user-abc</n1:userID> <n1:instanceID>{form_id}</n1:instanceID> </n1:meta> </data>""".format(date=date, attachment_source=attachment_source, form_id=form_id) form_id = uuid.uuid4().hex original_xml = _get_xml('2016-03-01T12:04:16Z', form_id) submit_form_locally( original_xml, self.domain, attachments=attachments, ) form = self.formdb.get_form(form_id) self.assertIn('fruity_file', form.attachments) self.assertIn(original_xml, form.get_xml().decode('utf-8')) # edit form edit_xml = _get_xml('2016-04-01T12:04:16Z', form_id) submit_form_locally( edit_xml, self.domain, ) form = self.formdb.get_form(form_id) self.assertIsNotNone(form.edited_on) self.assertIsNotNone(form.deprecated_form_id) self.assertIn('fruity_file', form.attachments) self.assertIn(edit_xml, form.get_xml().decode('utf-8')) def test_edit_an_error(self): form_id = uuid.uuid4().hex case_block = CaseBlock( create=True, case_id='', # this should cause the submission to error case_type='person', owner_id='some-owner', ) form, _ = submit_case_blocks(case_block.as_text(), domain=self.domain, form_id=form_id) self.assertTrue(form.is_error) self.assertTrue('IllegalCaseId' in form.problem) case_block.case_id = uuid.uuid4().hex form, _ = submit_case_blocks(case_block.as_text(), domain=self.domain, form_id=form_id) self.assertFalse(form.is_error) self.assertEqual(None, getattr(form, 'problem', None)) def test_broken_save(self): """ Test that if the second form submission terminates unexpectedly and the main form isn't saved, then there are no side effects such as the original having been marked as deprecated. """ original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') result = submit_form_locally(original_xml, self.domain) xform = result.xform self.assertEqual(self.ID, xform.form_id) self.assertTrue(xform.is_normal) self.assertEqual(self.domain, xform.domain) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 0) with patch.object(self.interface.processor, 'save_processed_models', side_effect=HTTPError): with self.assertRaises(HTTPError): submit_form_locally(edit_xml, self.domain) xform = self.formdb.get_form(self.ID) self.assertIsNotNone(xform) # it didn't go through, so make sure there are no edits still self.assertIsNone(getattr(xform, 'deprecated_form_id', None)) self.assertEqual( UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(), 0) def test_case_management(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex case_block = CaseBlock(create=True, case_id=case_id, case_type='person', owner_id=owner_id, update={ 'property': 'original value' }).as_text() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # validate some assumptions case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'person') self.assertEqual(case.dynamic_case_properties()['property'], 'original value') self.assertEqual([form_id], case.xform_ids) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) # submit a new form with a different case update case_block = CaseBlock(create=True, case_id=case_id, case_type='newtype', owner_id=owner_id, update={ 'property': 'edited value' }).as_text() xform, _ = submit_case_blocks(case_block, domain=self.domain, form_id=form_id) case = self.casedb.get_case(case_id) self.assertEqual(case.type, 'newtype') self.assertEqual(case.dynamic_case_properties()['property'], 'edited value') self.assertEqual([form_id], case.xform_ids) self.assertEqual(case.server_modified_on, xform.edited_on) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertEqual(2, len(case.actions)) for a in case.actions: self.assertEqual(form_id, a.xform_id) def test_second_edit_fails(self): form_id = uuid.uuid4().hex case_id = uuid.uuid4().hex case_block = CaseBlock( create=True, case_id=case_id, case_type='person', ).as_text() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) # submit an edit form with a bad case update (for example a bad ID) case_block = CaseBlock( create=True, case_id='', case_type='person', ).as_text() submit_case_blocks(case_block, domain=self.domain, form_id=form_id) xform = self.formdb.get_form(form_id) self.assertTrue(xform.is_error) deprecated_xform = self.formdb.get_form(xform.deprecated_form_id) self.assertTrue(deprecated_xform.is_deprecated) def test_case_management_ordering(self): case_id = uuid.uuid4().hex owner_id = uuid.uuid4().hex # create a case case_block = CaseBlock( create=True, case_id=case_id, case_type='person', owner_id=owner_id, ).as_text() create_form_id = submit_case_blocks(case_block, domain=self.domain)[0].form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual([create_form_id], case.xform_ids) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertTrue( create_form_id in [a.xform_id for a in case.actions]) for a in case.actions: self.assertEqual(create_form_id, a.xform_id) edit_date = datetime.utcnow() # set some property value case_block = CaseBlock(create=False, case_id=case_id, date_modified=edit_date, update={ 'property': 'first value', }).as_text() edit_form_id = submit_case_blocks(case_block, domain=self.domain)[0].form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'first value') self.assertEqual([create_form_id, edit_form_id], case.xform_ids) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertTrue( all(form_id in [a.xform_id for a in case.actions] for form_id in [create_form_id, edit_form_id])) # submit a second (new) form updating the value case_block = CaseBlock(create=False, case_id=case_id, update={ 'property': 'final value', }).as_text() second_edit_form_id = submit_case_blocks(case_block, domain=self.domain)[0].form_id # validate that worked case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertTrue( all(form_id in [a.xform_id for a in case.actions] for form_id in [create_form_id, edit_form_id, second_edit_form_id])) # deprecate the middle edit case_block = CaseBlock( create=False, case_id=case_id, date_modified= edit_date, # need to use the previous edit date for action sort comparisons update={ 'property': 'edited value', 'added_property': 'added value', }).as_text() submit_case_blocks(case_block, domain=self.domain, form_id=edit_form_id) # ensure that the middle edit stays in the right place and is applied # before the final one case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties()['property'], 'final value') self.assertEqual(case.dynamic_case_properties()['added_property'], 'added value') self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids) if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False): self.assertEqual([ create_form_id, create_form_id, edit_form_id, second_edit_form_id ], [a.xform_id for a in case.actions]) def test_edit_different_xmlns(self): form_id = uuid.uuid4().hex case1_id = uuid.uuid4().hex case2_id = uuid.uuid4().hex xmlns1 = 'http://commcarehq.org/xmlns1' xmlns2 = 'http://commcarehq.org/xmlns2' case_block = CaseBlock( create=True, case_id=case1_id, case_type='person', owner_id='owner1', ).as_text() xform, cases = submit_case_blocks(case_block, domain=self.domain, xmlns=xmlns1, form_id=form_id) self.assertTrue(xform.is_normal) self.assertEqual(form_id, xform.form_id) case_block = CaseBlock( create=True, case_id=case2_id, case_type='goat', owner_id='owner1', ).as_text() # submit new form with same form ID but different XMLNS xform, cases = submit_case_blocks(case_block, domain=self.domain, xmlns=xmlns2, form_id=form_id) self.assertTrue(xform.is_normal) self.assertNotEqual(form_id, xform.form_id) # form should have a different ID def test_copy_operations(self): original_xml = self.get_xml('original') edit_xml = self.get_xml('edit') xform = submit_form_locally(original_xml, self.domain).xform xform.archive(user_id='user1') xform.unarchive(user_id='user2') xform = submit_form_locally(edit_xml, self.domain).xform self.assertEqual(3, len(xform.history)) self.assertEqual('archive', xform.history[0].operation) self.assertEqual('unarchive', xform.history[1].operation) self.assertEqual('edit', xform.history[2].operation)
class TestFormArchiving(TestCase, TestFileMixin): file_path = ('data', 'sample_xforms') root = os.path.dirname(__file__) def setUp(self): self.casedb = CaseAccessors('test-domain') self.formdb = FormAccessors('test-domain') def tearDown(self): FormProcessorTestUtils.delete_all_xforms() FormProcessorTestUtils.delete_all_cases() @run_with_all_backends def testArchive(self): case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.archive(user_id='mr. librarian') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_archived) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.xform_ids, []) [archival] = xform.history self.assertTrue(lower_bound <= archival.date <= upper_bound) self.assertEqual('archive', archival.operation) self.assertEqual('mr. librarian', archival.user) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.unarchive(user_id='mr. researcher') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_normal) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(case.xform_ids, [xform.form_id]) [archival, restoration] = xform.history self.assertTrue(lower_bound <= restoration.date <= upper_bound) self.assertEqual('unarchive', restoration.operation) self.assertEqual('mr. researcher', restoration.user) @run_with_all_backends def testSignal(self): global archive_counter, restore_counter archive_counter = 0 restore_counter = 0 def count_archive(**kwargs): global archive_counter archive_counter += 1 def count_unarchive(**kwargs): global restore_counter restore_counter += 1 xform_archived.connect(count_archive) xform_unarchived.connect(count_unarchive) xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) self.assertEqual(0, archive_counter) self.assertEqual(0, restore_counter) xform.archive() self.assertEqual(1, archive_counter) self.assertEqual(0, restore_counter) xform = self.formdb.get_form(xform.form_id) xform.unarchive() self.assertEqual(1, archive_counter) self.assertEqual(1, restore_counter) @override_settings(TESTS_SHOULD_USE_SQL_BACKEND=True) def testPublishChanges(self): xml_data = self.get_xml('basic') response, xform, cases = submit_form_locally( xml_data, 'test-domain', ) with capture_kafka_changes_context(topics.FORM_SQL) as change_context: with drop_connected_signals(xform_archived): xform.archive() self.assertEqual(1, len(change_context.changes)) self.assertEqual(change_context.changes[0].id, xform.form_id) xform = self.formdb.get_form(xform.form_id) with capture_kafka_changes_context(topics.FORM_SQL) as change_context: with drop_connected_signals(xform_unarchived): xform.unarchive() self.assertEqual(1, len(change_context.changes)) self.assertEqual(change_context.changes[0].id, xform.form_id)
class TestFormArchiving(TestCase, TestFileMixin): file_path = ('data', 'sample_xforms') root = os.path.dirname(__file__) def setUp(self): super(TestFormArchiving, self).setUp() self.casedb = CaseAccessors('test-domain') self.formdb = FormAccessors('test-domain') def tearDown(self): FormProcessorTestUtils.delete_all_xforms() FormProcessorTestUtils.delete_all_cases() super(TestFormArchiving, self).tearDown() def testArchive(self): case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.archive(user_id='mr. librarian') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_archived) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.xform_ids, []) [archival] = xform.history self.assertTrue(lower_bound <= archival.date <= upper_bound) self.assertEqual('archive', archival.operation) self.assertEqual('mr. librarian', archival.user) lower_bound = datetime.utcnow() - timedelta(seconds=1) xform.unarchive(user_id='mr. researcher') upper_bound = datetime.utcnow() + timedelta(seconds=1) xform = self.formdb.get_form(xform.form_id) self.assertTrue(xform.is_normal) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(case.xform_ids, [xform.form_id]) [archival, restoration] = xform.history self.assertTrue(lower_bound <= restoration.date <= upper_bound) self.assertEqual('unarchive', restoration.operation) self.assertEqual('mr. researcher', restoration.user) def testUnfinishedArchiveStub(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the archive function throwing an error with mock.patch('couchforms.signals.xform_archived.send') as mock_send: try: mock_send.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # Get the form with the updated history, it should be archived xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case associated with the form should still exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # There should be a stub for the unfinished archive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The case and stub should both be deleted now case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter( ) self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnfinishedUnarchiveStub(self): # Test running the celery task reprocess_archive_stubs on an existing unarchive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the unarchive function throwing an error with mock.patch( 'couchforms.signals.xform_unarchived.send') as mock_send: try: mock_send.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # Make sure the history only has an archive and an unarchive xform = self.formdb.get_form(xform.form_id) self.assertEqual(2, len(xform.history)) self.assertFalse(xform.is_archived) self.assertEqual('archive', xform.history[0].operation) self.assertEqual('librarian', xform.history[0].user) self.assertEqual('unarchive', xform.history[1].operation) self.assertEqual('librarian', xform.history[1].user) # The case should not exist because the unarchived form was not rebuilt case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # There should be a stub for the unfinished unarchive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The case should be back, and the stub should be deleted now case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter( ) self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnarchivingWithArchiveStub(self): # Test a user-initiated unarchive with an existing archive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the archive function throwing an error with mock.patch('couchforms.signals.xform_archived.send') as mock_send: try: mock_send.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # There should be a stub for the unfinished archive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Call an unarchive xform.unarchive(user_id='librarian') # The unfinished archive stub should be deleted unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 0) # The case should exist because the case close was unarchived case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the case still exists (to double check that the archive stub was deleted) case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) def testArchivingWithUnarchiveStub(self): # Test a user-initiated archive with an existing unarchive stub case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the unarchive function throwing an error with mock.patch( 'couchforms.signals.xform_unarchived.send') as mock_send: try: mock_send.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # There should be a stub for the unfinished unarchive unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, True) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Call an archive xform.archive(user_id='librarian') # The unfinished archive stub should be deleted unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 0) # The case should not exist because the case close was archived case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # The history should not have been added to, make sure that it still only has one entry # Make sure the case still does not exist (to double check that the unarchive stub was deleted) case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) def testUnfinishedArchiveStubErrorAddingHistory(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive # initially failed on updating the history case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Mock the couch and sql archive function throwing an error (so that this test works for both) with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.' 'archive_form') \ as mock_operation_sql: with mock.patch('couchforms.models.XFormOperation' ) as mock_operation_couch: try: mock_operation_sql.side_effect = Exception mock_operation_couch.side_effect = Exception xform.archive(user_id='librarian') except Exception: pass # Get the form with the updated history, make sure it has not been archived yet xform = self.formdb.get_form(xform.form_id) self.assertEqual(0, len(xform.history)) self.assertFalse(xform.is_archived) # The case associated with the form should still exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) # There should be a stub for the unfinished archive, and the history should not be updated yet unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, False) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, True) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the history shows an archive now xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case and stub should both be deleted now case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter( ) self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testUnfinishedUnarchiveStubErrorAddingHistory(self): # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive # initially failed on updating the history case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169' xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) xform = result.xform self.assertTrue(xform.is_normal) self.assertEqual(0, len(xform.history)) # Archive the form successfully xform.archive(user_id='librarian') # Mock the couch and sql archive function throwing an error (so that this test works for both) with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.' 'unarchive_form') \ as mock_operation_sql: with mock.patch('couchforms.models.XFormOperation' ) as mock_operation_couch: try: mock_operation_sql.side_effect = Exception mock_operation_couch.side_effect = Exception xform.unarchive(user_id='librarian') except Exception: pass # Get the form with the updated history, make sure it only has one entry (the archive) xform = self.formdb.get_form(xform.form_id) self.assertEqual(1, len(xform.history)) self.assertTrue(xform.is_archived) [archival] = xform.history self.assertEqual('archive', archival.operation) self.assertEqual('librarian', archival.user) # The case associated with the form should not exist, it was not rebuilt because of the exception case = self.casedb.get_case(case_id) self.assertTrue(case.is_deleted) # There should be a stub for the unfinished archive, and the history should not be updated yet unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter() self.assertEqual(len(unfinished_archive_stubs), 1) self.assertEqual(unfinished_archive_stubs[0].history_updated, False) self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian') self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain') self.assertEqual(unfinished_archive_stubs[0].archive, False) # Manually call the periodic celery task that reruns archiving/unarchiving actions reprocess_archive_stubs() # Make sure the history shows an archive and an unarchive now xform = self.formdb.get_form(xform.form_id) self.assertEqual(2, len(xform.history)) self.assertFalse(xform.is_archived) self.assertEqual('archive', xform.history[0].operation) self.assertEqual('librarian', xform.history[0].user) self.assertEqual('unarchive', xform.history[1].operation) self.assertEqual('librarian', xform.history[1].user) # The case should be back, and the stub should be deleted now case = self.casedb.get_case(case_id) self.assertFalse(case.is_deleted) unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter( ) self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0) def testSignal(self): global archive_counter, restore_counter archive_counter = 0 restore_counter = 0 def count_archive(**kwargs): global archive_counter archive_counter += 1 def count_unarchive(**kwargs): global restore_counter restore_counter += 1 xform_archived.connect(count_archive) xform_unarchived.connect(count_unarchive) xml_data = self.get_xml('basic') result = submit_form_locally( xml_data, 'test-domain', ) self.assertEqual(0, archive_counter) self.assertEqual(0, restore_counter) result.xform.archive() self.assertEqual(1, archive_counter) self.assertEqual(0, restore_counter) xform = self.formdb.get_form(result.xform.form_id) xform.unarchive() self.assertEqual(1, archive_counter) self.assertEqual(1, restore_counter)
class ReprocessSubmissionStubTests(TestCase): @classmethod def setUpClass(cls): super(ReprocessSubmissionStubTests, cls).setUpClass() cls.domain = uuid.uuid4().hex cls.product = SQLProduct.objects.create(domain=cls.domain, product_id='product1', name='product1') @classmethod def tearDownClass(cls): cls.product.delete() super(ReprocessSubmissionStubTests, cls).tearDownClass() def setUp(self): super(ReprocessSubmissionStubTests, self).setUp() self.factory = CaseFactory(domain=self.domain) self.formdb = FormAccessors(self.domain) self.casedb = CaseAccessors(self.domain) self.ledgerdb = LedgerAccessors(self.domain) def tearDown(self): FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain) super(ReprocessSubmissionStubTests, self).tearDown() 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 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) with _patch_save_to_raise_error(self): 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 = 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(), 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) def test_fire_signals(self): from corehq.apps.receiverwrapper.tests.test_submit_errors import failing_signal_handler case_id = uuid.uuid4().hex form_id = uuid.uuid4().hex with failing_signal_handler('signal death'): submit_case_blocks(CaseBlock(case_id=case_id, create=True, case_type='box').as_string(), self.domain, form_id=form_id) form = self.formdb.get_form(form_id) with catch_signal( successful_form_received) as form_handler, catch_signal( case_post_save) as case_handler: submit_form_locally( instance=form.get_xml(), domain=self.domain, ) case = self.casedb.get_case(case_id) if should_use_sql_backend(self.domain): self.assertEqual(form, form_handler.call_args[1]['xform']) self.assertEqual(case, case_handler.call_args[1]['case']) else: signal_form = form_handler.call_args[1]['xform'] self.assertEqual(form.form_id, signal_form.form_id) self.assertEqual(form.get_rev, signal_form.get_rev) signal_case = case_handler.call_args[1]['case'] self.assertEqual(case.case_id, signal_case.case_id) self.assertEqual(case.get_rev, signal_case.get_rev)