Example #1
0
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,
    )
Example #2
0
    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))
Example #4
0
    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))
Example #5
0
    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))
Example #6
0
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
Example #7
0
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
Example #8
0
    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)
Example #9
0
    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)
Example #10
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 = 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)
Example #12
0
    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)
Example #13
0
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()
Example #14
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 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)
Example #15
0
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)
Example #16
0
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
Example #17
0
 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
Example #18
0
 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
Example #19
0
 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
Example #20
0
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 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)
Example #22
0
 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)
Example #23
0
    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)
Example #24
0
    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)
Example #25
0
    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
Example #26
0
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)
Example #27
0
def synclog_from_restore_payload(restore_payload):
    return SyncLog.get(synclog_id_from_restore_payload(restore_payload))
Example #28
0
    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)
Example #29
0
 def get_sync_token(self):
     if self.last_sync_token:
         return SyncLog.get(self.last_sync_token)
     return None
Example #30
0
def synclog_from_restore_payload(restore_payload):
    return SyncLog.get(synclog_id_from_restore_payload(restore_payload))
Example #31
0
        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: