Ejemplo n.º 1
0
 def _check_initial_state(case):
     self.assertTrue(case.closed)
     self.assertEqual(closed_by, case.closed_by)
     self.assertEqual(closed_on, case.closed_on)
     self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
     self.assertEqual(case.get_case_property('p2'), 'p2-2')  # updated in second post
     self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
     self.assertEqual(case.get_case_property('p4'), 'p4-3')  # updated in third post
     self.assertEqual(case.get_case_property('p5'), 'p5-3')  # new in third post
     if should_use_sql_backend(REBUILD_TEST_DOMAIN):
         # SQL stores one transaction per form
         self.assertEqual(3, len(primary_actions(case)))  # create + update + close
     else:
         self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close
Ejemplo n.º 2
0
def rebuild_case(case_id):
    """
    Given a case ID, rebuild the entire case state based on all existing forms
    referencing it. Useful when things go wrong or when you need to manually
    rebuild a case after archiving / deleting it
    """

    try:
        case = CommCareCase.get(case_id)
        found = True
    except ResourceNotFound:
        case = CommCareCase()
        case._id = case_id
        found = False

    # clear actions, xform_ids, close state, and all dynamic properties
    dynamic_properties = set([k for action in case.actions for k in action.updated_unknown_properties.keys()])
    for k in dynamic_properties:
        try:
            delattr(case, k)
        except KeyError:
            pass

    # already deleted means it was explicitly set to "deleted",
    # as opposed to getting set to that because it has no actions
    already_deleted = case.doc_type == 'CommCareCase-Deleted' and primary_actions(case)
    if not already_deleted:
        case.doc_type = 'CommCareCase'
    case.xform_ids = []
    case.actions = []
    case.closed = False
    case.closed_on = None
    case.closed_by = ''

    form_ids = get_case_xform_ids(case_id)
    forms = [fetch_and_wrap_form(id) for id in form_ids]
    filtered_forms = [f for f in forms if f.doc_type == "XFormInstance"]
    sorted_forms = sorted(filtered_forms, key=lambda f: f.received_on)
    for form in sorted_forms:
        if not found and case.domain is None:
            case.domain = form.domain
        assert form.domain == case.domain

        case_updates = get_case_updates(form)
        filtered_updates = [u for u in case_updates if u.id == case_id]
        for u in filtered_updates:
            case.update_from_case_update(u, form)

    case.xform_ids = [f._id for f in sorted_forms]
    if not case.xform_ids:
        if not found:
            return None
        # there were no more forms. 'delete' the case
        case.doc_type = 'CommCareCase-Deleted'

    # add a "rebuild" action
    case.actions.append(_rebuild_action())
    case.save()
    return case
Ejemplo n.º 3
0
 def _check_initial_state(case):
     self.assertTrue(case.closed)
     self.assertEqual(closed_by, case.closed_by)
     self.assertEqual(closed_on, case.closed_on)
     self.assertEqual(case.p1, 'p1-1')  # original
     self.assertEqual(case.p2, 'p2-2')  # updated in second post
     self.assertEqual(case.p3, 'p3-2')  # new in second post
     self.assertEqual(case.p4, 'p4-3')  # updated in third post
     self.assertEqual(case.p5, 'p5-3')  # new in third post
     self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close
Ejemplo n.º 4
0
    def testReconcileActions(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, form_extras={'received_on': now})
        _post_util(case_id=case_id, p1='p1-1', p2='p2-1', form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p2='p2-2', p3='p3-2', form_extras={'received_on': now + timedelta(seconds=2)})
        case = CommCareCase.get(case_id)
        update_strategy = ActionsUpdateStrategy(case)

        original_actions = [deepcopy(a) for a in case.actions]
        original_form_ids = [id for id in case.xform_ids]
        self.assertEqual(3, len(original_actions))
        self.assertEqual(3, len(original_form_ids))
        self._assertListEqual(original_actions, case.actions)

        # test reordering
        case.actions = [case.actions[2], case.actions[1], case.actions[0]]
        self._assertListNotEqual(original_actions, case.actions)
        update_strategy.reconcile_actions()
        self._assertListEqual(original_actions, case.actions)

        # test duplication
        case.actions = case.actions * 3
        self.assertEqual(9, len(case.actions))
        self._assertListNotEqual(original_actions, case.actions)
        update_strategy.reconcile_actions()
        self._assertListEqual(original_actions, case.actions)

        # test duplication, even when dates are off
        case.actions = original_actions + [deepcopy(case.actions[2])]
        case.actions[-1].server_date = case.actions[-1].server_date + timedelta(seconds=1)
        self._assertListNotEqual(original_actions, case.actions)
        update_strategy.reconcile_actions()
        self._assertListEqual(original_actions, case.actions)

        # test duplication with different properties is actually
        # treated differently
        case.actions = original_actions + [deepcopy(case.actions[2])]
        case.actions[-1].updated_unknown_properties['new'] = 'mismatch'
        self.assertEqual(4, len(case.actions))
        self._assertListNotEqual(original_actions, case.actions)
        update_strategy.reconcile_actions()
        self._assertListNotEqual(original_actions, case.actions)

        # test clean slate rebuild
        case = rebuild_case_from_forms(case_id)
        self._assertListEqual(original_actions, primary_actions(case))
        self._assertListEqual(original_form_ids, case.xform_ids)
