Example #1
0
    def last_sync_log(self):
        if self.params.sync_log_id:
            try:
                sync_log = get_properly_wrapped_sync_log(
                    self.params.sync_log_id)
            except ResourceNotFound:
                # if we are in loose mode, return an HTTP 412 so that the phone will
                # just force a fresh sync
                raise MissingSyncLog('No sync log with ID {} found'.format(
                    self.params.sync_log_id))
            if sync_log.doc_type != 'SyncLog':
                raise InvalidSyncLogException(
                    'Bad sync log doc type for {}'.format(
                        self.params.sync_log_id))
            elif sync_log.user_id != self.user.user_id:
                raise SyncLogUserMismatch(
                    'Sync log {} does not match user id {} (was {})'.format(
                        self.params.sync_log_id, self.user.user_id,
                        sync_log.user_id))

            # convert to the right type if necessary
            if not isinstance(sync_log, self.sync_log_class):
                # this call can fail with an IncompatibleSyncLogType error
                sync_log = self.sync_log_class.from_other_format(sync_log)
            return sync_log
        else:
            return None
Example #2
0
    def last_sync_log(self):
        if self._last_sync_log is Ellipsis:
            if self.params.sync_log_id:
                # if we are in loose mode, return an HTTP 412 so that the phone will
                # just force a fresh sync
                # This raises MissingSyncLog exception if synclog not found
                sync_log = get_properly_wrapped_sync_log(
                    self.params.sync_log_id)
                if sync_log.doc_type not in ('SyncLog', 'SimplifiedSyncLog'):
                    raise InvalidSyncLogException(
                        'Bad sync log doc type for {}'.format(
                            self.params.sync_log_id))
                elif sync_log.user_id != self.restore_user.user_id:
                    raise SyncLogUserMismatch(
                        'Sync log {} does not match user id {} (was {})'.
                        format(self.params.sync_log_id,
                               self.restore_user.user_id, sync_log.user_id))

                # convert to the right type if necessary
                if not isinstance(sync_log, SimplifiedSyncLog):
                    # this call can fail with an IncompatibleSyncLogType error
                    sync_log = SimplifiedSyncLog.from_other_format(sync_log)
                self._last_sync_log = sync_log
            else:
                self._last_sync_log = None
        return self._last_sync_log
Example #3
0
 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)
Example #4
0
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 as 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 == 'XFormInstance':
                        from casexml.apps.case.xform import process_cases_with_casedb
                        process_cases_with_casedb(
                            [form],
                            case_db,
                            CaseProcessingConfig(
                                strict_asserts=True,
                                case_id_blacklist=case_id_blacklist
                            )
                        )
            updated_log = get_properly_wrapped_sync_log(sync_log._id)

            update_sync_log_with_checks(updated_log, xform, cases, case_db,
                                        case_id_blacklist=case_id_blacklist)
Example #5
0
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 == 'XFormInstance':
                        from casexml.apps.case.xform import process_cases_with_casedb
                        process_cases_with_casedb(
                            [form],
                            case_db,
                            CaseProcessingConfig(
                                strict_asserts=True,
                                case_id_blacklist=case_id_blacklist
                            )
                        )
            updated_log = get_properly_wrapped_sync_log(sync_log._id)

            update_sync_log_with_checks(updated_log, xform, cases, case_db,
                                        case_id_blacklist=case_id_blacklist)
Example #6
0
 def get_sync_token(self):
     from casexml.apps.phone.models import get_properly_wrapped_sync_log
     if self.last_sync_token:
         try:
             return get_properly_wrapped_sync_log(self.last_sync_token)
         except ResourceNotFound:
             pass
     return None
Example #7
0
 def get_sync_token(self):
     from casexml.apps.phone.models import get_properly_wrapped_sync_log
     if self.last_sync_token:
         try:
             return get_properly_wrapped_sync_log(self.last_sync_token)
         except MissingSyncLog:
             pass
     return None
Example #8
0
 def phone_holds_all_cases(request):
     try:
         synclog = get_properly_wrapped_sync_log(request.last_sync_token)
         missing_case_ids_on_phone = set(
             case_ids) - synclog.case_ids_on_phone
         return not missing_case_ids_on_phone
     except MissingSyncLog:
         return False
