def test_archie_modified_on(self): case_id = uuid.uuid4().hex now = datetime.utcnow().replace(microsecond=0) earlier = now - timedelta(hours=1) way_earlier = now - timedelta(days=1) # make sure we timestamp everything so they have the right order create_block = CaseBlock(case_id, create=True, date_modified=way_earlier) post_case_blocks([create_block.as_xml()], form_extras={'received_on': way_earlier}) update_block = CaseBlock(case_id, update={'foo': 'bar'}, date_modified=earlier) post_case_blocks([update_block.as_xml()], form_extras={'received_on': earlier}) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) self.assertEqual(earlier, case.modified_on) second_form = FormAccessors(REBUILD_TEST_DOMAIN).get_form( case.xform_ids[-1]) second_form.archive() case = case_accessors.get_case(case_id) self.assertEqual(way_earlier, case.modified_on)
def test_archiving_only_form(self): """ Checks that archiving the only form associated with the case archives the case and unarchiving unarchives it. """ case_id = _post_util(create=True, p1='p1-1', p2='p2-1') case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) self.assertFalse(case.is_deleted) if should_use_sql_backend(REBUILD_TEST_DOMAIN): self.assertEqual(1, len(case.actions)) else: self.assertEqual(2, len(case.actions)) [form_id] = case.xform_ids form = FormAccessors(REBUILD_TEST_DOMAIN).get_form(form_id) form.archive() case = case_accessors.get_case(case_id) self.assertTrue(case.is_deleted) # should just have the 'rebuild' action self.assertEqual(1, len(case.actions)) self.assertTrue(case.actions[0].is_case_rebuild) form.unarchive() case = case_accessors.get_case(case_id) self.assertFalse(case.is_deleted) self.assertEqual(3, len(case.actions)) self.assertTrue(case.actions[-1].is_case_rebuild)
def test_edit_submissions_simple(self): initial_quantity = 100 form = submit_case_blocks( case_blocks=get_single_balance_block(quantity=initial_quantity, **self._stock_state_key), domain=self.domain, )[0] self._assert_stats(1, initial_quantity, initial_quantity) case_accessors = CaseAccessors(self.domain) case = case_accessors.get_case(self.case.case_id) try: self.assertTrue(any([action.is_ledger_transaction for action in case.actions])) except AttributeError: self.assertTrue('commtrack' in [action.action_type for action in case.actions]) self.assertEqual([form.form_id], case.xform_ids[1:]) # change the value to 50 edit_quantity = 50 submit_case_blocks( case_blocks=get_single_balance_block(quantity=edit_quantity, **self._stock_state_key), domain=self.domain, form_id=form.form_id, ) case = case_accessors.get_case(self.case.case_id) try: # CaseTransaction self.assertTrue(any([action.is_ledger_transaction for action in case.actions])) except AttributeError: # CaseAction self.assertTrue('commtrack' in [action.action_type for action in case.actions]) self._assert_stats(1, edit_quantity, edit_quantity) self.assertEqual([form.form_id], case.xform_ids[1:])
def get_cases(request, domain): request_params = request.GET if request.couch_user.is_commcare_user(): user_id = request.couch_user.get_id else: user_id = request_params.get("user_id", "") if not user_id and not request.couch_user.is_web_user(): return HttpResponseBadRequest("Must specify user_id!") ids_only = string_to_boolean(request_params.get("ids_only", "false")) case_id = request_params.get("case_id", "") footprint = string_to_boolean(request_params.get("footprint", "false")) accessor = CaseAccessors(domain) if toggles.HSPH_HACK.enabled(domain): hsph_case_id = request_params.get('hsph_hack', None) if hsph_case_id != 'None' and hsph_case_id and user_id: case = accessor.get_case(hsph_case_id) usercase_id = CommCareUser.get_by_user_id( user_id).get_usercase_id() usercase = accessor.get_case(usercase_id) if usercase_id else None return json_response( map( lambda case: CaseAPIResult( id=case['_id'], couch_doc=case, id_only=ids_only), filter(None, [case, case.parent, usercase])))
def handle(self, domain, case_id, **options): case_accessor = CaseAccessors(domain=domain) case = case_accessor.get_case(case_id) if not case.is_deleted and input('\n'.join([ 'Case {} is not already deleted. Are you sure you want to delete it? (y/N)'.format(case_id) ])).lower() != 'y': sys.exit(0)
def test_submit_to_deleted_case(self): """submitting to a deleted case should succeed and affect the case""" case_id = uuid.uuid4().hex xform, [case] = post_case_blocks([ CaseBlock(create=True, case_id=case_id, user_id='whatever', update={ 'foo': 'bar' }).as_xml() ], domain="test-domain") cases = CaseAccessors("test-domain") cases.soft_delete_cases([case_id]) case = cases.get_case(case_id) self.assertEqual('bar', case.dynamic_case_properties()['foo']) self.assertTrue(case.is_deleted) xform, [case] = post_case_blocks([ CaseBlock(create=False, case_id=case_id, user_id='whatever', update={ 'foo': 'not_bar' }).as_xml() ]) self.assertEqual('not_bar', case.dynamic_case_properties()['foo']) self.assertTrue(case.is_deleted)
def handle(self, domain, log_file_name, case_ids, **options): commit = options['commit'] print("Starting {} migration on {} at {}".format( "real" if commit else "fake", domain, datetime.datetime.utcnow() )) accessor = CaseAccessors(domain) case_ids = case_ids or accessor.get_case_ids_in_domain(type=self.case_type) with open(log_file_name, "w") as log_file: writer = csv.writer(log_file) writer.writerow( ['case_id'] + ['current_' + case_prop for case_prop in self.case_properties_to_update] + self.case_properties_to_update + [self.datamigration_case_property] ) for case_id in with_progress_bar(case_ids): if self.is_valid_case(domain, case_id): case = accessor.get_case(case_id) updated_case_properties = self.get_case_property_updates(case, domain) needs_update = bool(updated_case_properties) updated_case_properties[self.datamigration_case_property] = 'yes' if needs_update else 'no' writer.writerow( [case.case_id] + [case.get_case_property(case_prop) or '' for case_prop in self.case_properties_to_update] + [updated_case_properties.get(case_prop, '') for case_prop in ( self.case_properties_to_update + [self.datamigration_case_property])] ) if needs_update and commit: self.commit_updates(domain, case.case_id, updated_case_properties) print("Finished at {}".format(datetime.datetime.utcnow()))
def get_cases(request, domain): request_params = request.GET if request.couch_user.is_commcare_user(): user_id = request.couch_user.get_id else: user_id = request_params.get("user_id", "") if not user_id and not request.couch_user.is_web_user(): return HttpResponseBadRequest("Must specify user_id!") ids_only = string_to_boolean(request_params.get("ids_only", "false")) case_id = request_params.get("case_id", "") footprint = string_to_boolean(request_params.get("footprint", "false")) accessor = CaseAccessors(domain) if case_id and not footprint: # short circuit everything else and just return the case # NOTE: this allows any user in the domain to access any case given # they know its ID, which is slightly different from the previous # behavior (can only access things you own + footprint). If we want to # change this contract we would need to update this to check the # owned case list + footprint case = accessor.get_case(case_id) assert case.domain == domain cases = [ CaseAPIResult(domain=domain, id=case_id, couch_doc=case, id_only=ids_only) ]
def _update_case(domain, case_id, server_modified_on, last_visit_date=None): accessors = CaseAccessors(domain) case = accessors.get_case(case_id) case.server_modified_on = server_modified_on if last_visit_date: set_case_property_directly(case, 'last_visit_date', last_visit_date.strftime('%Y-%m-%d')) _save_case(domain, case)
def get_clays() -> Iterable[CassiusMarcellus]: case_accessors = CaseAccessors(DOMAIN) for case_id in case_accessors.get_case_ids_in_domain(type=CASE_TYPE): case = case_accessors.get_case(case_id) if not case.external_id: # This case is not mapped to a facility in DHIS2. continue yield CassiusMarcellus(case)
def test_update_case(self): update_properties = {'age': 99} self.factory.create_or_update_cases([self.person]) case_accessors = CaseAccessors(self.domain) person_case = case_accessors.get_case(self.person_id) self.assertEqual(person_case.dynamic_case_properties().get('age', None), '20') update_case(self.domain, self.person_id, update_properties) person_case = case_accessors.get_case(self.person_id) self.assertEqual(person_case.dynamic_case_properties()['age'], '99')
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])
def _delete_child_index(self): ref = CaseStructure() ref.case_id = "" # reset case_id to empty self.factory.create_or_update_case( CaseStructure(case_id=self.child.case_id, indices=[CaseIndex(ref, related_type='parent')], walk_related=False), ) # re-fetch case to clear memoized properties accessors = CaseAccessors(self.parent.domain) self.parent = accessors.get_case(self.parent.case_id) self.child = accessors.get_case(self.child.case_id)
def lookup_case(search_field, search_id, domain, case_type): """ Attempt to find the case in CouchDB by the provided search_field and search_id. Returns a tuple with case (if found) and an error code (if there was an error in lookup). """ found = False case_accessors = CaseAccessors(domain) if search_field == 'case_id': try: case = case_accessors.get_case(search_id) if case.domain == domain and case.type == case_type: found = True
def perform_resave_on_cases(domain): case_ids_missing_in_es, _ = compare_cases(domain, 'CommCareCase') print("%s Ids found for cases missing in ES." % len(case_ids_missing_in_es)) print(case_ids_missing_in_es) ok = input("Type 'ok' to continue: ") if ok != "ok": print("No changes made") return case_accessor = CaseAccessors(domain) for case_id in with_progress_bar(case_ids_missing_in_es): case = case_accessor.get_case(case_id) if case: resave_case(domain, case, send_post_save_signal=False) else: print("case not found %s" % case_id)
def test_soft_delete(self): _submit_case_block(True, 'c1', domain=DOMAIN) _submit_case_block(True, 'c2', domain=DOMAIN) _submit_case_block(True, 'c3', domain=DOMAIN) accessors = CaseAccessors(DOMAIN) # delete num = accessors.soft_delete_cases(['c1', 'c2'], deletion_id='123') self.assertEqual(num, 2) for case_id in ['c1', 'c2']: case = accessors.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.deletion_id, '123')
def __call__(self, item, context=None): case_id = self._case_id_expression(item, context) if not case_id: return None # a village will only be on the case as the owner if # its the assignment case so we should verify that if item['owner_id'] != UNOWNED_EXTENSION_OWNER_ID: accessor = CaseAccessors(context.root_doc['domain']) case = accessor.get_case(case_id) if case.type == 'assignment': # should verify it is a village case (not AWC) # via some property to be determined later return item['owner_id']
def test_archived_form_gets_removed_from_case_xform_ids(self): initial_amounts = [(p._id, float(100)) for p in self.products] instance_id = self.submit_xml_form(balance_submission(initial_amounts), timestamp=datetime.utcnow() + timedelta(-30)) case_accessors = CaseAccessors(self.domain.name) case = case_accessors.get_case(self.sp.case_id) self.assertIn(instance_id, case.xform_ids) form = FormAccessors(self.domain.name).get_form(instance_id) form.archive() case = case_accessors.get_case(self.sp.case_id) self.assertNotIn(instance_id, case.xform_ids)
def get_case_blocks() -> Iterable[CaseBlock]: case_accessors = CaseAccessors(DOMAIN) for case_id in case_accessors.get_case_ids_in_domain(type=CASE_TYPE): case = case_accessors.get_case(case_id) if not case.external_id: # This case is not mapped to a facility in DHIS2. continue case_block = CaseBlock( case_id=case.case_id, external_id=case.external_id, case_type=CASE_TYPE, case_name=case.name, update={}, ) yield case_block
def lookup_case(search_field, search_id, domain, case_type): """ Attempt to find the case in CouchDB by the provided search_field and search_id. Returns a tuple with case (if found) and an error code (if there was an error in lookup). """ found = False case_accessors = CaseAccessors(domain) if search_field == 'case_id': try: case = case_accessors.get_case(search_id) if case.domain == domain and case.type == case_type: found = True except CaseNotFound: pass elif search_field == EXTERNAL_ID: cases_by_type = case_accessors.get_cases_by_external_id(search_id, case_type=case_type) if not cases_by_type: return (None, LookupErrors.NotFound) elif len(cases_by_type) > 1: return (None, LookupErrors.MultipleResults) else: case = cases_by_type[0] found = True if found: return (case, None) else: return (None, LookupErrors.NotFound)
def get_parent_of_case(domain, case_id, parent_case_type): case_accessor = CaseAccessors(domain) try: if not isinstance(case_id, basestring): case_id = case_id.case_id child_case = case_accessor.get_case(case_id) except CaseNotFound: raise ENikshayCaseNotFound( "Couldn't find case: {}".format(case_id) ) parent_case_ids = [ indexed_case.referenced_id for indexed_case in child_case.indices if indexed_case.referenced_type == parent_case_type ] parent_cases = case_accessor.get_cases(parent_case_ids) open_parent_cases = [ occurrence_case for occurrence_case in parent_cases if not occurrence_case.closed ] if not open_parent_cases: raise ENikshayCaseNotFound( "Couldn't find any open {} cases for id: {}".format(parent_case_type, case_id) ) return open_parent_cases[0]
def parent_cases(self): from corehq.apps.api.util import case_to_es_case accessor = CaseAccessors(self.domain) return { index['identifier']: case_to_es_case(accessor.get_case(index['referenced_id'])) for index in self.indices }
def test_archived_form_gets_removed_from_case_xform_ids(self): initial_amounts = [(p._id, float(100)) for p in self.products] instance_id = self.submit_xml_form( balance_submission(initial_amounts), timestamp=datetime.utcnow() + timedelta(-30) ) case_accessors = CaseAccessors(self.domain.name) case = case_accessors.get_case(self.sp.case_id) self.assertIn(instance_id, case.xform_ids) form = FormAccessors(self.domain.name).get_form(instance_id) form.archive() case = case_accessors.get_case(self.sp.case_id) self.assertNotIn(instance_id, case.xform_ids)
class PopulateMissingMotherNameDocProcessor(DataManagementDocProcessor): def __init__(self, domain): super().__init__(domain) date_today = date.today() self.cut_off_dob = str(date_today.replace(year=date_today.year - CUT_OFF_AGE_IN_YEARS)) self.test_location_ids = find_test_awc_location_ids(self.domain) self.case_accessor = CaseAccessors(self.domain) def process_bulk_docs(self, docs): updates = {} for doc in docs: case_id = doc['_id'] mother_case_ids = [i.referenced_id for i in CaseAccessorSQL.get_indices(self.domain, case_id) if i.identifier == MOTHER_INDEX_IDENTIFIER] if len(mother_case_ids) == 1: try: mother_case = self.case_accessor.get_case(mother_case_ids[0]) except CaseNotFound: pass else: updates[case_id] = mother_case.name if updates: submit_case_blocks(self._create_case_blocks(updates, MOTHER_NAME_PROPERTY), self.domain, user_id=SYSTEM_USER_ID) return True def should_process(self, doc): owner_id = doc.get('owner_id') if owner_id and owner_id in self.test_location_ids: return False dob = doc.get(DOB_PROPERTY) if dob and dob >= self.cut_off_dob and not doc.get(MOTHER_NAME_PROPERTY): return True return False
def handle(self, file_path, *args, **options): print("Loading episode ids or person_ids") self._load_case_ids(file_path, options.get('parse_as_person_enikshay_ids')) print("Okay! Loaded {number} episode ids".format( number=len(self.episode_ids))) case_accessor = CaseAccessors(DOMAIN) for episode_id in with_progress_bar(self.episode_ids): should_be_forwarded = "Could not be determined" try: episode_case = case_accessor.get_case(episode_id) episode_case_properties = episode_case.dynamic_case_properties( ) should_be_forwarded = self._should_be_forwarded( episode_case, episode_case_properties) self._add_row(episode_id, episode_case, episode_case_properties, should_be_forwarded) except CaseNotFound: self._add_row(episode_id, None, None, should_be_forwarded, error_message="Could not find episode case") file_name = self._save_file() print("Report saved in file:{filename}".format(filename=file_name))
class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('infile') parser.add_argument('outfile') def handle(self, infile, outfile, *args, **options): self.case_accessor = CaseAccessors('icds-cas') with open(infile, 'r', encoding='utf-8') as old, open(outfile, 'w', encoding='utf-8') as new: reader = csv.reader(old) writer = csv.writer(new) headers = next(reader) writer.writerow(headers) for row in reader: case_id = row[4] hh_id = row[10] if hh_id: person, hh = self.case_accessor.get_cases([case_id, hh_id], ordered=True) else: person = self.case_accessor.get_case(case_id) hh = None if hh: row[18] = hh.get_case_property('name') row[19] = hh.get_case_property('hh_num') row[20] = person.get_case_property('name') writer.writerow(row)
class Command(BaseCommand): def add_arguments(self, parser): parser.add_argument('infile') parser.add_argument('outfile') def handle(self, infile, outfile, *args, **options): self.case_accessor = CaseAccessors('icds-cas') with open(infile, 'r') as old, open(outfile, 'w') as new: reader = csv.reader(old) writer = csv.writer(new) headers = next(reader) writer.writerow(headers) for row in reader: owner = row[2] case_id = row[3] hh_id = row[4] if hh_id: person, hh = self.case_accessor.get_cases([case_id, hh_id], ordered=True) else: person = self.case_accessor.get_case(case_id) hh = None row[15] = SQLLocation.objects.get(location_id=owner).name if hh: row[16] = hh.get_case_property('name') row[17] = hh.get_case_property('hh_num') row[18] = person.get_case_property('name') row[19] = person.get_case_property('dob') row[20] = person.get_case_property('sex') writer.writerow(row)
def _get_case_ids_related_to_person(domain, person_case_id): case_accessor = CaseAccessors(domain) person_case = case_accessor.get_case(person_case_id) assert person_case.type == 'person' assert person_case.dynamic_case_properties().get('migration_created_case') == 'true' all_occurrence_cases = _get_children_cases_by_type(domain, person_case.case_id, CASE_TYPE_OCCURRENCE) for occurrence_case in all_occurrence_cases: assert occurrence_case.dynamic_case_properties().get('migration_created_case') == 'true' all_episode_cases = _concatenate_list_of_lists([ _get_children_cases_by_type(domain, occurrence_child_case.case_id, CASE_TYPE_EPISODE) for occurrence_child_case in all_occurrence_cases ]) for episode_case in all_episode_cases: assert episode_case.dynamic_case_properties().get('migration_created_case') == 'true' all_drtb_hiv_referral_cases = _concatenate_list_of_lists([ _get_children_cases_by_type(domain, episode_child_case.case_id, CASE_TYPE_DRTB_HIV_REFERRAL) for episode_child_case in all_episode_cases ]) for drtb_hiv_referral_case in all_drtb_hiv_referral_cases: assert drtb_hiv_referral_case.dynamic_case_properties().get('migration_created_case') == 'true' return _concatenate_list_of_lists([ [person_case.case_id], [occurrence_child_case.case_id for occurrence_child_case in all_occurrence_cases], [episode_child_case.case_id for episode_child_case in all_episode_cases], [drtb_hiv_referral_case.case_id for drtb_hiv_referral_case in all_drtb_hiv_referral_cases], ])
def get_associated_episode_case_for_test(test_case, occurrence_case_id): """ get associated episode case set on the test case for new structure if has a new_episode_id (for diagnostic -> confirmed_tb or dstb -> drtb episode transition) return that episode which should be a confirmed_tb/confirmed_drtb case elif has a episode_case_id (for follow up test cases) return the associated episode case only which should be a confirmed_tb/confirmed_drtb case else fallback to finding the open confirmed tb episode case """ test_case_properties = test_case.dynamic_case_properties() test_case_episode_id = (test_case_properties.get('new_episode_case_id') or test_case_properties.get('episode_case_id')) if test_case_episode_id: accessor = CaseAccessors(test_case.domain) try: return accessor.get_case(test_case_episode_id) except CaseNotFound: raise ENikshayCaseNotFound( "Could not find episode case %s associated with test %s" % (test_case_episode_id, test_case.get_id)) return get_open_episode_case_from_occurrence(test_case.domain, occurrence_case_id)
def child_cases(self): from corehq.apps.api.util import case_to_es_case accessor = CaseAccessors(self.domain) return { index.case_id: case_to_es_case(accessor.get_case(index.case_id)) for index in self._reverse_indices }
def get_case_by_identifier(domain, identifier): # circular import from corehq.apps.api.es import CaseES case_es = CaseES(domain) case_accessors = CaseAccessors(domain) def _query_by_type(i_type): q = case_es.base_query( terms={ i_type: identifier, }, fields=['_id', i_type], size=1 ) response = case_es.run_query(q) raw_docs = response['hits']['hits'] if raw_docs: return case_accessors.get_case(raw_docs[0]['_id']) # Try by any of the allowed identifiers for identifier_type in ALLOWED_CASE_IDENTIFIER_TYPES: case = _query_by_type(identifier_type) if case is not None: return case # Try by case id try: case_by_id = case_accessors.get_case(identifier) if case_by_id.domain == domain: return case_by_id except (CaseNotFound, KeyError): pass return None
def handle(self, case_type, *args, **options): self.dry_run = options.get('dry_run') accessor = CaseAccessors(DOMAIN) result_file_path = "{case_type}_cases_update_report_{timestamp}.csv".format( case_type=case_type, timestamp=datetime.now().strftime("%Y-%m-%d-%H-%M-%S") ) if case_type == "all": case_types = ['person', 'episode', 'test'] else: case_types = [case_type] with open(result_file_path, 'w') as output_buffer: writer = csv.DictWriter(output_buffer, fieldnames=self._get_headers(case_types)) writer.writeheader() for person_case_id in self._get_person_case_ids_to_process(): person_case = accessor.get_case(person_case_id) if person_case.dynamic_case_properties().get('case_version') == PERSON_CASE_2B_VERSION: # reset all collections for next iteration occurrence_cases = [] if "person" in case_types: case_status = self.get_case_status(person_case) case_status = self.update_case(person_case.get_id, case_status) writer.writerow(case_status) if "episode" in case_types: occurrence_cases = [case for case in accessor.get_reverse_indexed_cases([person_case_id]) if case.type == CASE_TYPE_OCCURRENCE] for occurrence_case in occurrence_cases: episode_cases = [case for case in accessor.get_reverse_indexed_cases([occurrence_case.get_id]) if case.type == CASE_TYPE_EPISODE] for episode_case in episode_cases: case_status = self.get_case_status(episode_case, episode_cases=episode_cases) case_status = self.update_case(episode_case.get_id, case_status) writer.writerow(case_status)
def get_parent_of_case(domain, case_id, parent_case_type): case_accessor = CaseAccessors(domain) try: if not isinstance(case_id, basestring): case_id = case_id.case_id child_case = case_accessor.get_case(case_id) except CaseNotFound: raise ENikshayCaseNotFound("Couldn't find case: {}".format(case_id)) parent_case_ids = [ indexed_case.referenced_id for indexed_case in child_case.indices if indexed_case.referenced_type == parent_case_type ] parent_cases = case_accessor.get_cases(parent_case_ids) open_parent_cases = [ occurrence_case for occurrence_case in parent_cases if not occurrence_case.closed ] if not open_parent_cases: raise ENikshayCaseNotFound( "Couldn't find any open {} cases for id: {}".format( parent_case_type, case_id)) return open_parent_cases[0]
def _iter_supercase_info(self, info: CaseTriggerInfo): def filter_index(idx): return ((not self.identifier or idx.identifier == self.identifier) and (not self.referenced_type or idx.referenced_type == self.referenced_type) and (not self.relationship or idx.relationship == self.relationship)) case_accessor = CaseAccessors(info.domain) case = case_accessor.get_case(info.case_id) for index in case.live_indices: if filter_index(index): supercase = case_accessor.get_case(index.referenced_id) yield get_case_trigger_info_for_case( supercase, [self.supercase_value_source], )
def get_cases(request, domain): request_params = request.GET if request.couch_user.is_commcare_user(): user_id = request.couch_user.get_id else: user_id = request_params.get("user_id", "") if not user_id and not request.couch_user.is_web_user(): return HttpResponseBadRequest("Must specify user_id!") ids_only = string_to_boolean(request_params.get("ids_only", "false")) case_id = request_params.get("case_id", "") footprint = string_to_boolean(request_params.get("footprint", "false")) accessor = CaseAccessors(domain) if toggles.HSPH_HACK.enabled(domain): hsph_case_id = request_params.get('hsph_hack', None) if hsph_case_id != 'None' and hsph_case_id and user_id: case = accessor.get_case(hsph_case_id) usercase_id = CommCareUser.get_by_user_id(user_id).get_usercase_id() usercase = accessor.get_case(usercase_id) if usercase_id else None return json_response(map( lambda case: CaseAPIResult(domain=domain, id=case['_id'], couch_doc=case, id_only=ids_only), filter(None, [case, case.parent, usercase]) )) if case_id and not footprint: # short circuit everything else and just return the case # NOTE: this allows any user in the domain to access any case given # they know its ID, which is slightly different from the previous # behavior (can only access things you own + footprint). If we want to # change this contract we would need to update this to check the # owned case list + footprint case = accessor.get_case(case_id) assert case.domain == domain cases = [CaseAPIResult(domain=domain, id=case_id, couch_doc=case, id_only=ids_only)] else: filters = get_filters_from_request_params(request_params) status = api_closed_to_status(request_params.get('closed', 'false')) case_type = filters.get('properties/case_type', None) cases = get_filtered_cases(domain, status=status, case_type=case_type, user_id=user_id, filters=filters, footprint=footprint, ids_only=ids_only, strip_history=True) return json_response(cases)
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.case_accessors = CaseAccessors(domain=domain) def get_document(self, doc_id): return self.case_accessors.get_case(doc_id).to_json()
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_soft_delete(self): _submit_case_block(True, 'c1', domain=DOMAIN) _submit_case_block(True, 'c2', domain=DOMAIN) _submit_case_block(True, 'c3', domain=DOMAIN) accessors = CaseAccessors(DOMAIN) # delete num = accessors.soft_delete_cases(['c1', 'c2'], deletion_id='123') self.assertEqual(num, 2) for case_id in ['c1', 'c2']: case = accessors.get_case(case_id) self.assertTrue(case.is_deleted) self.assertEqual(case.deletion_id, '123') case = accessors.get_case('c3') self.assertFalse(case.is_deleted)
def test_archive_against_deleted_case(self): now = datetime.utcnow() # make sure we timestamp everything so they have the right order case_id = _post_util(create=True, p1='p1', form_extras={'received_on': now}) _post_util(case_id=case_id, p2='p2', form_extras={'received_on': now + timedelta(seconds=1)}) _post_util(case_id=case_id, p3='p3', form_extras={'received_on': now + timedelta(seconds=2)}) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) case_accessors.soft_delete_cases([case_id]) [f1, f2, f3] = case.xform_ids f2_doc = FormAccessors(REBUILD_TEST_DOMAIN).get_form(f2) f2_doc.archive() case = case_accessors.get_case(case_id) self.assertTrue(case.is_deleted)
def handle(self, domain, case_type, *args, **options): perform_update = True query = (CaseES(es_instance_alias=ES_EXPORT_INSTANCE).domain( domain).case_type(case_type).is_closed(False).term( 'name.exact', '')) cases_count = query.count() print("Number of cases to be updated approximately: %s" % cases_count) if not input("Do you wish to update cases (y/n)") == 'y': perform_update = False if not input("Do you wish to just log updates (y/n)") == 'y': exit(0) case_ids = query.get_ids() print("Begin iterating %s cases" % len(case_ids)) case_accessor = CaseAccessors(domain) case_updates = [] filename = "case_updates_%s_%s_%s.csv" % (domain, case_type, datetime.utcnow()) with open(filename, 'w') as f: writer = csv.DictWriter(f, ['case_id', 'new_value']) writer.writeheader() for case_id in with_progress_bar(case_ids): case = case_accessor.get_case(case_id) if case.name: continue update_to_name = get_last_non_blank_value(case, 'name') if update_to_name: writer.writerow({ 'case_id': case_id, 'new_value': update_to_name }) if perform_update: case_updates.append((case_id, { 'name': update_to_name }, False)) # update batch when we have the threshold if len(case_updates) == CASE_UPDATE_BATCH: bulk_update_cases(domain, case_updates, DEVICE_ID) case_updates = [] # submit left over case updates if case_updates: print("Performing last batch of updates") bulk_update_cases(domain, case_updates, DEVICE_ID) print("Finished. Update details in %s" % filename)
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.case_accessors = CaseAccessors(domain=domain) def get_document(self, doc_id): try: return self.case_accessors.get_case(doc_id).to_json() except CaseNotFound as e: raise DocumentNotFoundError(e)
def _update_case(domain, case_id, server_modified_on, last_visit_date=None): accessors = CaseAccessors(domain) case = accessors.get_case(case_id) case.server_modified_on = server_modified_on if last_visit_date: set_case_property_directly(case, 'last_visit_date', last_visit_date.strftime('%Y-%m-%d')) if should_use_sql_backend(domain): CaseAccessorSQL.save_case(case) else: # can't call case.save() since it overrides the server_modified_on property CommCareCase.get_db().save_doc(case.to_json())
def test_archive_removes_index(self): parent_case_id = uuid.uuid4().hex post_case_blocks([ CaseBlock(parent_case_id, create=True).as_xml() ]) child_case_id = uuid.uuid4().hex post_case_blocks([ CaseBlock(child_case_id, create=True).as_xml() ]) xform, _ = post_case_blocks([ CaseBlock(child_case_id, index={'mom': ('mother', parent_case_id)}).as_xml() ]) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(child_case_id) self.assertEqual(1, len(case.indices)) xform.archive() case = case_accessors.get_case(child_case_id) self.assertEqual(0, len(case.indices))
def test_archie_modified_on(self): case_id = uuid.uuid4().hex now = datetime.utcnow().replace(microsecond=0) earlier = now - timedelta(hours=1) way_earlier = now - timedelta(days=1) # make sure we timestamp everything so they have the right order create_block = CaseBlock(case_id, create=True, date_modified=way_earlier) post_case_blocks( [create_block.as_xml()], form_extras={'received_on': way_earlier} ) update_block = CaseBlock(case_id, update={'foo': 'bar'}, date_modified=earlier) post_case_blocks( [update_block.as_xml()], form_extras={'received_on': earlier} ) case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN) case = case_accessors.get_case(case_id) self.assertEqual(earlier, case.modified_on) second_form = FormAccessors(REBUILD_TEST_DOMAIN).get_form(case.xform_ids[-1]) second_form.archive() case = case_accessors.get_case(case_id) self.assertEqual(way_earlier, case.modified_on)
def hint_still_valid(domain, hint): """ For a given domain/owner/cleanliness hint check if it's still valid """ casedb = CaseAccessors(domain) try: hint_case = casedb.get_case(hint) hint_owner = hint_case.owner_id except CaseNotFound: # hint was deleted return False dependent_case_ids = set(get_dependent_case_info(domain, [hint]).all_ids) return any([c.owner_id != hint_owner and c.owner_id != UNOWNED_EXTENSION_OWNER_ID for c in casedb.get_cases(list(dependent_case_ids))])
def _with_case(domain, case_type, last_modified): with drop_connected_signals(case_post_save): case = CaseFactory(domain).create_case(case_type=case_type) _update_case(domain, case.case_id, last_modified) accessors = CaseAccessors(domain) case = accessors.get_case(case.case_id) try: yield case finally: if should_use_sql_backend(domain): CaseAccessorSQL.hard_delete_cases(domain, [case.case_id]) else: case.delete()
def handle(self, *args, **options): domain = options["domain"] case_id = options["case_id"] case_accessor = CaseAccessors(domain=domain) case = case_accessor.get_case(case_id) if ( not case.is_deleted and raw_input( "\n".join(["Case {} is not already deleted. Are you sure you want to delete it? (y/N)".format(case_id)]) ).lower() != "y" ): sys.exit(0) dependent_case_ids = get_entire_case_network(domain, [case_id]) cases_to_delete = filter(lambda case: not case.is_deleted, case_accessor.get_cases(dependent_case_ids)) if cases_to_delete: with open(options["filename"], "w") as csvfile: writer = csv.writer(csvfile) headers = ["case id", "case type", "owner", "opened by", "app version"] writer.writerow(headers) print headers for case in cases_to_delete: form = FormAccessors(domain=domain).get_form(case.xform_ids[0]) app_version_info = get_app_version_info( domain, form.build_id, form.form_data["@version"], form.metadata ) row = [ case.case_id, case.type, cached_owner_id_to_display(case.owner_id) or case.owner_id, cached_owner_id_to_display(case.opened_by), app_version_info.build_version, ] writer.writerow(row) print row if ( cases_to_delete and raw_input("\n".join(["Delete these {} cases? (y/N)".format(len(cases_to_delete))])).lower() == "y" ): case_accessor.soft_delete_cases([c.case_id for c in cases_to_delete]) print "deleted {} cases".format(len(cases_to_delete)) if cases_to_delete: print "details here: {}".format(options["filename"]) else: print "didn't find any cases to delete"
class ReadonlyCaseDocumentStore(ReadOnlyDocumentStore): def __init__(self, domain): self.domain = domain self.case_accessors = CaseAccessors(domain=domain) def get_document(self, doc_id): try: return self.case_accessors.get_case(doc_id).to_json() except CaseNotFound as e: raise DocumentNotFoundError(e) def iter_document_ids(self, last_id=None): # todo: support last_id return iter(self.case_accessors.get_case_ids_in_domain()) def iter_documents(self, ids): for wrapped_case in self.case_accessors.iter_cases(ids): yield wrapped_case.to_json()
class FundamentalCaseTests(TestCase): @classmethod def setUpClass(cls): super(FundamentalCaseTests, cls).setUpClass() FormProcessorTestUtils.delete_all_cases(DOMAIN) FormProcessorTestUtils.delete_all_xforms(DOMAIN) @classmethod def tearDownClass(cls): FormProcessorTestUtils.delete_all_cases(DOMAIN) FormProcessorTestUtils.delete_all_xforms(DOMAIN) super(FundamentalCaseTests, cls).tearDownClass() def setUp(self): super(FundamentalCaseTests, self).setUp() self.interface = FormProcessorInterface() self.casedb = CaseAccessors() @run_with_all_backends def test_create_case(self): case_id = uuid.uuid4().hex modified_on = datetime.utcnow() _submit_case_block( True, case_id, user_id='user1', owner_id='owner1', case_type='demo', case_name='create_case', date_modified=modified_on, date_opened=modified_on, update={ 'dynamic': '123' } ) case = self.casedb.get_case(case_id) self.assertIsNotNone(case) self.assertEqual(case.case_id, case_id) self.assertEqual(case.owner_id, 'owner1') self.assertEqual(case.type, 'demo') self.assertEqual(case.name, 'create_case') self.assertEqual(case.opened_on, modified_on) self.assertEqual(case.opened_by, 'user1') self.assertEqual(case.modified_on, modified_on) self.assertEqual(case.modified_by, 'user1') self.assertTrue(case.server_modified_on > modified_on) self.assertFalse(case.closed) self.assertIsNone(case.closed_on) if settings.TESTS_SHOULD_USE_SQL_BACKEND: self.assertIsNone(case.closed_by) else: self.assertEqual(case.closed_by, '') self.assertEqual(case.dynamic_case_properties()['dynamic'], '123') @run_with_all_backends def test_create_case_unicode_name(self): """ Submit case blocks with unicode names """ # This was failing hard: # http://manage.dimagi.com/default.asp?226582#1145687 case_id = uuid.uuid4().hex modified_on = datetime.utcnow() case_name = u'प्रसव' _submit_case_block( True, case_id, user_id='user1', owner_id='owner1', case_type='demo', case_name=case_name, date_modified=modified_on, update={ 'dynamic': '123' } ) case = self.casedb.get_case(case_id) self.assertEqual(case.name, case_name) @run_with_all_backends def test_update_case(self): case_id = uuid.uuid4().hex opened_on = datetime.utcnow() _submit_case_block( True, case_id, user_id='user1', owner_id='owner1', case_type='demo', case_name='create_case', date_modified=opened_on, update={ 'dynamic': '123' } ) modified_on = datetime.utcnow() _submit_case_block( False, case_id, user_id='user2', owner_id='owner2', case_name='update_case', date_modified=modified_on, date_opened=opened_on, update={ 'dynamic': '1234' } ) case = self.casedb.get_case(case_id) self.assertEqual(case.owner_id, 'owner2') self.assertEqual(case.name, 'update_case') self.assertEqual(coerce_to_datetime(case.opened_on), coerce_to_datetime(opened_on)) self.assertEqual(case.opened_by, 'user1') self.assertEqual(case.modified_on, modified_on) self.assertEqual(case.modified_by, 'user2') self.assertTrue(case.server_modified_on > modified_on) self.assertFalse(case.closed) self.assertIsNone(case.closed_on) self.assertEqual(case.dynamic_case_properties()['dynamic'], '1234') @run_with_all_backends def test_close_case(self): # same as update, closed, closed on, closed by case_id = uuid.uuid4().hex opened_on = datetime.utcnow() _submit_case_block( True, case_id, user_id='user1', owner_id='owner1', case_type='demo', case_name='create_case', date_modified=opened_on ) modified_on = datetime.utcnow() _submit_case_block( False, case_id, user_id='user2', date_modified=modified_on, close=True ) case = self.casedb.get_case(case_id) self.assertEqual(case.owner_id, 'owner1') self.assertEqual(case.modified_on, modified_on) self.assertEqual(case.modified_by, 'user2') self.assertTrue(case.closed) self.assertEqual(case.closed_on, modified_on) self.assertEqual(case.closed_by, 'user2') self.assertTrue(case.server_modified_on > modified_on) @run_with_all_backends def test_empty_update(self): case_id = uuid.uuid4().hex opened_on = datetime.utcnow() _submit_case_block( True, case_id, user_id='user1', owner_id='owner1', case_type='demo', case_name='create_case', date_modified=opened_on, update={ 'dynamic': '123' } ) modified_on = datetime.utcnow() _submit_case_block( False, case_id, user_id='user2', date_modified=modified_on, update={} ) case = self.casedb.get_case(case_id) self.assertEqual(case.dynamic_case_properties(), {'dynamic': '123'}) @run_with_all_backends def test_case_with_index(self): # same as update, indexes mother_case_id = uuid.uuid4().hex _submit_case_block( True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother', case_name='mother', date_modified=datetime.utcnow() ) child_case_id = uuid.uuid4().hex _submit_case_block( True, child_case_id, user_id='user1', owner_id='owner1', case_type='child', case_name='child', date_modified=datetime.utcnow(), index={ 'mom': ('mother', mother_case_id) } ) case = self.casedb.get_case(child_case_id) self.assertEqual(len(case.indices), 1) index = case.indices[0] self.assertEqual(index.identifier, 'mom') self.assertEqual(index.referenced_id, mother_case_id) self.assertEqual(index.referenced_type, 'mother') self.assertEqual(index.relationship, 'child') @run_with_all_backends def test_update_index(self): mother_case_id = uuid.uuid4().hex _submit_case_block( True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother', case_name='mother', date_modified=datetime.utcnow() ) child_case_id = uuid.uuid4().hex _submit_case_block( True, child_case_id, user_id='user1', owner_id='owner1', case_type='child', case_name='child', date_modified=datetime.utcnow(), index={ 'mom': ('mother', mother_case_id) } ) case = self.casedb.get_case(child_case_id) self.assertEqual(case.indices[0].identifier, 'mom') _submit_case_block( False, child_case_id, user_id='user1', date_modified=datetime.utcnow(), index={ 'mom': ('other_mother', mother_case_id) } ) case = self.casedb.get_case(child_case_id) self.assertEqual(case.indices[0].referenced_type, 'other_mother') @run_with_all_backends def test_delete_index(self): mother_case_id = uuid.uuid4().hex _submit_case_block( True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother', case_name='mother', date_modified=datetime.utcnow() ) child_case_id = uuid.uuid4().hex _submit_case_block( True, child_case_id, user_id='user1', owner_id='owner1', case_type='child', case_name='child', date_modified=datetime.utcnow(), index={ 'mom': ('mother', mother_case_id) } ) case = self.casedb.get_case(child_case_id) self.assertEqual(len(case.indices), 1) _submit_case_block( False, child_case_id, user_id='user1', date_modified=datetime.utcnow(), index={ 'mom': ('mother', '') } ) case = self.casedb.get_case(child_case_id) self.assertEqual(len(case.indices), 0) @run_with_all_backends def test_invalid_index(self): invalid_case_id = uuid.uuid4().hex child_case_id = uuid.uuid4().hex form, cases = _submit_case_block( True, child_case_id, user_id='user1', owner_id='owner1', case_type='child', case_name='child', date_modified=datetime.utcnow(), index={ 'mom': ('mother', invalid_case_id) } ) self.assertEqual(0, len(cases)) self.assertTrue(form.is_error) self.assertTrue('InvalidCaseIndex' in form.problem) @run_with_all_backends def test_invalid_index_cross_domain(self): mother_case_id = uuid.uuid4().hex _submit_case_block( True, mother_case_id, user_id='user1', owner_id='owner1', case_type='mother', case_name='mother', date_modified=datetime.utcnow(), domain='domain-1', ) child_case_id = uuid.uuid4().hex form, cases = _submit_case_block( True, child_case_id, user_id='user1', owner_id='owner1', case_type='child', case_name='child', date_modified=datetime.utcnow(), index={ 'mom': ('mother', mother_case_id) }, domain='domain-2', ) self.assertEqual(0, len(cases)) self.assertTrue(form.is_error) self.assertTrue('InvalidCaseIndex' in form.problem) def test_case_with_attachment(self): # same as update, attachments pass @run_with_all_backends def test_date_opened_coercion(self): delete_all_users() self.project = Domain(name='some-domain') self.project.save() user = create_restore_user(self.project.name) case_id = uuid.uuid4().hex modified_on = datetime.utcnow() case = CaseBlock( create=True, case_id=case_id, user_id=user.user_id, owner_id=user.user_id, case_type='demo', case_name='create_case', date_modified=modified_on, date_opened=modified_on, update={ 'dynamic': '123' } ) post_case_blocks([case.as_xml()], domain='some-domain') # update the date_opened to date type to check for value on restore case.date_opened = case.date_opened.date() check_user_has_case(self, user, case.as_xml()) @run_with_all_backends def test_restore_caches_cleared(self): cache = get_redis_default_cache() cache_key = restore_cache_key(RESTORE_CACHE_KEY_PREFIX, 'user_id', version="2.0") cache.set(cache_key, 'test-thing') self.assertEqual(cache.get(cache_key), 'test-thing') form = """ <data xmlns="http://openrosa.org/formdesigner/blah"> <meta> <userID>{user_id}</userID> </meta> </data> """ submit_form_locally(form.format(user_id='user_id'), DOMAIN) self.assertIsNone(cache.get(cache_key))
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))
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 AutomaticCaseUpdateTest(TestCase): def setUp(self): self.domain = 'auto-update-test' self.case_db = CaseAccessors(self.domain) self.factory = CaseFactory(self.domain) self.rule = AutomaticUpdateRule( domain=self.domain, name='test-rule', case_type='test-case-type', active=True, server_modified_boundary=30, ) self.rule.save() self.rule.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='last_visit_date', property_value='30', match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE, ), ] self.rule.automaticupdateaction_set = [ AutomaticUpdateAction( action=AutomaticUpdateAction.ACTION_UPDATE, property_name='update_flag', property_value='Y', ), AutomaticUpdateAction( action=AutomaticUpdateAction.ACTION_CLOSE, ), ] self.rule2 = AutomaticUpdateRule( domain=self.domain, name='test-rule-2', case_type='test-case-type-2', active=True, server_modified_boundary=30, ) self.rule2.save() self.rule2.automaticupdateaction_set = [ AutomaticUpdateAction( action=AutomaticUpdateAction.ACTION_CLOSE, ), ] self.rule3 = AutomaticUpdateRule( domain=self.domain, name='test-rule-3', case_type='test-case-type-2', active=True, server_modified_boundary=50, ) self.rule3.save() self.rule3.automaticupdateaction_set = [ AutomaticUpdateAction( action=AutomaticUpdateAction.ACTION_CLOSE, ), ] with drop_connected_signals(case_post_save): case = self.factory.create_case(case_type='test-case-type') self.case_id = case.case_id def tearDown(self): AutomaticUpdateRuleCriteria.objects.all().delete() AutomaticUpdateAction.objects.all().delete() AutomaticUpdateRule.objects.all().delete() FormProcessorTestUtils.delete_all_cases(self.domain) def _get_case_ids(self, *args, **kwargs): return [self.case_id] def _get_case(self): return self.case_db.get_case(self.case_id) def _assert_case_revision(self, rev_number, last_modified, expect_modified=False): if should_use_sql_backend(self.domain): self.assertEqual( expect_modified, CaseAccessorSQL.case_modified_since(self.case_id, last_modified) ) else: doc = self._get_case() self.assertTrue(doc['_rev'].startswith('%s-' % rev_number)) @run_with_all_backends def test_rule(self): now = datetime(2015, 10, 22, 0, 0) with patch('corehq.apps.data_interfaces.models.AutomaticUpdateRule.get_case_ids', new=self._get_case_ids): # No update: both dates are 27 days away last_modified = datetime(2015, 9, 25, 12, 0) _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 25)) self._assert_case_revision(2, last_modified) run_case_update_rules_for_domain(self.domain, now=now) self._assert_case_revision(2, last_modified) # No update: server_modified_on is 32 days away but last_visit_date is 27 days away last_modified = datetime(2015, 9, 20, 12, 0) _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 25)) self._assert_case_revision(3, last_modified) run_case_update_rules_for_domain(self.domain, now=now) self._assert_case_revision(3, last_modified) # No update: last_visit_date is 32 days away but server_modified_on is 27 days away last_modified = datetime(2015, 9, 25, 12, 0) _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 20)) self._assert_case_revision(4, last_modified) run_case_update_rules_for_domain(self.domain, now=now) self._assert_case_revision(4, last_modified) # Perform update: both dates are 32 days away last_modified = datetime(2015, 9, 20, 12, 0) _update_case(self.domain, self.case_id, last_modified, date(2015, 9, 20)) self._assert_case_revision(5, last_modified) with drop_connected_signals(case_post_save): run_case_update_rules_for_domain(self.domain, now=now) self._assert_case_revision(6, last_modified, True) case = self._get_case() self.assertEqual(case.get_case_property('update_flag'), 'Y') self.assertEqual(case.closed, True) @run_with_all_backends def test_match_days_since(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='last_visit_date', property_value='30', match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE, ), ] self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-12-30') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-12-03') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-12-02') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-11-01') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_match_equal(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='property1', property_value='value1', match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL, ), ] self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property1', 'x') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property1', 'value1') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_match_not_equal(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='property2', property_value='value2', match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL, ), ] self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property2', 'value2') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property2', 'x') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_date_case_properties_for_equality(self): """ Date case properties are automatically converted from string to date when fetching from the db, so here we want to make sure this doesn't interfere with our ability to compare dates for equality. """ with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='property1', property_value='2016-02-24', match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL, ), ] set_case_property_directly(case, 'property1', '2016-02-24') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property1', '2016-02-25') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_date_case_properties_for_inequality(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='property1', property_value='2016-02-24', match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL, ), ] set_case_property_directly(case, 'property1', '2016-02-24') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property1', '2016-02-25') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_match_has_value(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='property3', match_type=AutomaticUpdateRuleCriteria.MATCH_HAS_VALUE, ), ] self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property3', 'x') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property3', '') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) @run_with_all_backends def test_and_criteria(self): with _with_case(self.domain, 'test-case-type-2', datetime(2015, 1, 1)) as case: self.rule2.automaticupdaterulecriteria_set = [ AutomaticUpdateRuleCriteria( property_name='last_visit_date', property_value='30', match_type=AutomaticUpdateRuleCriteria.MATCH_DAYS_SINCE, ), AutomaticUpdateRuleCriteria( property_name='property1', property_value='value1', match_type=AutomaticUpdateRuleCriteria.MATCH_EQUAL, ), AutomaticUpdateRuleCriteria( property_name='property2', property_value='value2', match_type=AutomaticUpdateRuleCriteria.MATCH_NOT_EQUAL, ), AutomaticUpdateRuleCriteria( property_name='property3', match_type=AutomaticUpdateRuleCriteria.MATCH_HAS_VALUE, ), ] set_case_property_directly(case, 'last_visit_date', '2015-11-01') set_case_property_directly(case, 'property1', 'value1') set_case_property_directly(case, 'property2', 'x') set_case_property_directly(case, 'property3', 'x') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-12-30') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'last_visit_date', '2015-11-01') set_case_property_directly(case, 'property1', 'x') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property1', 'value1') set_case_property_directly(case, 'property2', 'value2') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property2', 'x') set_case_property_directly(case, 'property3', '') self.assertFalse(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) set_case_property_directly(case, 'property3', 'x') self.assertTrue(self.rule2.rule_matches_case(case, datetime(2016, 1, 1))) def test_get_rules_from_domain(self): rules = AutomaticUpdateRule.by_domain(self.domain) rules_by_case_type = AutomaticUpdateRule.organize_rules_by_case_type(rules) expected_case_types = ['test-case-type', 'test-case-type-2'] actual_case_types = rules_by_case_type.keys() self.assertEqual(set(expected_case_types), set(actual_case_types)) expected_rule_ids = [self.rule.pk] actual_rule_ids = [rule.pk for rule in rules_by_case_type['test-case-type']] self.assertEqual(set(expected_rule_ids), set(actual_rule_ids)) expected_rule_ids = [self.rule2.pk, self.rule3.pk] actual_rule_ids = [rule.pk for rule in rules_by_case_type['test-case-type-2']] self.assertEqual(set(expected_rule_ids), set(actual_rule_ids)) def test_boundary_date(self): rules = AutomaticUpdateRule.by_domain(self.domain) rules_by_case_type = AutomaticUpdateRule.organize_rules_by_case_type(rules) boundary_date = AutomaticUpdateRule.get_boundary_date( rules_by_case_type['test-case-type'], datetime(2016, 1, 1)) self.assertEqual(boundary_date, datetime(2015, 12, 2)) boundary_date = AutomaticUpdateRule.get_boundary_date( rules_by_case_type['test-case-type-2'], datetime(2016, 1, 1)) self.assertEqual(boundary_date, datetime(2015, 12, 2))
class ImporterTest(TestCase): def setUp(self): super(ImporterTest, self).setUp() self.domain_obj = create_domain("importer-test") self.domain = self.domain_obj.name self.default_case_type = 'importer-test-casetype' self.couch_user = WebUser.create(None, "test", "foobar") self.couch_user.add_domain_membership(self.domain, is_admin=True) self.couch_user.save() self.accessor = CaseAccessors(self.domain) self.factory = CaseFactory(domain=self.domain, case_defaults={ 'case_type': self.default_case_type, }) delete_all_cases() def tearDown(self): self.couch_user.delete() self.domain_obj.delete() super(ImporterTest, self).tearDown() def _config(self, col_names, search_column=None, case_type=None, search_field='case_id', create_new_cases=True): return ImporterConfig( couch_user_id=self.couch_user._id, case_type=case_type or self.default_case_type, excel_fields=col_names, case_fields=[''] * len(col_names), custom_fields=col_names, search_column=search_column or col_names[0], search_field=search_field, create_new_cases=create_new_cases, ) @run_with_all_backends @patch('corehq.apps.case_importer.tasks.bulk_import_async.update_state') def testImportNone(self, update_state): res = bulk_import_async.delay(self._config(['anything']), self.domain, None) self.assertIsInstance(res.result, Ignore) update_state.assert_called_with( state=states.FAILURE, meta={'errors': 'Sorry, your session has expired. Please start over and try again.'}) self.assertEqual(0, len(get_case_ids_in_domain(self.domain))) @run_with_all_backends def testImportBasic(self): config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], ['case_id-0', 'age-0', 'sex-0', 'location-0'], ['case_id-1', 'age-1', 'sex-1', 'location-1'], ['case_id-2', 'age-2', 'sex-2', 'location-2'], ['case_id-3', 'age-3', 'sex-3', 'location-3'], ['case_id-4', 'age-4', 'sex-4', 'location-4'], ) res = do_import(file, config, self.domain) self.assertEqual(5, res['created_count']) self.assertEqual(0, res['match_count']) self.assertFalse(res['errors']) self.assertEqual(1, res['num_chunks']) case_ids = self.accessor.get_case_ids_in_domain() cases = list(self.accessor.get_cases(case_ids)) self.assertEqual(5, len(cases)) properties_seen = set() for case in cases: self.assertEqual(self.couch_user._id, case.user_id) self.assertEqual(self.couch_user._id, case.owner_id) self.assertEqual(self.default_case_type, case.type) for prop in ['age', 'sex', 'location']: self.assertTrue(prop in case.get_case_property(prop)) self.assertFalse(case.get_case_property(prop) in properties_seen) properties_seen.add(case.get_case_property(prop)) @run_with_all_backends def testImportNamedColumns(self): config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], ['case_id-0', 'age-0', 'sex-0', 'location-0'], ['case_id-1', 'age-1', 'sex-1', 'location-1'], ['case_id-2', 'age-2', 'sex-2', 'location-2'], ['case_id-3', 'age-3', 'sex-3', 'location-3'], ) res = do_import(file, config, self.domain) self.assertEqual(4, res['created_count']) self.assertEqual(4, len(self.accessor.get_case_ids_in_domain())) @run_with_all_backends def testImportTrailingWhitespace(self): cols = ['case_id', 'age', 'sex\xa0', 'location'] config = self._config(cols) file = make_worksheet_wrapper( ['case_id', 'age', 'sex\xa0', 'location'], ['case_id-0', 'age-0', 'sex\xa0-0', 'location-0'], ) res = do_import(file, config, self.domain) self.assertEqual(1, res['created_count']) case_ids = self.accessor.get_case_ids_in_domain() self.assertEqual(1, len(case_ids)) case = self.accessor.get_case(case_ids[0]) self.assertTrue(bool(case.get_case_property('sex'))) # make sure the value also got properly set @run_with_all_backends def testCaseIdMatching(self): # bootstrap a stub case [case] = self.factory.create_or_update_case(CaseStructure(attrs={ 'create': True, 'update': {'importer_test_prop': 'foo'}, })) self.assertEqual(1, len(self.accessor.get_case_ids_in_domain())) config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], [case.case_id, 'age-0', 'sex-0', 'location-0'], [case.case_id, 'age-1', 'sex-1', 'location-1'], [case.case_id, 'age-2', 'sex-2', 'location-2'], ) res = do_import(file, config, self.domain) self.assertEqual(0, res['created_count']) self.assertEqual(3, res['match_count']) self.assertFalse(res['errors']) # shouldn't create any more cases, just the one case_ids = self.accessor.get_case_ids_in_domain() self.assertEqual(1, len(case_ids)) [case] = self.accessor.get_cases(case_ids) for prop in ['age', 'sex', 'location']: self.assertTrue(prop in case.get_case_property(prop)) # shouldn't touch existing properties self.assertEqual('foo', case.get_case_property('importer_test_prop')) @run_with_all_backends def testCaseLookupTypeCheck(self): [case] = self.factory.create_or_update_case(CaseStructure(attrs={ 'create': True, 'case_type': 'nonmatch-type', })) self.assertEqual(1, len(self.accessor.get_case_ids_in_domain())) config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], [case.case_id, 'age-0', 'sex-0', 'location-0'], [case.case_id, 'age-1', 'sex-1', 'location-1'], [case.case_id, 'age-2', 'sex-2', 'location-2'], ) res = do_import(file, config, self.domain) # because the type is wrong these shouldn't match self.assertEqual(3, res['created_count']) self.assertEqual(0, res['match_count']) self.assertEqual(4, len(self.accessor.get_case_ids_in_domain())) @run_with_all_backends def testCaseLookupDomainCheck(self): self.factory.domain = 'wrong-domain' [case] = self.factory.create_or_update_case(CaseStructure(attrs={ 'create': True, })) self.assertEqual(0, len(self.accessor.get_case_ids_in_domain())) config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], [case.case_id, 'age-0', 'sex-0', 'location-0'], [case.case_id, 'age-1', 'sex-1', 'location-1'], [case.case_id, 'age-2', 'sex-2', 'location-2'], ) res = do_import(file, config, self.domain) # because the domain is wrong these shouldn't match self.assertEqual(3, res['created_count']) self.assertEqual(0, res['match_count']) self.assertEqual(3, len(self.accessor.get_case_ids_in_domain())) @run_with_all_backends def testExternalIdMatching(self): # bootstrap a stub case external_id = 'importer-test-external-id' [case] = self.factory.create_or_update_case(CaseStructure( attrs={ 'create': True, 'external_id': external_id, } )) self.assertEqual(1, len(self.accessor.get_case_ids_in_domain())) headers = ['external_id', 'age', 'sex', 'location'] config = self._config(headers, search_field='external_id') file = make_worksheet_wrapper( ['external_id', 'age', 'sex', 'location'], ['importer-test-external-id', 'age-0', 'sex-0', 'location-0'], ['importer-test-external-id', 'age-1', 'sex-1', 'location-1'], ['importer-test-external-id', 'age-2', 'sex-2', 'location-2'], ) res = do_import(file, config, self.domain) self.assertEqual(0, res['created_count']) self.assertEqual(3, res['match_count']) self.assertFalse(res['errors']) # shouldn't create any more cases, just the one self.assertEqual(1, len(self.accessor.get_case_ids_in_domain())) @run_with_all_backends def test_external_id_matching_on_create_with_custom_column_name(self): headers = ['id_column', 'age', 'sex', 'location'] external_id = 'external-id-test' config = self._config(headers[1:], search_column='id_column', search_field='external_id') file = make_worksheet_wrapper( ['id_column', 'age', 'sex', 'location'], ['external-id-test', 'age-0', 'sex-0', 'location-0'], ['external-id-test', 'age-1', 'sex-1', 'location-1'], ) res = do_import(file, config, self.domain) self.assertFalse(res['errors']) self.assertEqual(1, res['created_count']) self.assertEqual(1, res['match_count']) case_ids = self.accessor.get_case_ids_in_domain() self.assertEqual(1, len(case_ids)) case = self.accessor.get_case(case_ids[0]) self.assertEqual(external_id, case.external_id) def testNoCreateNew(self): config = self._config(['case_id', 'age', 'sex', 'location'], create_new_cases=False) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], ['case_id-0', 'age-0', 'sex-0', 'location-0'], ['case_id-1', 'age-1', 'sex-1', 'location-1'], ['case_id-2', 'age-2', 'sex-2', 'location-2'], ['case_id-3', 'age-3', 'sex-3', 'location-3'], ['case_id-4', 'age-4', 'sex-4', 'location-4'], ) res = do_import(file, config, self.domain) # no matching and no create new set - should do nothing self.assertEqual(0, res['created_count']) self.assertEqual(0, res['match_count']) self.assertEqual(0, len(get_case_ids_in_domain(self.domain))) def testBlankRows(self): # don't create new cases for rows left blank config = self._config(['case_id', 'age', 'sex', 'location'], create_new_cases=True) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], [None, None, None, None], ['', '', '', ''], ) res = do_import(file, config, self.domain) # no matching and no create new set - should do nothing self.assertEqual(0, res['created_count']) self.assertEqual(0, res['match_count']) self.assertEqual(0, len(get_case_ids_in_domain(self.domain))) def testBasicChunking(self): config = self._config(['case_id', 'age', 'sex', 'location']) file = make_worksheet_wrapper( ['case_id', 'age', 'sex', 'location'], ['case_id-0', 'age-0', 'sex-0', 'location-0'], ['case_id-1', 'age-1', 'sex-1', 'location-1'], ['case_id-2', 'age-2', 'sex-2', 'location-2'], ['case_id-3', 'age-3', 'sex-3', 'location-3'], ['case_id-4', 'age-4', 'sex-4', 'location-4'], ) res = do_import(file, config, self.domain, chunksize=2) # 5 cases in chunks of 2 = 3 chunks self.assertEqual(3, res['num_chunks']) self.assertEqual(5, res['created_count']) self.assertEqual(5, len(get_case_ids_in_domain(self.domain))) @run_with_all_backends def testExternalIdChunking(self): # bootstrap a stub case external_id = 'importer-test-external-id' headers = ['external_id', 'age', 'sex', 'location'] config = self._config(headers, search_field='external_id') file = make_worksheet_wrapper( ['external_id', 'age', 'sex', 'location'], ['importer-test-external-id', 'age-0', 'sex-0', 'location-0'], ['importer-test-external-id', 'age-1', 'sex-1', 'location-1'], ['importer-test-external-id', 'age-2', 'sex-2', 'location-2'], ) # the first one should create the case, and the remaining two should update it res = do_import(file, config, self.domain) self.assertEqual(1, res['created_count']) self.assertEqual(2, res['match_count']) self.assertFalse(res['errors']) self.assertEqual(2, res['num_chunks']) # the lookup causes an extra chunk # should just create the one case case_ids = self.accessor.get_case_ids_in_domain() self.assertEqual(1, len(case_ids)) [case] = self.accessor.get_cases(case_ids) self.assertEqual(external_id, case.external_id) for prop in ['age', 'sex', 'location']: self.assertTrue(prop in case.get_case_property(prop)) @run_with_all_backends def testParentCase(self): headers = ['parent_id', 'name', 'case_id'] config = self._config(headers, create_new_cases=True, search_column='case_id') rows = 3 [parent_case] = self.factory.create_or_update_case(CaseStructure(attrs={'create': True})) self.assertEqual(1, len(self.accessor.get_case_ids_in_domain())) file = make_worksheet_wrapper( ['parent_id', 'name', 'case_id'], [parent_case.case_id, 'name-0', 'case_id-0'], [parent_case.case_id, 'name-1', 'case_id-1'], [parent_case.case_id, 'name-2', 'case_id-2'], ) file_missing = make_worksheet_wrapper( ['parent_id', 'name', 'case_id'], ['parent_id-0', 'name-0', 'case_id-0'], ['parent_id-1', 'name-1', 'case_id-1'], ['parent_id-2', 'name-2', 'case_id-2'], ) # Should successfully match on `rows` cases res = do_import(file, config, self.domain) self.assertEqual(rows, res['created_count']) # Should be unable to find parent case on `rows` cases res = do_import(file_missing, config, self.domain) error_column_name = 'parent_id' self.assertEqual(rows, len(res['errors'][ImportErrors.InvalidParentId][error_column_name]['rows']), "All cases should have missing parent") def import_mock_file(self, rows): config = self._config(rows[0]) xls_file = make_worksheet_wrapper(*rows) return do_import(xls_file, config, self.domain) @run_with_all_backends def testLocationOwner(self): # This is actually testing several different things, but I figure it's # worth it, as each of these tests takes a non-trivial amount of time. non_case_sharing = LocationType.objects.create( domain=self.domain, name='lt1', shares_cases=False ) case_sharing = LocationType.objects.create( domain=self.domain, name='lt2', shares_cases=True ) location = make_loc('loc-1', 'Loc 1', self.domain, case_sharing.code) make_loc('loc-2', 'Loc 2', self.domain, case_sharing.code) duplicate_loc = make_loc('loc-3', 'Loc 2', self.domain, case_sharing.code) improper_loc = make_loc('loc-4', 'Loc 4', self.domain, non_case_sharing.code) res = self.import_mock_file([ ['case_id', 'name', 'owner_id', 'owner_name'], ['', 'location-owner-id', location.group_id, ''], ['', 'location-owner-code', '', location.site_code], ['', 'location-owner-name', '', location.name], ['', 'duplicate-location-name', '', duplicate_loc.name], ['', 'non-case-owning-name', '', improper_loc.name], ]) case_ids = self.accessor.get_case_ids_in_domain() cases = {c.name: c for c in list(self.accessor.get_cases(case_ids))} self.assertEqual(cases['location-owner-id'].owner_id, location.group_id) self.assertEqual(cases['location-owner-code'].owner_id, location.group_id) self.assertEqual(cases['location-owner-name'].owner_id, location.group_id) error_message = ImportErrors.DuplicateLocationName error_column_name = None self.assertIn(error_message, res['errors']) self.assertEqual(res['errors'][error_message][error_column_name]['rows'], [5]) error_message = ImportErrors.InvalidOwnerId self.assertIn(error_message, res['errors']) error_column_name = 'owner_id' self.assertEqual(res['errors'][error_message][error_column_name]['rows'], [6]) @run_with_all_backends def test_opened_on(self): case = self.factory.create_case() new_date = '2015-04-30T14:41:53.000000Z' with flag_enabled('BULK_UPLOAD_DATE_OPENED'): self.import_mock_file([ ['case_id', 'date_opened'], [case.case_id, new_date] ]) case = CaseAccessors(self.domain).get_case(case.case_id) self.assertEqual(case.opened_on, PhoneTime(parse_datetime(new_date)).done()) @run_with_all_backends def test_columns_and_rows_align(self): case_owner = CommCareUser.create(self.domain, 'username', 'pw') res = self.import_mock_file([ ['case_id', 'name', '', 'favorite_color', 'owner_id'], ['', 'Jeff', '', 'blue', case_owner._id], ['', 'Caroline', '', 'yellow', case_owner._id], ]) self.assertEqual(res['errors'], {}) case_ids = self.accessor.get_case_ids_in_domain() cases = {c.name: c for c in list(self.accessor.get_cases(case_ids))} self.assertEqual(cases['Jeff'].owner_id, case_owner._id) self.assertEqual(cases['Jeff'].get_case_property('favorite_color'), 'blue') self.assertEqual(cases['Caroline'].owner_id, case_owner._id) self.assertEqual(cases['Caroline'].get_case_property('favorite_color'), 'yellow')
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] )
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)