Ejemplo n.º 5
0
    def testRebuildCreateCase(self):
        case_id = _post_util(create=True)
        _post_util(case_id=case_id, p1='p1', p2='p2')

        # delete initial case
        case = CommCareCase.get(case_id)
        case.delete()

        try:
            CommCareCase.get(case_id)
            self.fail('get should fail on deleted cases')
        except ResourceNotFound:
            pass

        case = rebuild_case_from_forms(REBUILD_TEST_DOMAIN, case_id, RebuildWithReason(reason='test'))
        self.assertEqual(case.p1, 'p1')
        self.assertEqual(case.p2, 'p2')
        self.assertEqual(2, len(primary_actions(case)))  # create + update
Ejemplo n.º 6
0
    def test_couch_rebuild_deleted_case(self):
        # Note: Can't run this on SQL because if a case gets hard deleted then
        # there is no way to find out which forms created / updated it without
        # going through ALL the forms in the domain. ie. there is no SQL
        # equivalent to the "form_case_index/form_case_index" couch view

        case_id = _post_util(create=True)
        _post_util(case_id=case_id, p1='p1', p2='p2')

        # delete initial case
        delete_all_cases()

        with self.assertRaises(CaseNotFound):
            CaseAccessors(REBUILD_TEST_DOMAIN).get_case(case_id)

        case = rebuild_case_from_forms(REBUILD_TEST_DOMAIN, case_id, RebuildWithReason(reason='test'))
        self.assertEqual(case.p1, 'p1')
        self.assertEqual(case.p2, 'p2')
        self.assertEqual(3, len(primary_actions(case)))  # create + update
Ejemplo n.º 7
0
    def reset_case_state(self):
        """
        Clear known case properties, and all dynamic properties
        """
        dynamic_properties = set([
            k for action in self.case.actions
            for k in action.updated_unknown_properties.keys()
        ])
        for k in dynamic_properties:
            try:
                delattr(self.case, k)
            except KeyError:
                pass
            except AttributeError:
                # 'case_id' is not a valid property so don't worry about spamming
                # this error.
                if k != 'case_id':
                    logging.error(
                        "Cannot delete attribute '%(attribute)s' from case '%(case_id)s'" % {
                            'case_id': self.case.case_id,
                            'attribute': k,
                        }
                    )

        # Clear indices and attachments
        self.case.indices = []
        self.case.case_attachments = {}

        # already deleted means it was explicitly set to "deleted",
        # as opposed to getting set to that because it has no actions
        already_deleted = self.case.doc_type == 'CommCareCase-Deleted' and primary_actions(self.case)
        if not already_deleted:
            self.case.doc_type = 'CommCareCase'

        # hard-coded normal properties (from a create block)
        for prop, default_value in KNOWN_PROPERTIES.items():
            setattr(self.case, prop, default_value)

        self.case.closed = False
        self.case.modified_on = None
        self.case.closed_on = None
        self.case.closed_by = ''
        return self
Ejemplo n.º 8
0
    def testRebuildCreateCase(self):
        case_id = post_util(create=True)
        post_util(case_id=case_id, p1='p1', p2='p2')

        # delete initial case
        case = CommCareCase.get(case_id)
        case.delete()

        case = rebuild_case(case_id)
        self.assertEqual(case.p1, 'p1')
        self.assertEqual(case.p2, 'p2')
        self.assertEqual(2, len(primary_actions(case)))  # create + update

        case.delete()
        try:
            CommCareCase.get(case_id)
            self.fail('get should fail on deleted cases')
        except ResourceNotFound:
            pass

        case = CommCareCase.get_with_rebuild(case_id)
        self.assertEqual(case.p1, 'p1')
        self.assertEqual(case.p2, 'p2')
Ejemplo n.º 9
0
    def testFormArchiving(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1-1', p2='p2-1',
                            form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True,
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case = CommCareCase.get(case_id)
        closed_by = case.closed_by
        closed_on = case.closed_on
        self.assertNotEqual('', closed_by)
        self.assertNotEqual(None, closed_on)

        def _check_initial_state(case):
            self.assertTrue(case.closed)
            self.assertEqual(closed_by, case.closed_by)
            self.assertEqual(closed_on, case.closed_on)
            self.assertEqual(case.p1, 'p1-1')  # original
            self.assertEqual(case.p2, 'p2-2')  # updated in second post
            self.assertEqual(case.p3, 'p3-2')  # new in second post
            self.assertEqual(case.p4, 'p4-3')  # updated in third post
            self.assertEqual(case.p5, 'p5-3')  # new in third post
            self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close

        _check_initial_state(case)

        # verify xform/action states
        [create, u1, u2, u3, close] = case.actions
        [f1, f2, f3] = case.xform_ids
        self.assertEqual(f1, create.xform_id)
        self.assertEqual(f1, u1.xform_id)
        self.assertEqual(f2, u2.xform_id)
        self.assertEqual(f3, u3.xform_id)

        # todo: should this be the behavior for archiving the create form?
        f1_doc = XFormInstance.get(f1)
        f1_doc.archive()
        case = CommCareCase.get(case_id)

        self.assertEqual(3, len(primary_actions(case)))
        [u2, u3] = case.xform_ids
        self.assertEqual(f2, u2)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed)  # no change
        self.assertFalse('p1' in case._doc)  # should disappear entirely
        self.assertEqual(case.p2, 'p2-2')  # no change
        self.assertEqual(case.p3, 'p3-2')  # no change
        self.assertEqual(case.p4, 'p4-3')  # no change
        self.assertEqual(case.p5, 'p5-3')  # no change

        def _reset(form_id):
            form_doc = XFormInstance.get(form_id)
            form_doc.unarchive()
            case = CommCareCase.get(case_id)
            _check_initial_state(case)

        _reset(f1)

        f2_doc = XFormInstance.get(f2)
        f2_doc.archive()
        case = CommCareCase.get(case_id)

        self.assertEqual(4, len(primary_actions(case)))
        [u1, u3] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed) # no change
        self.assertEqual(case.p1, 'p1-1') # original
        self.assertEqual(case.p2, 'p2-1') # loses second form update
        # self.assertFalse('p3' in case._doc) # todo: should disappear entirely
        self.assertEqual(case.p4, 'p4-3') # no change
        self.assertEqual(case.p5, 'p5-3') # no change

        _reset(f2)

        f3_doc = XFormInstance.get(f3)
        f3_doc.archive()
        case = CommCareCase.get(case_id)

        self.assertEqual(3, len(primary_actions(case)))
        [u1, u2] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f2, u2)

        self.assertFalse(case.closed)  # reopened!
        self.assertEqual('', case.closed_by)
        self.assertEqual(None, case.closed_on)
        self.assertEqual(case.p1, 'p1-1')  # original
        self.assertEqual(case.p2, 'p2-2')  # original
        self.assertEqual(case.p3, 'p3-2')  # new in second post
        self.assertEqual(case.p4, 'p4-2')  # loses third form update
        # self.assertFalse('p5' in case._doc) # todo: should disappear entirely
        _reset(f3)
Ejemplo n.º 10
0
    def test_form_archiving(self):
        now = datetime.utcnow()
        # make sure we timestamp everything so they have the right order
        case_id = _post_util(create=True, p1='p1-1', p2='p2-1',
                            form_extras={'received_on': now})
        _post_util(case_id=case_id, p2='p2-2', p3='p3-2', p4='p4-2',
                  form_extras={'received_on': now + timedelta(seconds=1)})
        _post_util(case_id=case_id, p4='p4-3', p5='p5-3', close=True,
                  form_extras={'received_on': now + timedelta(seconds=2)})

        case_accessors = CaseAccessors(REBUILD_TEST_DOMAIN)
        case = case_accessors.get_case(case_id)
        closed_by = case.closed_by
        closed_on = case.closed_on
        self.assertNotEqual('', closed_by)
        self.assertNotEqual(None, closed_on)

        def _check_initial_state(case):
            self.assertTrue(case.closed)
            self.assertEqual(closed_by, case.closed_by)
            self.assertEqual(closed_on, case.closed_on)
            self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
            self.assertEqual(case.get_case_property('p2'), 'p2-2')  # updated in second post
            self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
            self.assertEqual(case.get_case_property('p4'), 'p4-3')  # updated in third post
            self.assertEqual(case.get_case_property('p5'), 'p5-3')  # new in third post
            if should_use_sql_backend(REBUILD_TEST_DOMAIN):
                # SQL stores one transaction per form
                self.assertEqual(3, len(primary_actions(case)))  # create + update + close
            else:
                self.assertEqual(5, len(primary_actions(case)))  # create + 3 updates + close

        _check_initial_state(case)

        # verify xform/action states
        [f1, f2, f3] = case.xform_ids
        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            [create, update, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f2, update.form_id)
            self.assertEqual(f3, close.form_id)
        else:
            [create, u1, u2, u3, close] = case.actions
            self.assertEqual(f1, create.form_id)
            self.assertEqual(f1, u1.form_id)
            self.assertEqual(f2, u2.form_id)
            self.assertEqual(f3, u3.form_id)

        # todo: should this be the behavior for archiving the create form?
        form_acessors = FormAccessors(REBUILD_TEST_DOMAIN)
        f1_doc = form_acessors.get_form(f1)
        with capture_kafka_changes_context(topics.CASE_SQL) as change_context:
            f1_doc.archive()

        if should_use_sql_backend(case.domain):
            self.assertEqual([case.case_id], [change.id for change in change_context.changes])

        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(3, len(primary_actions(case)))

        [u2, u3] = case.xform_ids
        self.assertEqual(f2, u2)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed)  # no change
        self.assertFalse('p1' in case.dynamic_case_properties())  # should disappear entirely
        self.assertEqual(case.get_case_property('p2'), 'p2-2')  # no change
        self.assertEqual(case.get_case_property('p3'), 'p3-2')  # no change
        self.assertEqual(case.get_case_property('p4'), 'p4-3')  # no change
        self.assertEqual(case.get_case_property('p5'), 'p5-3')  # no change

        def _reset(form_id):
            form_doc = form_acessors.get_form(form_id)
            form_doc.unarchive()
            case = case_accessors.get_case(case_id)
            _check_initial_state(case)

        _reset(f1)

        f2_doc = form_acessors.get_form(f2)
        f2_doc.archive()
        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(4, len(primary_actions(case)))

        [u1, u3] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f3, u3)

        self.assertTrue(case.closed)  # no change
        self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
        self.assertEqual(case.get_case_property('p2'), 'p2-1')  # loses second form update
        self.assertFalse('p3' in case.dynamic_case_properties())  # should disappear entirely
        self.assertEqual(case.get_case_property('p4'), 'p4-3')  # no change
        self.assertEqual(case.get_case_property('p5'), 'p5-3')  # no change

        _reset(f2)

        f3_doc = form_acessors.get_form(f3)
        f3_doc.archive()
        case = case_accessors.get_case(case_id)

        if should_use_sql_backend(REBUILD_TEST_DOMAIN):
            self.assertEqual(2, len(primary_actions(case)))
        else:
            self.assertEqual(3, len(primary_actions(case)))

        [u1, u2] = case.xform_ids
        self.assertEqual(f1, u1)
        self.assertEqual(f2, u2)

        self.assertFalse(case.closed)  # reopened!
        self.assertEqual('', case.closed_by)
        self.assertEqual(None, case.closed_on)
        self.assertEqual(case.get_case_property('p1'), 'p1-1')  # original
        self.assertEqual(case.get_case_property('p2'), 'p2-2')  # original
        self.assertEqual(case.get_case_property('p3'), 'p3-2')  # new in second post
        self.assertEqual(case.get_case_property('p4'), 'p4-2')  # loses third form update
        self.assertFalse('p5' in case.dynamic_case_properties())  # should disappear entirely
        _reset(f3)