Example #9
0
 def get_sync_token(self):
     from casexml.apps.phone.models import get_properly_wrapped_sync_log
     if self.last_sync_token:
         try:
             return get_properly_wrapped_sync_log(self.last_sync_token)
         except MissingSyncLog:
             pass
     return None
 def test(self):
     if skip:
         self.skipTest(skip)
     self.build_case_structures(test_name)
     desired_cases = self._get_test(test_name).get('outcome', [])
     undesired_cases = [case for case in self.ALL_CASES if case not in desired_cases]
     sync_log = get_properly_wrapped_sync_log(self.sync_log._id)
     self.assertEqual(sync_log.case_ids_on_phone, set(desired_cases))
     assert_user_has_cases(self, self.user, desired_cases)
     assert_user_doesnt_have_cases(self, self.user, undesired_cases)
Example #11
0
    def test_previous_log_purged(self):
        initial_synclog_id = synclog_id_from_restore_payload(
            generate_restore_payload(self.project,
                                     self.restore_user,
                                     items=True))

        # form submission success when there is no previous sync log
        form_xml = get_simple_form_xml(uuid.uuid4().hex)
        submit_form_locally(form_xml,
                            self.domain,
                            last_sync_token=initial_synclog_id)

        # second sync
        synclog_id = synclog_id_from_restore_payload(
            generate_restore_payload(self.project,
                                     self.restore_user,
                                     restore_id=initial_synclog_id))

        synclog = get_properly_wrapped_sync_log(synclog_id)
        self.assertEqual(synclog.previous_log_id, initial_synclog_id)
        self.assertFalse(synclog.previous_log_removed)

        # form submission after second sync should remove first synclog
        form_xml = get_simple_form_xml(uuid.uuid4().hex)
        submit_form_locally(form_xml, self.domain, last_sync_token=synclog_id)

        synclog = get_properly_wrapped_sync_log(synclog_id)
        self.assertEqual(synclog.previous_log_id, initial_synclog_id)
        self.assertTrue(synclog.previous_log_removed)

        with self.assertRaises(ResourceNotFound):
            get_properly_wrapped_sync_log(initial_synclog_id)

        # form submissions after purge don't fail
        form_xml = get_simple_form_xml(uuid.uuid4().hex)
        submit_form_locally(form_xml, self.domain, last_sync_token=synclog_id)

        # restores after purge don't fail
        response = generate_restore_response(self.project,
                                             self.restore_user,
                                             restore_id=synclog_id)
        self.assertEqual(response.status_code, 200)
Example #12
0
 def get_sync_token(self):
     from casexml.apps.phone.models import get_properly_wrapped_sync_log
     if self.last_sync_token:
         try:
             return get_properly_wrapped_sync_log(self.last_sync_token)
         except ResourceNotFound:
             logging.exception('No sync token with ID {} found. Form is {} in domain {}'.format(
                 self.last_sync_token, self.form_id, self.domain,
             ))
             raise
     return None
Example #13
0
 def get_sync_token(self):
     from casexml.apps.phone.models import get_properly_wrapped_sync_log
     if self.last_sync_token:
         try:
             return get_properly_wrapped_sync_log(self.last_sync_token)
         except ResourceNotFound:
             logging.exception('No sync token with ID {} found. Form is {} in domain {}'.format(
                 self.last_sync_token, self.form_id, self.domain,
             ))
             raise
     return None
Example #14
0
    def get_log(self):
        """Get the latest sync log from the database

        Unlike the `log` property, this method does not cache its
        result. A sync log is updated when new cases are processed as
        part of a form submission referencing the sync log. Therefore
        a sync log returned by this method and the one returned by the
        `log` property may reference different cases. See
        `casexml.apps.case.xform.process_cases_with_casedb` and
        `casexml.apps.case.util.update_sync_log_with_checks`.
        """
        return get_properly_wrapped_sync_log(self.restore_id)
Example #15
0
    def get_log(self):
        """Get the latest sync log from the database

        Unlike the `log` property, this method does not cache its
        result. A sync log is updated when new cases are processed as
        part of a form submission referencing the sync log. Therefore
        a sync log returned by this method and the one returned by the
        `log` property may reference different cases. See
        `casexml.apps.case.xform.process_cases_with_casedb` and
        `casexml.apps.case.util.update_sync_log_with_checks`.
        """
        return get_properly_wrapped_sync_log(self.restore_id)
Example #16
0
    def test(self):
        if skip:
            self.skipTest(skip)
        self.build_case_structures(test_name)
        desired_cases = self._get_test(test_name).get('outcome', [])
        undesired_cases = [
            case for case in self.ALL_CASES if case not in desired_cases
        ]
        sync_log = get_properly_wrapped_sync_log(self.sync_log._id)
        self.assertEqual(sync_log.case_ids_on_phone, set(desired_cases))
        assert_user_has_cases(self, self.user, desired_cases)
        assert_user_doesnt_have_cases(self, self.user, undesired_cases)

    test.__name__ = get_test_name(test_name)
    return test
Example #17
0
def copy_payload_and_synclog_and_get_new_file(filelike_payload):
    """
    Given a restore payload, extracts the sync log id and sync log from the payload,
    makes a copy of the sync log, and then returns a new FileReference with the same contents
    except using the new sync log ID.
    """
    synclog_id, end_position = extract_synclog_id_from_filelike_payload(filelike_payload)
    old_sync_log = get_properly_wrapped_sync_log(synclog_id)
    new_sync_log_doc = old_sync_log.to_json()
    new_sync_log_id = uuid.uuid4().hex
    new_sync_log_doc['_id'] = new_sync_log_id
    del new_sync_log_doc['_rev']
    old_sync_log.get_db().save_doc(new_sync_log_doc)
    return replace_sync_log_id_in_filelike_payload(
        filelike_payload, old_sync_log._id, new_sync_log_id, end_position
    )
Example #18
0
 def last_sync_log(self):
     if self._last_sync_log is Ellipsis:
         if self.params.sync_log_id:
             # if we are in loose mode, return an HTTP 412 so that the phone will
             # just force a fresh sync
             # This raises MissingSyncLog exception if synclog not found
             sync_log = get_properly_wrapped_sync_log(self.params.sync_log_id)
             if sync_log.doc_type != 'SimplifiedSyncLog':
                 raise InvalidSyncLogException('Bad sync log doc type for {}'.format(self.params.sync_log_id))
             elif sync_log.user_id != self.restore_user.user_id:
                 raise SyncLogUserMismatch('Sync log {} does not match user id {} (was {})'.format(
                     self.params.sync_log_id, self.restore_user.user_id, sync_log.user_id
                 ))
             self._last_sync_log = sync_log
         else:
             self._last_sync_log = None
     return self._last_sync_log
    def test_copy_payload(self):
        sync_log = SimplifiedSyncLog(case_ids_on_phone=set(["case-1", "case-2"]))
        sync_log.save()
        payload = dummy_restore_xml(sync_log._id).strip()
        fd, path = tempfile.mkstemp()
        with os.fdopen(fd, "wb") as f:
            f.write(payload)

        with open(path, "r") as f:
            updated_fileref = copy_payload_and_synclog_and_get_new_file(f)

        updated_payload = updated_fileref.file.read()
        updated_id = synclog_id_from_restore_payload(updated_payload)
        self.assertNotEqual(sync_log._id, updated_id)
        self.assertTrue(_restore_id_block(updated_id) in updated_payload)
        self.assertFalse(sync_log._id in updated_payload)
        updated_log = get_properly_wrapped_sync_log(updated_id)
        self.assertEqual(updated_log.case_ids_on_phone, sync_log.case_ids_on_phone)
