def check_user_has_case(testcase, user, case_blocks, should_have=True, line_by_line=True, restore_id="", version=V2, purge_restore_cache=False, return_single=False): if restore_id and purge_restore_cache: SyncLog.get(restore_id).invalidate_cached_payloads() restore_config = RestoreConfig(project=user.project, restore_user=user, params=RestoreParams(restore_id, version=version)) payload_string = restore_config.get_payload().as_string() return check_payload_has_cases( testcase=testcase, payload_string=payload_string, username=user.username, case_blocks=case_blocks, should_have=should_have, line_by_line=line_by_line, version=version, return_single=return_single, restore_config=restore_config, )
def testCacheInvalidation(self): original_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # posting a case associated with this sync token should invalidate the cache case_id = "cache_invalidation" self._createCaseStubs([case_id]) self.sync_log = SyncLog.get(self.sync_log._id) self.assertFalse(self.sync_log.has_cached_payload(V2)) # resyncing should recreate the cache next_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) self.assertNotEqual(original_payload, next_payload) self.assertFalse(case_id in original_payload) self.assertTrue(case_id in next_payload)
def testShouldHaveCase(self): case_id = "should_have" self._createCaseStubs([case_id]) sync_log = SyncLog.get(self.sync_log._id) self.assertEqual(1, len(sync_log.cases_on_phone)) self.assertEqual(case_id, sync_log.cases_on_phone[0].case_id) # manually delete it and then try to update sync_log.cases_on_phone = [] sync_log.save() update = CaseBlock( create=False, case_id=case_id, user_id=USER_ID, owner_id=USER_ID, case_type=PARENT_TYPE, version=V2, update={'something': "changed"}, ).as_xml() # this should work because it should magically fix itself self._postFakeWithSyncToken(update, self.sync_log.get_id) sync_log = SyncLog.get(self.sync_log._id) self.assertFalse(getattr(sync_log, 'has_assert_errors', False))
def testShouldHaveCase(self): case_id = "should_have" self._createCaseStubs([case_id]) sync_log = SyncLog.get(self.sync_log._id) self.assertEqual(1, len(sync_log.cases_on_phone)) self.assertEqual(case_id, sync_log.cases_on_phone[0].case_id) # manually delete it and then try to update sync_log.cases_on_phone = [] sync_log.save() update = CaseBlock( create=False, case_id=case_id, user_id=USER_ID, owner_id=USER_ID, case_type=PARENT_TYPE, version=V2, update={ 'something': "changed" }, ).as_xml() # this should work because it should magically fix itself self._postFakeWithSyncToken(update, self.sync_log.get_id) sync_log = SyncLog.get(self.sync_log._id) self.assertFalse(getattr(sync_log, 'has_assert_errors', False))
def testCacheInvalidation(self): original_payload = RestoreConfig( self.user, version=V2, restore_id=self.sync_log._id, ).get_payload().as_string() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # posting a case associated with this sync token should invalidate the cache case_id = "cache_invalidation" self._createCaseStubs([case_id]) self.sync_log = SyncLog.get(self.sync_log._id) self.assertFalse(self.sync_log.has_cached_payload(V2)) # resyncing should recreate the cache next_payload = RestoreConfig( self.user, version=V2, restore_id=self.sync_log._id, ).get_payload().as_string() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) self.assertNotEqual(original_payload, next_payload) self.assertFalse(case_id in original_payload) # since it was our own update, it shouldn't be in the new payload either self.assertFalse(case_id in next_payload) # we can be explicit about why this is the case self.assertTrue(self.sync_log.phone_has_case(case_id))
def check_user_has_case(testcase, user, case_blocks, should_have=True, line_by_line=True, restore_id="", version=V2, purge_restore_cache=False, return_single=False): if not isinstance(case_blocks, list): case_blocks = [case_blocks] return_single = True XMLNS = NS_VERSION_MAP.get(version, 'http://openrosa.org/http/response') if restore_id and purge_restore_cache: SyncLog.get(restore_id).invalidate_cached_payloads() restore_config = RestoreConfig( project=user.project, restore_user=user, params=RestoreParams(restore_id, version=version) ) payload_string = restore_config.get_payload().as_string() blocks_from_restore = extract_caseblocks_from_xml(payload_string, version) def check_block(case_block): case_block.set('xmlns', XMLNS) case_block = RestoreCaseBlock(ElementTree.fromstring(ElementTree.tostring(case_block)), version=version) case_id = case_block.get_case_id() n = 0 def extra_info(): return "\n%s\n%s" % (case_block.to_string(), map(lambda b: b.to_string(), blocks_from_restore)) match = None for block in blocks_from_restore: if block.get_case_id() == case_id: if should_have: if line_by_line: check_xml_line_by_line( testcase, case_block.to_string(), block.to_string(), ) match = block n += 1 if n == 2: testcase.fail( "Block for case_id '%s' appears twice" " in ota restore for user '%s':%s" % (case_id, user.username, extra_info()) ) else: testcase.fail( "User '%s' gets case '%s' " "but shouldn't:%s" % (user.username, case_id, extra_info()) ) if not n and should_have: testcase.fail("Block for case_id '%s' doesn't appear in ota restore for user '%s':%s" % (case_id, user.username, extra_info())) return match matches = [check_block(case_block) for case_block in case_blocks] return restore_config, matches[0] if return_single else matches
def testCacheNonInvalidation(self): original_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # posting a case associated with this sync token should invalidate the cache # submitting a case not with the token will not touch the cache for that token case_id = "cache_noninvalidation" post_case_blocks([ CaseBlock( create=True, case_id=case_id, user_id=self.user.user_id, owner_id=self.user.user_id, case_type=PARENT_TYPE, version=V2, ).as_xml() ]) next_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.assertEqual(original_payload, next_payload) self.assertFalse(case_id in next_payload)
def testOtherUserUpdatesIndex(self): # create a parent and child case (with index) from one user parent_id = "other_updates_index_parent" case_id = "other_updates_index_child" self._createCaseStubs([parent_id]) child = CaseBlock( create=True, case_id=case_id, user_id=USER_ID, owner_id=USER_ID, version=V2, index={'mother': ('mother', parent_id)} ).as_xml() self._postFakeWithSyncToken(child, self.sync_log.get_id) assert_user_doesnt_have_case(self, self.user, parent_id, restore_id=self.sync_log.get_id) assert_user_doesnt_have_case(self, self.user, case_id, restore_id=self.sync_log.get_id) # assign the parent case away from same user parent_update = CaseBlock( create=False, case_id=parent_id, user_id=USER_ID, owner_id=OTHER_USER_ID, update={"greeting": "hello"}, version=V2).as_xml() self._postFakeWithSyncToken(parent_update, self.sync_log.get_id) self.sync_log = SyncLog.get(self.sync_log.get_id) # these tests added to debug another issue revealed by this test self.assertTrue(self.sync_log.phone_has_case(case_id)) self.assertTrue(self.sync_log.phone_has_dependent_case(parent_id)) self.assertTrue(self.sync_log.phone_is_holding_case(case_id)) self.assertTrue(self.sync_log.phone_is_holding_case(parent_id)) # original user syncs again # make sure there are no new changes assert_user_doesnt_have_case(self, self.user, parent_id, restore_id=self.sync_log.get_id, purge_restore_cache=True) assert_user_doesnt_have_case(self, self.user, case_id, restore_id=self.sync_log.get_id) assert_user_has_case(self, self.other_user, parent_id, restore_id=self.other_sync_log.get_id, purge_restore_cache=True) # update the parent case from another user self.other_sync_log = SyncLog.last_for_user(OTHER_USER_ID) other_parent_update = CaseBlock( create=False, case_id=parent_id, user_id=OTHER_USER_ID, update={"greeting2": "hi"}, version=V2 ).as_xml() self._postFakeWithSyncToken(other_parent_update, self.other_sync_log.get_id) # make sure the indexed case syncs again self.sync_log = SyncLog.last_for_user(USER_ID) assert_user_has_case(self, self.user, parent_id, restore_id=self.sync_log.get_id, purge_restore_cache=True)
def testMismatch(self): self.assertEqual(CaseStateHash(EMPTY_HASH), self.sync_log.get_state_hash()) c1 = CaseBlock(case_id="abc123", create=True, owner_id=self.user.user_id).as_xml() c2 = CaseBlock(case_id="123abc", create=True, owner_id=self.user.user_id).as_xml() post_case_blocks([c1, c2], form_extras={"last_sync_token": self.sync_log.get_id}) self.sync_log = SyncLog.get(self.sync_log.get_id) real_hash = CaseStateHash("409c5c597fa2c2a693b769f0d2ad432b") bad_hash = CaseStateHash("thisisntright") self.assertEqual(real_hash, self.sync_log.get_state_hash()) generate_restore_payload(self.user, self.sync_log.get_id, version=V2, state_hash=str(real_hash)) try: generate_restore_payload(self.user, self.sync_log.get_id, version=V2, state_hash=str(bad_hash)) self.fail("Call to generate a payload with a bad hash should fail!") except BadStateException, e: self.assertEqual(real_hash, e.expected) self.assertEqual(bad_hash, e.actual) self.assertEqual(2, len(e.case_ids)) self.assertTrue("abc123" in e.case_ids) self.assertTrue("123abc" in e.case_ids)
def testCacheNonInvalidation(self): original_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # posting a case associated with this sync token should invalidate the cache # submitting a case not with the token will not touch the cache for that token case_id = "cache_noninvalidation" post_case_blocks([CaseBlock( create=True, case_id=case_id, user_id=self.user.user_id, owner_id=self.user.user_id, case_type=PARENT_TYPE, version=V2, ).as_xml()]) next_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.assertEqual(original_payload, next_payload) self.assertFalse(case_id in next_payload)
def testCaching(self): self.assertFalse(self.sync_log.has_cached_payload(V2)) # first request should populate the cache original_payload = RestoreConfig( self.user, version=V2, restore_id=self.sync_log._id, ).get_payload().as_string() next_sync_log = synclog_from_restore_payload(original_payload) self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # a second request with the same config should be exactly the same cached_payload = RestoreConfig( self.user, version=V2, restore_id=self.sync_log._id, ).get_payload().as_string() self.assertEqual(original_payload, cached_payload) # caching a different version should also produce something new versioned_payload = RestoreConfig( self.user, version=V1, restore_id=self.sync_log._id, ).get_payload().as_string() self.assertNotEqual(original_payload, versioned_payload) versioned_sync_log = synclog_from_restore_payload(versioned_payload) self.assertNotEqual(next_sync_log._id, versioned_sync_log._id)
def process_cases(sender, xform, **kwargs): """Creates or updates case objects which live outside of the form""" # recursive import fail from casexml.apps.case.xform import get_or_update_cases # avoid Document conflicts cases = get_or_update_cases(xform).values() # attach domain if it's there if hasattr(xform, "domain"): domain = xform.domain def attach_domain(case): case.domain = domain if domain and hasattr(case, 'type'): case['#export_tag'] = ["domain", "type"] return case cases = [attach_domain(case) for case in cases] # HACK -- figure out how to do this more properly # todo: create a pillow for this if cases: case = cases[0] if case.location_ is not None: # should probably store this in computed_ xform.location_ = list(case.location_) # handle updating the sync records for apps that use sync mode if hasattr(xform, "last_sync_token") and xform.last_sync_token: relevant_log = SyncLog.get(xform.last_sync_token) relevant_log.update_phone_lists(xform, cases) # set flags for indicator pillows and save xform.initial_processing_complete = True xform.save() for case in cases: case.initial_processing_complete = True case.save()
def update_sync_log_with_checks(sync_log, xform, cases, case_db, case_id_blacklist=None): assert case_db is not None from casexml.apps.case.xform import CaseProcessingConfig case_id_blacklist = case_id_blacklist or [] try: sync_log.update_phone_lists(xform, cases) except SyncLogAssertionError, e: if e.case_id and e.case_id not in case_id_blacklist: form_ids = get_case_xform_ids(e.case_id) case_id_blacklist.append(e.case_id) for form_id in form_ids: if form_id != xform._id: form = XFormInstance.get(form_id) if form.doc_type in ['XFormInstance', 'XFormError']: reprocess_form_cases( form, CaseProcessingConfig( strict_asserts=True, case_id_blacklist=case_id_blacklist ), case_db=case_db ) updated_log = SyncLog.get(sync_log._id) update_sync_log_with_checks(updated_log, xform, cases, case_db, case_id_blacklist=case_id_blacklist)
def update_sync_log_with_checks(sync_log, xform, cases, case_id_blacklist=None): from casexml.apps.case.xform import CaseProcessingConfig case_id_blacklist = case_id_blacklist or [] try: sync_log.update_phone_lists(xform, cases) except SyncLogAssertionError, e: if e.case_id and e.case_id not in case_id_blacklist: form_ids = get_case_xform_ids(e.case_id) case_id_blacklist.append(e.case_id) for form_id in form_ids: if form_id != xform._id: form = XFormInstance.get(form_id) if form.doc_type in ['XFormInstance', 'XFormError']: reprocess_form_cases( form, CaseProcessingConfig( strict_asserts=True, case_id_blacklist=case_id_blacklist)) updated_log = SyncLog.get(sync_log._id) update_sync_log_with_checks(updated_log, xform, cases, case_id_blacklist=case_id_blacklist)
def _process_cases(xform, config, case_db): cases = get_or_update_cases(xform, case_db).values() if config.reconcile: for c in cases: c.reconcile_actions(rebuild=True) # attach domain and export tag if domain is there if hasattr(xform, "domain"): domain = xform.domain def attach_extras(case): case.domain = domain if domain: assert hasattr(case, "type") case["#export_tag"] = ["domain", "type"] return case cases = [attach_extras(case) for case in cases] # handle updating the sync records for apps that use sync mode last_sync_token = getattr(xform, "last_sync_token", None) if last_sync_token: relevant_log = SyncLog.get(last_sync_token) # in reconciliation mode, things can be unexpected relevant_log.strict = config.strict_asserts from casexml.apps.case.util import update_sync_log_with_checks update_sync_log_with_checks(relevant_log, xform, cases, case_id_blacklist=config.case_id_blacklist) if config.reconcile: relevant_log.reconcile_cases() relevant_log.save() try: cases_received.send(sender=None, xform=xform, cases=cases) except Exception as e: # don't let the exceptions in signals prevent standard case processing notify_exception( None, "something went wrong sending the cases_received signal " "for form %s: %s" % (xform._id, e) ) for case in cases: if not case.check_action_order(): try: case.reconcile_actions(rebuild=True) except ReconciliationError: pass case.force_save() # set flags for indicator pillows and save xform.initial_processing_complete = True # if there are pillows or other _changes listeners competing to update # this form, override them. this will create a new entry in the feed # that they can re-pick up on xform.save(force_update=True) return cases
def sync_log(self): if self.restore_id: sync_log = SyncLog.get(self.restore_id) if sync_log.user_id == self.user.user_id and sync_log.doc_type == "SyncLog": return sync_log else: raise HttpException(412) else: return None
def sync_log(self): if self.restore_id: sync_log = SyncLog.get(self.restore_id) if sync_log.user_id == self.user.user_id \ and sync_log.doc_type == 'SyncLog': return sync_log else: raise HttpException(412) else: return None
def get_sync_token(self): from casexml.apps.phone.models import SyncLog if self.last_sync_token: try: return SyncLog.get(self.last_sync_token) except ResourceNotFound: logging.exception('No sync token with ID {} found. Form is {} in domain {}'.format( self.last_sync_token, self._id, self.domain, )) raise return None
def check_user_has_case(testcase, user, case_block, should_have=True, line_by_line=True, restore_id="", version=V1, purge_restore_cache=False): XMLNS = NS_VERSION_MAP.get(version, 'http://openrosa.org/http/response') case_block.set('xmlns', XMLNS) case_block = ElementTree.fromstring(ElementTree.tostring(case_block)) if restore_id and purge_restore_cache: SyncLog.get(restore_id).invalidate_cached_payloads() payload_string = RestoreConfig(user, restore_id, version=version).get_payload() payload = ElementTree.fromstring(payload_string) blocks = payload.findall('{{{0}}}case'.format(XMLNS)) def get_case_id(block): if version == V1: return block.findtext('{{{0}}}case_id'.format(XMLNS)) else: return block.get('case_id') case_id = get_case_id(case_block) n = 0 def extra_info(): return "\n%s\n%s" % (ElementTree.tostring(case_block), map(ElementTree.tostring, blocks)) match = None for block in blocks: if get_case_id(block) == case_id: if should_have: if line_by_line: check_xml_line_by_line(testcase, ElementTree.tostring(case_block), ElementTree.tostring(block)) match = block n += 1 if n == 2: testcase.fail("Block for case_id '%s' appears twice in ota restore for user '%s':%s" % (case_id, user.username, extra_info())) else: testcase.fail("User '%s' gets case '%s' but shouldn't:%s" % (user.username, case_id, extra_info())) if not n and should_have: testcase.fail("Block for case_id '%s' doesn't appear in ota restore for user '%s':%s" \ % (case_id, user.username, extra_info())) return match
def _testUpdate(self, sync_id, case_id_map, dependent_case_id_map={}): sync_log = SyncLog.get(sync_id) # check case map self.assertEqual(len(case_id_map), len(sync_log.cases_on_phone)) for case_id, indices in case_id_map.items(): self.assertTrue(sync_log.phone_has_case(case_id)) state = sync_log.get_case_state(case_id) self._checkLists(indices, state.indices) # check dependent case map self.assertEqual(len(dependent_case_id_map), len(sync_log.dependent_cases_on_phone)) for case_id, indices in dependent_case_id_map.items(): self.assertTrue(sync_log.phone_has_dependent_case(case_id)) state = sync_log.get_dependent_case_state(case_id) self._checkLists(indices, state.indices)
def _testUpdate(self, sync_id, case_id_map, dependent_case_id_map=None): dependent_case_id_map = dependent_case_id_map or {} sync_log = SyncLog.get(sync_id) # check case map self.assertEqual(len(case_id_map), len(sync_log.cases_on_phone)) for case_id, indices in case_id_map.items(): self.assertTrue(sync_log.phone_has_case(case_id)) state = sync_log.get_case_state(case_id) self._checkLists(indices, state.indices) # check dependent case map self.assertEqual(len(dependent_case_id_map), len(sync_log.dependent_cases_on_phone)) for case_id, indices in dependent_case_id_map.items(): self.assertTrue(sync_log.phone_has_dependent_case(case_id)) state = sync_log.get_dependent_case_state(case_id) self._checkLists(indices, state.indices)
def testCaching(self): self.assertFalse(self.sync_log.has_cached_payload(V2)) # first request should populate the cache original_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() next_sync_log = synclog_from_restore_payload(original_payload) self.sync_log = SyncLog.get(self.sync_log._id) self.assertTrue(self.sync_log.has_cached_payload(V2)) # a second request with the same config should be exactly the same cached_payload = RestoreConfig( self.user, version=V2, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.assertEqual(original_payload, cached_payload) # a second request without caching should be different (generate a new id) uncached_payload = RestoreConfig( self.user, version=V2, caching_enabled=False, restore_id=self.sync_log._id, ).get_payload() self.assertNotEqual(original_payload, uncached_payload) uncached_sync_log = synclog_from_restore_payload(uncached_payload) self.assertNotEqual(next_sync_log._id, uncached_sync_log._id) # caching a different version should also produce something new versioned_payload = RestoreConfig( self.user, version=V1, caching_enabled=True, restore_id=self.sync_log._id, ).get_payload() self.assertNotEqual(original_payload, versioned_payload) versioned_sync_log = synclog_from_restore_payload(versioned_payload) self.assertNotEqual(next_sync_log._id, versioned_sync_log._id)
def sync_log(self): if self.restore_id: try: sync_log = SyncLog.get(self.restore_id) except ResourceNotFound: # if we are in loose mode, return an HTTP 412 so that the phone will # just force a fresh sync if LOOSE_SYNC_TOKEN_VALIDATION.enabled(self.domain): raise HttpException(412) else: raise if sync_log.user_id == self.user.user_id \ and sync_log.doc_type == 'SyncLog': return sync_log else: raise HttpException(412) else: return None
def generate_restore_payload(user, restore_id="", version="1.0", state_hash=""): """ Gets an XML payload suitable for OTA restore. If you need to do something other than find all cases matching user_id = user.user_id then you have to pass in a user object that overrides the get_case_updates() method. It should match the same signature as models.user.get_case_updates(): user: who the payload is for. must implement get_case_updates restore_id: sync token version: the CommCare version returns: the xml payload of the sync operation """ check_version(version) last_sync = None if restore_id: try: last_sync = SyncLog.get(restore_id) except Exception: logging.error("Request for bad sync log %s by %s, ignoring..." % (restore_id, user)) if last_sync and state_hash: parsed_hash = CaseStateHash.parse(state_hash) if last_sync.get_state_hash() != parsed_hash: raise BadStateException(expected=last_sync.get_state_hash(), actual=parsed_hash, case_ids=last_sync.get_footprint_of_cases_on_phone()) sync_operation = user.get_case_updates(last_sync) case_xml_elements = [xml.get_case_element(op.case, op.required_updates, version) \ for op in sync_operation.actual_cases_to_sync] last_seq = get_db().info()["update_seq"] # create a sync log for this previous_log_id = last_sync.get_id if last_sync else None synclog = SyncLog(user_id=user.user_id, last_seq=last_seq, owner_ids_on_phone=user.get_owner_ids(), date=datetime.utcnow(), previous_log_id=previous_log_id, cases_on_phone=[CaseState.from_case(c) for c in \ sync_operation.actual_owned_cases], dependent_cases_on_phone=[CaseState.from_case(c) for c in \ sync_operation.actual_extended_cases]) synclog.save() # start with standard response response = get_response_element( "Successfully restored account %s!" % user.username, ResponseNature.OTA_RESTORE_SUCCESS) # add sync token info response.append(xml.get_sync_element(synclog.get_id)) # registration block response.append(xml.get_registration_element(user)) # fixture block for fixture in generator.get_fixtures(user, version, last_sync): response.append(fixture) # case blocks for case_elem in case_xml_elements: response.append(case_elem) return xml.tostring(response)
def synclog_from_restore_payload(restore_payload): return SyncLog.get(synclog_id_from_restore_payload(restore_payload))
def testOtherUserUpdatesIndex(self): # create a parent and child case (with index) from one user parent_id = "other_updates_index_parent" case_id = "other_updates_index_child" self._createCaseStubs([parent_id]) parent = CaseBlock(case_id=parent_id, version=V2).as_xml() child = CaseBlock(create=True, case_id=case_id, user_id=USER_ID, owner_id=USER_ID, version=V2, index={ 'mother': ('mother', parent_id) }).as_xml() self._postFakeWithSyncToken(child, self.sync_log.get_id) assert_user_doesnt_have_case(self, self.user, parent_id, restore_id=self.sync_log.get_id) assert_user_doesnt_have_case(self, self.user, case_id, restore_id=self.sync_log.get_id) # assign the parent case away from same user parent_update = CaseBlock(create=False, case_id=parent_id, user_id=USER_ID, owner_id=OTHER_USER_ID, update={ "greeting": "hello" }, version=V2).as_xml() self._postFakeWithSyncToken(parent_update, self.sync_log.get_id) self.sync_log = SyncLog.get(self.sync_log.get_id) # these tests added to debug another issue revealed by this test self.assertTrue(self.sync_log.phone_has_case(case_id)) self.assertTrue(self.sync_log.phone_has_dependent_case(parent_id)) self.assertTrue(self.sync_log.phone_is_holding_case(case_id)) self.assertTrue(self.sync_log.phone_is_holding_case(parent_id)) # original user syncs again # make sure there are no new changes assert_user_doesnt_have_case(self, self.user, parent_id, restore_id=self.sync_log.get_id) assert_user_doesnt_have_case(self, self.user, case_id, restore_id=self.sync_log.get_id) # update the parent case from another user assert_user_has_case(self, self.other_user, parent_id, restore_id=self.other_sync_log.get_id) self.other_sync_log = SyncLog.last_for_user(OTHER_USER_ID) other_parent_update = CaseBlock(create=False, case_id=parent_id, user_id=OTHER_USER_ID, update={ "greeting2": "hi" }, version=V2).as_xml() self._postFakeWithSyncToken(other_parent_update, self.other_sync_log.get_id) # make sure the indexed case syncs again self.sync_log = SyncLog.last_for_user(USER_ID) assert_user_has_case(self, self.user, parent_id, restore_id=self.sync_log.get_id)
def get_sync_token(self): if self.last_sync_token: return SyncLog.get(self.last_sync_token) return None
domain = xform.domain def attach_extras(case): case.domain = domain if domain: assert hasattr(case, 'type') case['#export_tag'] = ["domain", "type"] return case cases = [attach_extras(case) for case in cases] # handle updating the sync records for apps that use sync mode last_sync_token = getattr(xform, 'last_sync_token', None) if last_sync_token: relevant_log = SyncLog.get(last_sync_token) # in reconciliation mode, things can be unexpected relevant_log.strict = config.strict_asserts from casexml.apps.case.util import update_sync_log_with_checks update_sync_log_with_checks(relevant_log, xform, cases, case_id_blacklist=config.case_id_blacklist) if config.reconcile: relevant_log.reconcile_cases() relevant_log.save() try: cases_received.send(sender=None, xform=xform, cases=cases) except Exception as e: