def testEmpty(self): empty_hash = CaseStateHash(EMPTY_HASH) wrong_hash = CaseStateHash("thisisntright") self.assertEqual(empty_hash, self.sync_log.get_state_hash()) response = generate_restore_response(self.project, self.user, self.sync_log.get_id, version=V2) self.assertEqual(200, response.status_code) try: generate_restore_payload(self.project, self.user, self.sync_log.get_id, version=V2, state_hash=str(wrong_hash)) self.fail( "Call to generate a payload with a bad hash should fail!") except BadStateException as e: self.assertEqual(empty_hash, e.server_hash) self.assertEqual(wrong_hash, e.phone_hash) self.assertEqual(0, len(e.case_ids)) response = generate_restore_response(self.project, self.user, self.sync_log.get_id, version=V2, state_hash=str(wrong_hash)) self.assertEqual(412, response.status_code)
def testMismatch(self): sync = self.device.last_sync self.assertEqual(CaseStateHash(EMPTY_HASH), sync.log.get_state_hash()) c1 = CaseBlock(case_id="abc123", create=True, owner_id=self.user.user_id) c2 = CaseBlock(case_id="123abc", create=True, owner_id=self.user.user_id) self.device.post_changes([c1, c2]) real_hash = CaseStateHash("409c5c597fa2c2a693b769f0d2ad432b") bad_hash = CaseStateHash("thisisntright") self.assertEqual(real_hash, sync.get_log().get_state_hash()) self.device.sync(state_hash=str(real_hash)) self.device.last_sync = sync try: self.device.sync(state_hash=str(bad_hash)) except BadStateException as e: self.assertEqual(real_hash, e.server_hash) self.assertEqual(bad_hash, e.phone_hash) self.assertEqual(set(e.case_ids), {"abc123", "123abc"}) else: self.fail( "Call to generate a payload with a bad hash should fail!")
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 = get_properly_wrapped_sync_log(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.project, self.user, self.sync_log.get_id, version=V2, state_hash=str(real_hash) ) try: generate_restore_payload(self.project, 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.server_hash) self.assertEqual(bad_hash, e.phone_hash) self.assertEqual(2, len(e.case_ids)) self.assertTrue("abc123" in e.case_ids) self.assertTrue("123abc" in e.case_ids)
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() exception = BadStateException( server_hash=computed_hash, phone_hash=parsed_hash, case_ids=self.last_sync_log. get_footprint_of_cases_on_phone()) if self.last_sync_log.log_format == LOG_FORMAT_SIMPLIFIED: from corehq.apps.reports.standard.deployments import SyncHistoryReport _assert = soft_assert(to=['czue' + '@' + 'dimagi.com']) sync_history_url = '{}?individual={}'.format( SyncHistoryReport.get_url(self.domain), self.user.user_id) _assert( False, '{}, sync history report: {}'.format( exception, sync_history_url)) raise exception
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() exception = BadStateException( server_hash=computed_hash, phone_hash=parsed_hash, case_ids=self.last_sync_log.get_footprint_of_cases_on_phone() ) if self.last_sync_log.log_format == LOG_FORMAT_SIMPLIFIED: from corehq.apps.reports.standard.deployments import SyncHistoryReport _assert = soft_assert(to=['czue' + '@' + 'dimagi.com']) sync_history_url = '{}?individual={}'.format( SyncHistoryReport.get_url(self.domain), self.user.user_id ) _assert(False, '{}, sync history report: {}'.format(exception, sync_history_url)) raise exception
def validate(self): # runs validation checks, raises exceptions if anything is amiss check_version(self.version) if self.sync_log and self.state_hash: parsed_hash = CaseStateHash.parse(self.state_hash) if self.sync_log.get_state_hash() != parsed_hash: raise BadStateException(expected=self.sync_log.get_state_hash(), actual=parsed_hash, case_ids=self.sync_log.get_footprint_of_cases_on_phone())
def testEmpty(self): empty_hash = CaseStateHash(EMPTY_HASH) wrong_hash = CaseStateHash("thisisntright") self.assertEqual(empty_hash, self.device.last_sync.log.get_state_hash()) response = self.device.get_restore_config().get_response() self.assertEqual(200, response.status_code) config = self.device.get_restore_config(state_hash=str(wrong_hash)) try: config.get_payload() except BadStateException as e: self.assertEqual(empty_hash, e.server_hash) self.assertEqual(wrong_hash, e.phone_hash) self.assertEqual(0, len(e.case_ids)) else: self.fail("Call to generate a payload with a bad hash should fail!") self.assertEqual(412, config.get_response().status_code)
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if not isinstance(self.last_sync_log, self.sync_log_class): raise IncompatibleSyncLogType('Unable to convert from {} to {}'.format( type(self.last_sync_log), self.sync_log_class, )) if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: raise BadStateException(expected=computed_hash, actual=parsed_hash, case_ids=self.last_sync_log.get_footprint_of_cases_on_phone())
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() raise BadStateException(server_hash=computed_hash, phone_hash=parsed_hash, case_ids=self.last_sync_log. get_footprint_of_cases_on_phone())
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() raise BadStateException( server_hash=computed_hash, phone_hash=parsed_hash, case_ids=self.last_sync_log.get_footprint_of_cases_on_phone() )
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if (self._case_sync == CLEAN_OWNERS and self.last_sync_log.log_format == LOG_FORMAT_LIVEQUERY): raise RestoreException("clean_owners sync after livequery sync") if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() raise BadStateException( server_hash=computed_hash, phone_hash=parsed_hash, case_ids=self.last_sync_log.get_footprint_of_cases_on_phone() )
def validate_state(self): check_version(self.params.version) if self.last_sync_log: if self.params.state_hash: parsed_hash = CaseStateHash.parse(self.params.state_hash) computed_hash = self.last_sync_log.get_state_hash() if computed_hash != parsed_hash: # log state error on the sync log self.last_sync_log.had_state_error = True self.last_sync_log.error_date = datetime.utcnow() self.last_sync_log.error_hash = str(parsed_hash) self.last_sync_log.save() exception = BadStateException( expected=computed_hash, actual=parsed_hash, case_ids=self.last_sync_log.get_footprint_of_cases_on_phone() ) if self.last_sync_log.log_format == LOG_FORMAT_SIMPLIFIED: _assert = soft_assert(to=['czue' + '@' + 'dimagi.com']) _assert(False, str(exception)) raise exception
def get_state_hash(self): return CaseStateHash( Checksum(self.get_footprint_of_cases_on_phone()).hexdigest())
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)