Example #20
0
    def test_get_and_save_legacy_synclog_with_attachm(self):
        legacy_sync_log = self.legacy_sync_logs[0]
        attachment_name = 'test_attach'
        get_db(None).put_attachment(legacy_sync_log._doc, 'test', attachment_name, 'text/plain')

        sync_log = get_properly_wrapped_sync_log(legacy_sync_log._id)
        self.assertIn(attachment_name, sync_log._attachments)

        # this used to fail for docs with attachments
        sync_log.save()

        # cleanup
        def del_attachment():
            get_db(None).delete_attachment(legacy_sync_log._doc, attachment_name)
            del legacy_sync_log._attachments

        self.addCleanup(del_attachment)
        self.addCleanup(sync_log.delete)
    def test_copy_payload(self):
        sync_log = SimplifiedSyncLog(case_ids_on_phone=set(['case-1', 'case-2']))
        sync_log.save()
        payload = dummy_restore_xml(sync_log._id).strip()
        fd, path = tempfile.mkstemp()
        with os.fdopen(fd, 'wb') as f:
            f.write(payload)

        with open(path, 'r') as f:
            updated_fileref = copy_payload_and_synclog_and_get_new_file(f)

        updated_payload = updated_fileref.file.read()
        updated_id = synclog_id_from_restore_payload(updated_payload)
        self.assertNotEqual(sync_log._id, updated_id)
        self.assertTrue(_restore_id_block(updated_id) in updated_payload)
        self.assertFalse(sync_log._id in updated_payload)
        updated_log = get_properly_wrapped_sync_log(updated_id)
        self.assertEqual(updated_log.case_ids_on_phone, sync_log.case_ids_on_phone)
Example #22
0
def copy_payload_and_synclog_and_get_new_file(filelike_payload):
    """
    Given a restore payload, extracts the sync log id and sync log from the payload,
    makes a copy of the sync log, and then returns a new FileReference with the same contents
    except using the new sync log ID.
    """
    synclog_id, end_position = extract_synclog_id_from_filelike_payload(
        filelike_payload)
    old_sync_log = get_properly_wrapped_sync_log(synclog_id)
    new_sync_log_doc = old_sync_log.to_json()
    new_sync_log_id = uuid.uuid4().hex
    new_sync_log_doc['_id'] = new_sync_log_id
    del new_sync_log_doc['_rev']
    old_sync_log.get_db().save_doc(new_sync_log_doc)
    return replace_sync_log_id_in_filelike_payload(filelike_payload,
                                                   old_sync_log._id,
                                                   new_sync_log_id,
                                                   end_position)
Example #23
0
    def test_get_and_save_legacy_synclog_with_attachm(self):
        legacy_sync_log = self.legacy_sync_logs[0]
        attachment_name = 'test_attach'
        get_db(None).put_attachment(legacy_sync_log._doc, 'test', attachment_name, 'text/plain')

        sync_log = get_properly_wrapped_sync_log(legacy_sync_log._id)
        self.assertIn(attachment_name, sync_log._attachments)

        # this used to fail for docs with attachments
        sync_log.save()

        # cleanup
        def del_attachment():
            get_db(None).delete_attachment(legacy_sync_log._doc, attachment_name)
            del legacy_sync_log._attachments

        self.addCleanup(del_attachment)
        self.addCleanup(sync_log.delete)
Example #24
0
    def last_sync_log(self):
        if self.params.sync_log_id:
            try:
                sync_log = get_properly_wrapped_sync_log(self.params.sync_log_id)
            except ResourceNotFound:
                # if we are in loose mode, return an HTTP 412 so that the phone will
                # just force a fresh sync
                raise MissingSyncLog('No sync log with ID {} found'.format(self.params.sync_log_id))
            if sync_log.doc_type != 'SyncLog':
                raise InvalidSyncLogException('Bad sync log doc type for {}'.format(self.params.sync_log_id))
            elif sync_log.user_id != self.user.user_id:
                raise SyncLogUserMismatch('Sync log {} does not match user id {} (was {})'.format(
                    self.params.sync_log_id, self.user.user_id, sync_log.user_id
                ))

            return sync_log
        else:
            return None
Example #25
0
    def last_sync_log(self):
        if self.params.sync_log_id:
            try:
                sync_log = get_properly_wrapped_sync_log(self.params.sync_log_id)
            except ResourceNotFound:
                # if we are in loose mode, return an HTTP 412 so that the phone will
                # just force a fresh sync
                raise MissingSyncLog('No sync log with ID {} found'.format(self.params.sync_log_id))
            if sync_log.doc_type != 'SyncLog':
                raise InvalidSyncLogException('Bad sync log doc type for {}'.format(self.params.sync_log_id))
            elif sync_log.user_id != self.user.user_id:
                raise SyncLogUserMismatch('Sync log {} does not match user id {} (was {})'.format(
                    self.params.sync_log_id, self.user.user_id, sync_log.user_id
                ))

            # convert to the right type if necessary
            if not isinstance(sync_log, self.sync_log_class):
                # this call can fail with an IncompatibleSyncLogType error
                sync_log = self.sync_log_class.from_other_format(sync_log)
            return sync_log
        else:
            return None
Example #26
0
    def last_sync_log(self):
        if self._last_sync_log is Ellipsis:
            if self.params.sync_log_id:
                # if we are in loose mode, return an HTTP 412 so that the phone will
                # just force a fresh sync
                # This raises MissingSyncLog exception if synclog not found
                sync_log = get_properly_wrapped_sync_log(self.params.sync_log_id)
                if sync_log.doc_type not in ('SyncLog', 'SimplifiedSyncLog'):
                    raise InvalidSyncLogException('Bad sync log doc type for {}'.format(self.params.sync_log_id))
                elif sync_log.user_id != self.restore_user.user_id:
                    raise SyncLogUserMismatch('Sync log {} does not match user id {} (was {})'.format(
                        self.params.sync_log_id, self.restore_user.user_id, sync_log.user_id
                    ))

                # convert to the right type if necessary
                if not isinstance(sync_log, SimplifiedSyncLog):
                    # this call can fail with an IncompatibleSyncLogType error
                    sync_log = SimplifiedSyncLog.from_other_format(sync_log)
                self._last_sync_log = sync_log
            else:
                self._last_sync_log = None
        return self._last_sync_log
Example #27
0
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 as e:
        soft_assert('@'.join(['skelly', 'dimagi.com']))(
            False,
            'SyncLogAssertionError raised while updating phone lists',
            {
                'form_id': xform.form_id,
                'cases': [case.case_id for case in cases]
            }
        )
        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 == 'XFormInstance':
                        from casexml.apps.case.xform import process_cases_with_casedb
                        process_cases_with_casedb(
                            [form],
                            case_db,
                            CaseProcessingConfig(
                                strict_asserts=True,
                                case_id_blacklist=case_id_blacklist
                            )
                        )
            updated_log = get_properly_wrapped_sync_log(sync_log._id)

            update_sync_log_with_checks(updated_log, xform, cases, case_db,
                                        case_id_blacklist=case_id_blacklist)
Example #28
0
def deprecated_synclog_from_restore_payload(restore_payload):
    """DEPRECATED use <MockDevice>.sync().get_log()"""
    return get_properly_wrapped_sync_log(
        deprecated_synclog_id_from_restore_payload(restore_payload))
Example #29
0
def synclog_from_restore_payload(restore_payload):
    return get_properly_wrapped_sync_log(synclog_id_from_restore_payload(restore_payload))
Example #30
0
def deprecated_synclog_from_restore_payload(restore_payload):
    """DEPRECATED use <MockDevice>.sync().get_log()"""
    return get_properly_wrapped_sync_log(
        deprecated_synclog_id_from_restore_payload(restore_payload))
Example #31
0
                'cases': [case.case_id for case in cases]
            })
        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 == 'XFormInstance':
                        from casexml.apps.case.xform import process_cases_with_casedb
                        process_cases_with_casedb(
                            [form], case_db,
                            CaseProcessingConfig(
                                strict_asserts=True,
                                case_id_blacklist=case_id_blacklist))
            updated_log = get_properly_wrapped_sync_log(sync_log._id)

            update_sync_log_with_checks(updated_log,
                                        xform,
                                        cases,
                                        case_db,
                                        case_id_blacklist=case_id_blacklist)


def get_indexed_cases(domain, case_ids):
    """
    Given a base list of cases, gets all wrapped cases that they reference
    (parent cases).
    """
    from casexml.apps.case.models import CommCareCase
    return [