예제 #1
0
    def test_soft_delete(self):
        meta = TestFormMetadata(domain=DOMAIN)
        get_simple_wrapped_form('f1', metadata=meta)
        f2 = get_simple_wrapped_form('f2', metadata=meta)
        f2.archive()
        get_simple_wrapped_form('f3', metadata=meta)

        accessors = FormAccessors(DOMAIN)

        # delete
        num = accessors.soft_delete_forms(['f1', 'f2'], deletion_id='123')
        self.assertEqual(num, 2)

        for form_id in ['f1', 'f2']:
            form = accessors.get_form(form_id)
            self.assertTrue(form.is_deleted)
            self.assertEqual(form.deletion_id, '123')

        form = accessors.get_form('f3')
        self.assertFalse(form.is_deleted)

        for form_id in ['f1', 'f2']:
            form = FormAccessors(DOMAIN).get_form(form_id)
            if isinstance(form, PartitionedModel):
                form.delete(using=get_db_alias_for_partitioned_doc(form.form_id))
            else:
                form.delete()
예제 #2
0
class TestHardDelete(TestCase):

    def setUp(self):
        self.casedb = CaseAccessors(TEST_DOMAIN_NAME)
        self.formdb = FormAccessors(TEST_DOMAIN_NAME)

    @run_with_all_backends
    def test_simple_delete(self):
        factory = CaseFactory()
        case = factory.create_case()
        [case] = factory.create_or_update_case(
            CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}})
        )
        self.assertIsNotNone(self.casedb.get_case(case.case_id))
        self.assertEqual(2, len(case.xform_ids))
        for form_id in case.xform_ids:
            self.assertIsNotNone(self.formdb.get_form(form_id))
        safe_hard_delete(case)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case.case_id)

        for form_id in case.xform_ids:
            with self.assertRaises(XFormNotFound):
                self.formdb.get_form(form_id)

    @run_with_all_backends
    def test_delete_with_related(self):
        factory = CaseFactory()
        parent = factory.create_case()
        [child] = factory.create_or_update_case(
            CaseStructure(attrs={'create': True}, walk_related=False, indices=[
                CaseIndex(CaseStructure(case_id=parent.case_id))
            ]),
        )
        # deleting the parent should not be allowed because the child still references it
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(parent)

        # deleting the child is ok
        safe_hard_delete(child)
        self.assertIsNotNone(self.casedb.get_case(parent.case_id))
        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(child.case_id)

    @run_with_all_backends
    def test_delete_sharing_form(self):
        factory = CaseFactory()
        c1, c2 = factory.create_or_update_cases([
            CaseStructure(attrs={'create': True}),
            CaseStructure(attrs={'create': True}),
        ])
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c1)

        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c2)

        self.assertIsNotNone(self.casedb.get_case(c1.case_id))
        self.assertIsNotNone(self.casedb.get_case(c2.case_id))
예제 #3
0
    def test_submit_deprecated_form_with_attachments(self):
        def list_attachments(form):
            return sorted(
                (att.name, att.open().read())
                for att in form.get_attachments()
                if att.name != "form.xml"
            )

        self._submit('simple_form.xml', attachments={
            "image": BytesIO(b"fake image"),
            "file": BytesIO(b"text file"),
        })
        response = self._submit(
            'simple_form_edited.xml',
            attachments={"image": BytesIO(b"other fake image")},
            url=reverse("receiver_secure_post", args=[self.domain]),
        )
        acc = FormAccessors(self.domain.name)
        new_form = acc.get_form(response['X-CommCareHQ-FormID'])
        old_form = acc.get_form(new_form.deprecated_form_id)
        self.assertIn(b"<bop>bang</bop>", old_form.get_xml())
        self.assertIn(b"<bop>bong</bop>", new_form.get_xml())
        self.assertEqual(list_attachments(old_form),
            [("file", b"text file"), ("image", b"fake image")])
        self.assertEqual(list_attachments(new_form),
            [("file", b"text file"), ("image", b"other fake image")])
예제 #4
0
    def test_submit_deprecated_form_with_attachments(self):
        def list_attachments(form):
            return sorted((att.name, att.open().read())
                          for att in form.get_attachments()
                          if att.name != "form.xml")

        self._submit('simple_form.xml',
                     attachments={
                         "image": BytesIO(b"fake image"),
                         "file": BytesIO(b"text file"),
                     })
        response = self._submit(
            'simple_form_edited.xml',
            attachments={"image": BytesIO(b"other fake image")},
            url=reverse("receiver_secure_post", args=[self.domain]),
        )
        acc = FormAccessors(self.domain.name)
        new_form = acc.get_form(response['X-CommCareHQ-FormID'])
        old_form = acc.get_form(new_form.deprecated_form_id)
        self.assertIn(b"<bop>bang</bop>", old_form.get_xml())
        self.assertIn(b"<bop>bong</bop>", new_form.get_xml())
        self.assertEqual(list_attachments(old_form),
                         [("file", b"text file"), ("image", b"fake image")])
        self.assertEqual(list_attachments(new_form),
                         [("file", b"text file"),
                          ("image", b"other fake image")])
예제 #5
0
    def test_soft_delete(self):
        meta = TestFormMetadata(domain=DOMAIN)
        get_simple_wrapped_form('f1', metadata=meta)
        f2 = get_simple_wrapped_form('f2', metadata=meta)
        f2.archive()
        get_simple_wrapped_form('f3', metadata=meta)

        accessors = FormAccessors(DOMAIN)

        # delete
        num = accessors.soft_delete_forms(['f1', 'f2'], deletion_id='123')
        self.assertEqual(num, 2)

        for form_id in ['f1', 'f2']:
            form = accessors.get_form(form_id)
            self.assertTrue(form.is_deleted)
            self.assertEqual(form.deletion_id, '123')

        form = accessors.get_form('f3')
        self.assertFalse(form.is_deleted)

        for form_id in ['f1', 'f2']:
            form = FormAccessors(DOMAIN).get_form(form_id)
            if isinstance(form, DisabledDbMixin):
                super(DisabledDbMixin, form).delete()
            else:
                form.delete()
예제 #6
0
class FormDocumentStore(DocumentStore):
    def __init__(self, domain, xmlns=None):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)
        self.xmlns = xmlns

    def get_document(self, doc_id):
        try:
            form = self.form_accessors.get_form(doc_id)
            return self._to_json(form)
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)

    @staticmethod
    def _to_json(form):
        if isinstance(form, XFormInstanceSQL):
            return form.to_json(include_attachments=True)
        else:
            return form.to_json()

    def iter_document_ids(self):
        return iter(self.form_accessors.iter_form_ids_by_xmlns(self.xmlns))

    def iter_documents(self, ids):
        for wrapped_form in self.form_accessors.iter_forms(ids):
            try:
                yield self._to_json(wrapped_form)
            except (DocumentNotFoundError, MissingFormXml):
                pass
예제 #7
0
    def test_reprocess_xform_error(self):
        case_id = uuid.uuid4().hex
        parent_case_id = uuid.uuid4().hex
        case = CaseBlock(
            create=True,
            case_id=case_id,
            user_id='user1',
            owner_id='user1',
            case_type='demo',
            case_name='child',
            index={'parent': ('parent_type', parent_case_id)}
        )

        post_case_blocks([case.as_xml()], domain=self.domain)

        form_accessors = FormAccessors(self.domain)
        error_forms = form_accessors.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))

        form = error_forms[0]
        reprocess_xform_error(form)
        error_forms = form_accessors.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))

        case = CaseBlock(
            create=True,
            case_id=parent_case_id,
            user_id='user1',
            owner_id='user1',
            case_type='parent_type',
            case_name='parent',
        )

        post_case_blocks([case.as_xml()], domain=self.domain)

        reprocess_xform_error(form_accessors.get_form(form.form_id))

        form = form_accessors.get_form(form.form_id)
        # self.assertTrue(form.initial_processing_complete)  Can't change this with SQL forms at the moment
        self.assertTrue(form.is_normal)
        self.assertIsNone(form.problem)

        case = CaseAccessors(self.domain).get_case(case_id)
        self.assertEqual(1, len(case.indices))
        self.assertEqual(case.indices[0].referenced_id, parent_case_id)
        self._validate_case(case)
예제 #8
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)

    def get_document(self, doc_id):
        return self.form_accessors.get_form(doc_id).to_json()
예제 #9
0
    def test_reprocess_xform_error(self):
        case_id = uuid.uuid4().hex
        parent_case_id = uuid.uuid4().hex
        case = CaseBlock(
            create=True,
            case_id=case_id,
            user_id='user1',
            owner_id='user1',
            case_type='demo',
            case_name='child',
            index={'parent': ('parent_type', parent_case_id)}
        )

        post_case_blocks([case.as_xml()], domain=self.domain)

        form_accessors = FormAccessors(self.domain)
        error_forms = form_accessors.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))

        form = error_forms[0]
        reprocess_xform_error(form)
        error_forms = form_accessors.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))

        case = CaseBlock(
            create=True,
            case_id=parent_case_id,
            user_id='user1',
            owner_id='user1',
            case_type='parent_type',
            case_name='parent',
        )

        post_case_blocks([case.as_xml()], domain=self.domain)

        reprocess_xform_error(form_accessors.get_form(form.form_id))

        form = form_accessors.get_form(form.form_id)
        # self.assertTrue(form.initial_processing_complete)  Can't change this with SQL forms at the moment
        self.assertTrue(form.is_normal)
        self.assertIsNone(form.problem)

        case = CaseAccessors(self.domain).get_case(case_id)
        self.assertEqual(1, len(case.indices))
        self.assertEqual(case.indices[0].referenced_id, parent_case_id)
        self._validate_case(case)
예제 #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])
예제 #11
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)

    def get_document(self, doc_id):
        try:
            return self.form_accessors.get_form(doc_id).to_json()
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)
def perform_resave_on_xforms(domain):
    xform_ids_missing_in_es, _ = compare_xforms(domain, 'XFormInstance')
    print("%s Ids found for xforms missing in ES." % len(xform_ids_missing_in_es))
    print(xform_ids_missing_in_es)
    ok = input("Type 'ok' to continue: ")
    if ok != "ok":
        print("No changes made")
        return
    form_accessor = FormAccessors(domain)
    for xform_id in with_progress_bar(xform_ids_missing_in_es):
        xform = form_accessor.get_form(xform_id)
        if xform:
            resave_form(domain, xform)
        else:
            print("form not found %s" % xform_id)
예제 #13
0
    def get_version_from_app_object(self, item, app_version_from_app_string):
        domain_name = item.get('domain')
        form_accessor = FormAccessors(domain_name)
        form_id = item.get('form').get('meta').get('instanceID')
        form = form_accessor.get_form(form_id)

        app_build = get_build_by_version(domain_name, form.app_id, app_version_from_app_string)

        if app_build is None:
            return app_version_from_app_string

        elif app_build.get('value').get('doc_type') == 'LinkedApplication':
            return app_build.get('value').get('upstream_version')
        else:
            return app_build.get('value').get('version')
예제 #14
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)

    def get_document(self, doc_id):
        try:
            return add_couch_properties_to_sql_form_json(self.form_accessors.get_form(doc_id).to_json())
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)

    def iter_document_ids(self, last_id=None):
        # todo: support last_id
        return iter(self.form_accessors.get_all_form_ids_in_domain())

    def iter_documents(self, ids):
        for wrapped_form in self.form_accessors.iter_forms(ids):
            yield add_couch_properties_to_sql_form_json(wrapped_form.to_json())
예제 #15
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)

    def get_document(self, doc_id):
        try:
            return self.form_accessors.get_form(doc_id).to_json(include_attachments=True)
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)

    def iter_document_ids(self, last_id=None):
        # todo: support last_id
        return iter(self.form_accessors.get_all_form_ids_in_domain())

    def iter_documents(self, ids):
        for wrapped_form in self.form_accessors.iter_forms(ids):
            yield wrapped_form.to_json()
예제 #16
0
def _reopen_or_create_supply_point(location):
    from .helpers import update_supply_point_from_location
    from .dbaccessors import get_supply_point_by_location_id
    supply_point = get_supply_point_by_location_id(location.domain, location.location_id)
    if supply_point:
        if supply_point and supply_point.closed:
            form_ids = CaseAccessors(supply_point.domain).get_case_xform_ids(supply_point.case_id)
            form_accessor = FormAccessors(supply_point.domain)
            for form_id in form_ids:
                form = form_accessor.get_form(form_id)
                closes_case = any(map(
                    lambda case_update: case_update.closes_case(),
                    get_case_updates(form),
                ))
                if closes_case:
                    form.archive(user_id=const.COMMTRACK_USERNAME)
        update_supply_point_from_location(supply_point, location)
        return supply_point
    else:
        return SupplyInterface.create_from_location(location.domain, location)
예제 #17
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):
    def __init__(self, domain, xmlns=None):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)
        self.xmlns = xmlns

    def get_document(self, doc_id):
        try:
            form = self.form_accessors.get_form(doc_id)
            if isinstance(form, XFormInstanceSQL):
                return form.to_json(include_attachments=True)
            else:
                return form.to_json()
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)

    def iter_document_ids(self, last_id=None):
        # todo: support last_id
        return iter(self.form_accessors.iter_form_ids_by_xmlns(self.xmlns))

    def iter_documents(self, ids):
        for wrapped_form in self.form_accessors.iter_forms(ids):
            yield wrapped_form.to_json()
예제 #18
0
class ReadonlyFormDocumentStore(ReadOnlyDocumentStore):

    def __init__(self, domain, xmlns=None):
        self.domain = domain
        self.form_accessors = FormAccessors(domain=domain)
        self.xmlns = xmlns

    def get_document(self, doc_id):
        try:
            form = self.form_accessors.get_form(doc_id)
            if isinstance(form, XFormInstanceSQL):
                return form.to_json(include_attachments=True)
            else:
                return form.to_json()
        except (XFormNotFound, BlobError) as e:
            raise DocumentNotFoundError(e)

    def iter_document_ids(self, last_id=None):
        # todo: support last_id
        return iter(self.form_accessors.iter_form_ids_by_xmlns(self.xmlns))

    def iter_documents(self, ids):
        for wrapped_form in self.form_accessors.iter_forms(ids):
            yield wrapped_form.to_json()
예제 #19
0
class EditFormTest(TestCase, TestFileMixin):
    ID = '7H46J37FGH3'
    domain = 'test-form-edits'

    file_path = ('data', 'deprecation')
    root = os.path.dirname(__file__)

    def setUp(self):
        self.interface = FormProcessorInterface(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.formdb = FormAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms(self.domain)
        FormProcessorTestUtils.delete_all_cases(self.domain)
        UnfinishedSubmissionStub.objects.all().delete()

    @run_with_all_backends
    def test_basic_edit(self):
        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        xform = post_xform(original_xml, domain=self.domain)

        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("", xform.form_data['vitals']['height'])
        self.assertEqual("other", xform.form_data['assessment']['categories'])

        xform = post_xform(edit_xml, domain=self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("100", xform.form_data['vitals']['height'])
        self.assertEqual("Edited Baby!", xform.form_data['assessment']['categories'])

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)

        self.assertEqual(self.ID, deprecated_xform.orig_id)
        self.assertNotEqual(self.ID, deprecated_xform.form_id)
        self.assertTrue(deprecated_xform.is_deprecated)
        self.assertEqual("", deprecated_xform.form_data['vitals']['height'])
        self.assertEqual("other", deprecated_xform.form_data['assessment']['categories'])

        self.assertEqual(xform.received_on, deprecated_xform.received_on)
        self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id)
        self.assertTrue(xform.edited_on > deprecated_xform.received_on)

        self.assertEqual(
            deprecated_xform.get_xml(),
            original_xml
        )
        self.assertEqual(xform.get_xml(), edit_xml)

    @run_with_all_backends
    def test_broken_save(self):
        """
        Test that if the second form submission terminates unexpectedly
        and the main form isn't saved, then there are no side effects
        such as the original having been marked as deprecated.
        """

        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        _, xform, _ = submit_form_locally(original_xml, self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual(self.domain, xform.domain)

        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            0
        )

        with patch.object(self.interface.processor, 'save_processed_models', side_effect=RequestFailed):
            with self.assertRaises(RequestFailed):
                submit_form_locally(edit_xml, self.domain)

        # it didn't go through, so make sure there are no edits still
        self.assertIsNone(getattr(xform, 'deprecated_form_id', None))

        xform = self.formdb.get_form(self.ID)
        self.assertIsNotNone(xform)
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID,
                                                    saved=False).count(),
            1
        )
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            1
        )

    @run_with_all_backends
    def test_case_management(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
            update={
                'property': 'original value'
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # validate some assumptions
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'person')
        self.assertEqual(case.dynamic_case_properties()['property'], 'original value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

        # submit a new form with a different case update
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='newtype',
            owner_id=owner_id,
            update={
                'property': 'edited value'
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'newtype')
        self.assertEqual(case.dynamic_case_properties()['property'], 'edited value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

    @run_with_all_backends
    def test_second_edit_fails(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # submit an edit form with a bad case update (for example a bad ID)
        case_block = CaseBlock(
            create=True,
            case_id='',
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        xform = self.formdb.get_form(form_id)
        self.assertTrue(xform.is_error)

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)
        self.assertTrue(deprecated_xform.is_deprecated)

    @run_with_all_backends
    def test_case_management_ordering(self):
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex

        # create a case
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
        ).as_string()
        create_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual([create_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual([create_form_id], [a.xform_id for a in case.actions])
            for a in case.actions:
                self.assertEqual(create_form_id, a.xform_id)

        edit_date = datetime.utcnow()
        # set some property value
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=edit_date,
            update={
                'property': 'first value',
            }
        ).as_string()
        edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'first value')
        self.assertEqual([create_form_id, edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual([create_form_id, edit_form_id], [a.xform_id for a in case.actions])

        # submit a second (new) form updating the value
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            update={
                'property': 'final value',
            }
        ).as_string()
        second_edit_form_id = submit_case_blocks(case_block, domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'final value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(
                [create_form_id, edit_form_id, second_edit_form_id],
                [a.xform_id for a in case.actions]
            )

        # deprecate the middle edit
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=edit_date,  # need to use the previous edit date for action sort comparisons
            update={
                'property': 'edited value',
                'added_property': 'added value',
            }
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=edit_form_id)

        # ensure that the middle edit stays in the right place and is applied
        # before the final one
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'], 'final value')
        self.assertEqual(case.dynamic_case_properties()['added_property'], 'added value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(
                [create_form_id, edit_form_id, second_edit_form_id],
                [a.xform_id for a in case.actions]
            )
예제 #20
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)
class BaseCaseMultimediaTest(TestCase, TestFileMixin):

    file_path = ('data', 'multimedia')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(BaseCaseMultimediaTest, self).setUp()
        self.formdb = FormAccessors()
        FormProcessorTestUtils.delete_all_cases()
        FormProcessorTestUtils.delete_all_xforms()

    def _formatXForm(self, doc_id, raw_xml, attachment_block, date=None):
        if date is None:
            date = datetime.utcnow()
        final_xml = Template(raw_xml).render(
            Context({
                "attachments":
                attachment_block,
                "time_start":
                json_format_datetime(date - timedelta(minutes=4)),
                "time_end":
                json_format_datetime(date),
                "date_modified":
                json_format_datetime(date),
                "doc_id":
                doc_id
            }))
        return final_xml

    def _prepAttachments(self, new_attachments, removes=[]):
        """
        Returns:
            attachment_block - An XML representation of the attachment
            dict_attachments - A key-value dict where the key is the name and the value is a Stream of the
            attachment
        """
        attachment_block = ''.join(
            [self._singleAttachBlock(x) for x in new_attachments] +
            [self._singleAttachRemoveBlock(x) for x in removes])
        dict_attachments = dict(
            (MEDIA_FILES[attach_name], self._attachmentFileStream(attach_name))
            for attach_name in new_attachments)
        return attachment_block, dict_attachments

    def _singleAttachBlock(self, key):
        return '<n0:%s src="%s" from="local"/>' % (key, MEDIA_FILES[key])

    def _singleAttachRemoveBlock(self, key):
        return '<n0:%s />' % key

    def _attachmentFileStream(self, key):
        attachment_path = MEDIA_FILES[key]
        attachment = open(attachment_path, 'rb')
        return NoClose(UploadedFile(attachment, key))

    def _calc_file_hash(self, key):
        with open(MEDIA_FILES[key], 'rb') as attach:
            return hashlib.md5(attach.read()).hexdigest()

    def _do_submit(self,
                   xml_data,
                   dict_attachments,
                   sync_token=None,
                   date=None):
        """
        RequestFactory submitter - simulates direct submission to server
        directly (no need to call process case after fact)
        """
        with flag_enabled('MM_CASE_PROPERTIES'):
            result = submit_form_locally(xml_data,
                                         TEST_DOMAIN_NAME,
                                         attachments=dict_attachments,
                                         last_sync_token=sync_token,
                                         received_on=date)
        xform = result.xform
        attachments = xform.attachments
        self.assertEqual(set(dict_attachments.keys()), set(attachments.keys()))
        self.assertEqual(result.case.case_id, TEST_CASE_ID)

        return result.response, self.formdb.get_form(
            xform.form_id), result.cases

    def _submit_and_verify(self,
                           doc_id,
                           xml_data,
                           dict_attachments,
                           sync_token=None,
                           date=None):
        response, form, [case] = self._do_submit(xml_data,
                                                 dict_attachments,
                                                 sync_token,
                                                 date=date)

        attachments = form.attachments
        self.assertEqual(len(dict_attachments), len(attachments))
        for k, vstream in dict_attachments.items():
            fileback = form.get_attachment(k)
            # rewind the pointer before comparing
            orig_attachment = vstream
            orig_attachment.seek(0)
            self.assertEqual(
                hashlib.md5(fileback).hexdigest(),
                hashlib.md5(orig_attachment.read()).hexdigest())

        case = CaseAccessors(TEST_DOMAIN_NAME).get_case(
            case.case_id)  # re-fetch case
        return form, case

    def _doCreateCaseWithMultimedia(self, attachments=['fruity_file']):
        xml_data = self.get_xml('multimedia_create')
        attachment_block, dict_attachments = self._prepAttachments(attachments)
        final_xml = self._formatXForm(CREATE_XFORM_ID, xml_data,
                                      attachment_block)
        return self._submit_and_verify(CREATE_XFORM_ID, final_xml,
                                       dict_attachments)

    def _doSubmitUpdateWithMultimedia(self,
                                      new_attachments=None,
                                      removes=None,
                                      sync_token=None,
                                      date=None):
        new_attachments = new_attachments if new_attachments is not None \
            else ['commcare_logo_file', 'dimagi_logo_file']
        removes = removes if removes is not None else ['fruity_file']
        attachment_block, dict_attachments = self._prepAttachments(
            new_attachments, removes=removes)
        raw_xform = self.get_xml('multimedia_update')
        doc_id = uuid.uuid4().hex
        final_xform = self._formatXForm(doc_id, raw_xform, attachment_block,
                                        date)
        return self._submit_and_verify(doc_id,
                                       final_xform,
                                       dict_attachments,
                                       sync_token,
                                       date=date)
예제 #22
0
class ReprocessSubmissionStubTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super(ReprocessSubmissionStubTests, cls).setUpClass()
        cls.domain = uuid.uuid4().hex
        cls.product = SQLProduct.objects.create(domain=cls.domain, product_id='product1', name='product1')

    @classmethod
    def tearDownClass(cls):
        cls.product.delete()
        super(ReprocessSubmissionStubTests, cls).tearDownClass()

    def setUp(self):
        super(ReprocessSubmissionStubTests, self).setUp()
        self.factory = CaseFactory(domain=self.domain)
        self.formdb = FormAccessors(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.ledgerdb = LedgerAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain)
        super(ReprocessSubmissionStubTests, self).tearDown()

    def test_reprocess_unfinished_submission_case_create(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id, attrs={'case_type': 'parent', 'create': True})
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        # form that was saved before case error raised
        normal_form_ids = self.formdb.get_all_form_ids_in_domain('XFormInstance')
        self.assertEqual(0, len(normal_form_ids))

        # shows error form (duplicate of form that was saved before case error)
        # this is saved becuase the saving was assumed to be atomic so if there was any error it's assumed
        # the form didn't get saved
        # we don't really care about this form in this test
        error_forms = self.formdb.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))
        self.assertIsNone(error_forms[0].orig_id)
        self.assertEqual(error_forms[0].form_id, stubs[0].xform_id)

        self.assertEqual(0, len(self.casedb.get_case_ids_in_domain(self.domain)))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))

        case_ids = self.casedb.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        self.assertEqual(case_id, case_ids[0])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)

    def test_reprocess_unfinished_submission_case_update(self):
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(submit_case_blocks(
            CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'),
            self.domain
        )[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={'prop': 'a'}).as_string().decode('utf-8'),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        form_ids.append(stubs[0].xform_id)

        # submit second form with case update
        form_ids.append(submit_case_blocks(
            CaseBlock(case_id=case_id, update={'prop': 'b'}).as_string().decode('utf-8'),
            self.domain
        )[0].form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        self.assertEqual('b', case.get_case_property('prop'))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual('b', case.get_case_property('prop'))  # should be property value from most recent form
        self.assertEqual(3, len(case.xform_ids))
        self.assertEqual(form_ids, case.xform_ids)

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)

    def test_reprocess_unfinished_submission_ledger_create(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        self.factory.create_or_update_cases([
            CaseStructure(case_id=case_id, attrs={'case_type': 'parent', 'create': True})
        ])

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 100),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(0, len(ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual(1, len(case.xform_ids))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        self.assertEqual(0, len(ledger_transactions))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        self.assertEqual(1, len(ledger_transactions))

        # case still only has 2 transactions
        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        if should_use_sql_backend(self.domain):
            self.assertTrue(case.actions[1].is_ledger_transaction)

    def test_reprocess_unfinished_submission_ledger_rebuild(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(submit_case_blocks(
            [
                CaseBlock(case_id=case_id, create=True, case_type='shop').as_string().decode('utf-8'),
                get_single_balance_block(case_id, 'product1', 100),
            ],
            self.domain
        )[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 50),
                self.domain
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))
        form_ids.append(stubs[0].xform_id)

        # submit another form afterwards
        form_ids.append(submit_case_blocks(
            get_single_balance_block(case_id, 'product1', 25),
            self.domain
        )[0].form_id)

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(2, len(ledger_transactions))
        else:
            # includes extra consumption transaction
            self.assertEqual(3, len(ledger_transactions))

        # should rebuild ledger transactions
        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))  # still only 1
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(3, len(ledger_transactions))
            # make sure transactions are in correct order
            self.assertEqual(form_ids, [trans.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].updated_balance)
            self.assertEqual(100, ledger_transactions[0].delta)
            self.assertEqual(50, ledger_transactions[1].updated_balance)
            self.assertEqual(-50, ledger_transactions[1].delta)
            self.assertEqual(25, ledger_transactions[2].updated_balance)
            self.assertEqual(-25, ledger_transactions[2].delta)

        else:
            self.assertEqual(3, len(ledger_transactions))
            self.assertEqual(form_ids, [trans.report.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].stock_on_hand)
            self.assertEqual(50, ledger_transactions[1].stock_on_hand)
            self.assertEqual(25, ledger_transactions[2].stock_on_hand)

    def test_fire_signals(self):
        from corehq.apps.receiverwrapper.tests.test_submit_errors import failing_signal_handler
        case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        with failing_signal_handler('signal death'):
            submit_case_blocks(
                CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'),
                self.domain,
                form_id=form_id
            )

        form = self.formdb.get_form(form_id)

        with catch_signal(successful_form_received) as form_handler, catch_signal(case_post_save) as case_handler:
            submit_form_locally(
                instance=form.get_xml(),
                domain=self.domain,
            )

        case = self.casedb.get_case(case_id)

        if should_use_sql_backend(self.domain):
            self.assertEqual(form, form_handler.call_args[1]['xform'])
            self.assertEqual(case, case_handler.call_args[1]['case'])
        else:
            signal_form = form_handler.call_args[1]['xform']
            self.assertEqual(form.form_id, signal_form.form_id)
            self.assertEqual(form.get_rev, signal_form.get_rev)

            signal_case = case_handler.call_args[1]['case']
            self.assertEqual(case.case_id, signal_case.case_id)
            self.assertEqual(case.get_rev, signal_case.get_rev)
예제 #23
0
class TestReprocessDuringSubmission(TestCase):
    @classmethod
    def setUpClass(cls):
        super(TestReprocessDuringSubmission, cls).setUpClass()
        cls.domain = uuid.uuid4().hex

    def setUp(self):
        super(TestReprocessDuringSubmission, self).setUp()
        self.factory = CaseFactory(domain=self.domain)
        self.formdb = FormAccessors(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.ledgerdb = LedgerAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain)
        super(TestReprocessDuringSubmission, self).tearDown()

    def test_error_saving(self):
        case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                CaseBlock(case_id=case_id, create=True, case_type='box').as_string().decode('utf-8'),
                self.domain,
                form_id=form_id
            )

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(1, len(stubs))

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_error)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case_id)

        result = submit_form_locally(
            instance=form.get_xml(),
            domain=self.domain,
        )
        duplicate_form = result.xform
        self.assertTrue(duplicate_form.is_duplicate)

        case = self.casedb.get_case(case_id)
        self.assertIsNotNone(case)

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_normal)
        self.assertIsNone(getattr(form, 'problem', None))
        self.assertEqual(duplicate_form.orig_id, form.form_id)

    def test_processing_error(self):
        case_id = uuid.uuid4().hex
        parent_case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        form, _ = submit_case_blocks(
            CaseBlock(
                case_id=case_id, create=True, case_type='box',
                index={'cupboard': ('cupboard', parent_case_id)},
            ).as_string().decode('utf-8'),
            self.domain,
            form_id=form_id
        )

        self.assertTrue(form.is_error)
        self.assertTrue('InvalidCaseIndex' in form.problem)
        self.assertEqual(form.form_id, form_id)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case_id)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain, saved=False).all()
        self.assertEqual(0, len(stubs))

        # create parent case
        submit_case_blocks(
            CaseBlock(case_id=parent_case_id, create=True, case_type='cupboard').as_string().decode('utf-8'),
            self.domain,
        )

        # re-submit form
        result = submit_form_locally(
            instance=form.get_xml(),
            domain=self.domain,
        )
        duplicate_form = result.xform
        self.assertTrue(duplicate_form.is_duplicate)

        case = self.casedb.get_case(case_id)
        self.assertIsNotNone(case)

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_normal)
        self.assertIsNone(getattr(form, 'problem', None))
        self.assertEqual(duplicate_form.orig_id, form.form_id)
예제 #24
0
class EditFormTest(TestCase, TestFileMixin):
    ID = '7H46J37FGH3'
    domain = 'test-form-edits'

    file_path = ('data', 'deprecation')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(EditFormTest, self).setUp()
        self.interface = FormProcessorInterface(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.formdb = FormAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms(self.domain)
        FormProcessorTestUtils.delete_all_cases(self.domain)
        UnfinishedSubmissionStub.objects.all().delete()
        super(EditFormTest, self).tearDown()

    @run_with_all_backends
    def test_basic_edit(self):
        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        xform = post_xform(original_xml, domain=self.domain)

        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("", xform.form_data['vitals']['height'])
        self.assertEqual("other", xform.form_data['assessment']['categories'])

        xform = post_xform(edit_xml, domain=self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("100", xform.form_data['vitals']['height'])
        self.assertEqual("Edited Baby!",
                         xform.form_data['assessment']['categories'])

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)

        self.assertEqual(self.ID, deprecated_xform.orig_id)
        self.assertNotEqual(self.ID, deprecated_xform.form_id)
        self.assertTrue(deprecated_xform.is_deprecated)
        self.assertEqual("", deprecated_xform.form_data['vitals']['height'])
        self.assertEqual(
            "other", deprecated_xform.form_data['assessment']['categories'])

        self.assertEqual(xform.received_on, deprecated_xform.received_on)
        self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id)
        self.assertTrue(xform.edited_on > deprecated_xform.received_on)

        self.assertEqual(deprecated_xform.get_xml(), original_xml)
        self.assertEqual(xform.get_xml(), edit_xml)

    @run_with_all_backends
    def test_broken_save(self):
        """
        Test that if the second form submission terminates unexpectedly
        and the main form isn't saved, then there are no side effects
        such as the original having been marked as deprecated.
        """

        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        _, xform, _ = submit_form_locally(original_xml, self.domain)
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual(self.domain, xform.domain)

        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            0)

        with patch.object(self.interface.processor,
                          'save_processed_models',
                          side_effect=RequestFailed):
            with self.assertRaises(RequestFailed):
                submit_form_locally(edit_xml, self.domain)

        # it didn't go through, so make sure there are no edits still
        self.assertIsNone(getattr(xform, 'deprecated_form_id', None))

        xform = self.formdb.get_form(self.ID)
        self.assertIsNotNone(xform)
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID,
                                                    saved=False).count(), 1)
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            1)

    @run_with_all_backends
    def test_case_management(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex
        case_block = CaseBlock(create=True,
                               case_id=case_id,
                               case_type='person',
                               owner_id=owner_id,
                               update={
                                   'property': 'original value'
                               }).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # validate some assumptions
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'person')
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'original value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

        # submit a new form with a different case update
        case_block = CaseBlock(create=True,
                               case_id=case_id,
                               case_type='newtype',
                               owner_id=owner_id,
                               update={
                                   'property': 'edited value'
                               }).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'newtype')
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'edited value')
        self.assertEqual([form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

    @run_with_all_backends
    def test_second_edit_fails(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # submit an edit form with a bad case update (for example a bad ID)
        case_block = CaseBlock(
            create=True,
            case_id='',
            case_type='person',
        ).as_string()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        xform = self.formdb.get_form(form_id)
        self.assertTrue(xform.is_error)

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)
        self.assertTrue(deprecated_xform.is_deprecated)

    @run_with_all_backends
    def test_case_management_ordering(self):
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex

        # create a case
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
        ).as_string()
        create_form_id = submit_case_blocks(case_block,
                                            domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual([create_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertTrue(
                create_form_id in [a.xform_id for a in case.actions])
            for a in case.actions:
                self.assertEqual(create_form_id, a.xform_id)

        edit_date = datetime.utcnow()
        # set some property value
        case_block = CaseBlock(create=False,
                               case_id=case_id,
                               date_modified=edit_date,
                               update={
                                   'property': 'first value',
                               }).as_string()
        edit_form_id = submit_case_blocks(case_block,
                                          domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'first value')
        self.assertEqual([create_form_id, edit_form_id], case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertTrue(
                all(form_id in [a.xform_id for a in case.actions]
                    for form_id in [create_form_id, edit_form_id]))

        # submit a second (new) form updating the value
        case_block = CaseBlock(create=False,
                               case_id=case_id,
                               update={
                                   'property': 'final value',
                               }).as_string()
        second_edit_form_id = submit_case_blocks(case_block,
                                                 domain=self.domain).form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'final value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id],
                         case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertTrue(
                all(form_id in [a.xform_id for a in case.actions] for form_id
                    in [create_form_id, edit_form_id, second_edit_form_id]))

        # deprecate the middle edit
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=
            edit_date,  # need to use the previous edit date for action sort comparisons
            update={
                'property': 'edited value',
                'added_property': 'added value',
            }).as_string()
        submit_case_blocks(case_block,
                           domain=self.domain,
                           form_id=edit_form_id)

        # ensure that the middle edit stays in the right place and is applied
        # before the final one
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'final value')
        self.assertEqual(case.dynamic_case_properties()['added_property'],
                         'added value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id],
                         case.xform_ids)

        if not settings.TESTS_SHOULD_USE_SQL_BACKEND:
            self.assertEqual([
                create_form_id, create_form_id, edit_form_id,
                second_edit_form_id
            ], [a.xform_id for a in case.actions])

    def test_couch_blob_migration_edit(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        case_block = CaseBlock(create=True, case_id=case_id).as_string()
        xform = submit_case_blocks(case_block,
                                   domain=self.domain,
                                   form_id=form_id)
        # explicitly convert to old-style couch attachments to test the migration workflow
        form_xml = xform.get_xml()
        xform.delete_attachment('form.xml')
        xform.get_db().put_attachment(xform.to_json(), form_xml, 'form.xml')
        # make sure that worked
        updated_form_xml = XFormInstance.get(xform._id).get_xml()
        self.assertEqual(form_xml, updated_form_xml)
        # this call was previously failing
        updated_form = submit_case_blocks(case_block,
                                          domain=self.domain,
                                          form_id=form_id)
        self.assertEqual(
            form_xml,
            XFormInstance.get(updated_form.deprecated_form_id).get_xml())
예제 #25
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(TestFormArchiving, self).setUp()
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()
        super(TestFormArchiving, self).tearDown()

    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    def testUnfinishedArchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # Get the form with the updated history, it should be archived
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # Make sure the history only has an archive and an unarchive
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should not exist because the unarchived form was not rebuilt
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnarchivingWithArchiveStub(self):
        # Test a user-initiated unarchive with an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))
        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Call an unarchive
        xform.unarchive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should exist because the case close was unarchived
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the case still exists (to double check that the archive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

    def testArchivingWithUnarchiveStub(self):
        # Test a user-initiated archive with an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch('couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Call an archive
        xform.archive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should not exist because the case close was archived
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The history should not have been added to, make sure that it still only has one entry

        # Make sure the case still does not exist (to double check that the unarchive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

    def testUnfinishedArchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'archive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.archive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it has not been archived yet
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(0, len(xform.history))
        self.assertFalse(xform.is_archived)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'unarchive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation') as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.unarchive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it only has one entry (the archive)
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should not exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive and an unarchive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        result.xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(result.xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)
예제 #26
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(TestFormArchiving, self).setUp()
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()
        super(TestFormArchiving, self).tearDown()

    @run_with_all_backends
    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    @run_with_all_backends
    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)

    @override_settings(TESTS_SHOULD_USE_SQL_BACKEND=True)
    def testPublishChanges(self):
        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        with capture_kafka_changes_context(topics.FORM_SQL) as change_context:
            with drop_connected_signals(xform_archived):
                xform.archive()
        self.assertEqual(1, len(change_context.changes))
        self.assertEqual(change_context.changes[0].id, xform.form_id)

        xform = self.formdb.get_form(xform.form_id)
        with capture_kafka_changes_context(topics.FORM_SQL) as change_context:
            with drop_connected_signals(xform_unarchived):
                xform.unarchive()
        self.assertEqual(1, len(change_context.changes))
        self.assertEqual(change_context.changes[0].id, xform.form_id)
예제 #27
0
class TestHardDelete(TestCase):

    def setUp(self):
        self.casedb = CaseAccessors(TEST_DOMAIN_NAME)
        self.formdb = FormAccessors(TEST_DOMAIN_NAME)

    @run_with_all_backends
    def test_simple_delete(self):
        factory = CaseFactory()
        case = factory.create_case()
        [case] = factory.create_or_update_case(
            CaseStructure(case_id=case.case_id, attrs={'update': {'foo': 'bar'}})
        )
        self.assertIsNotNone(self.casedb.get_case(case.case_id))
        self.assertEqual(2, len(case.xform_ids))
        for form_id in case.xform_ids:
            self.assertIsNotNone(self.formdb.get_form(form_id))

        with capture_kafka_changes_context(topics.FORM_SQL, topics.CASE_SQL) as change_context:
            safe_hard_delete(case)

        if should_use_sql_backend(case.domain):
            self.assertEqual(3, len(change_context.changes))
            expected_ids = {case.case_id} | set(case.xform_ids)
            self.assertEqual(expected_ids, {change.id for change in change_context.changes})
            for change in change_context.changes:
                self.assertTrue(change.deleted)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case.case_id)

        for form_id in case.xform_ids:
            with self.assertRaises(XFormNotFound):
                self.formdb.get_form(form_id)

    @run_with_all_backends
    def test_delete_with_related(self):
        factory = CaseFactory()
        parent = factory.create_case()
        [child] = factory.create_or_update_case(
            CaseStructure(attrs={'create': True}, walk_related=False, indices=[
                CaseIndex(CaseStructure(case_id=parent.case_id))
            ]),
        )
        # deleting the parent should not be allowed because the child still references it
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(parent)

        # deleting the child is ok
        safe_hard_delete(child)
        self.assertIsNotNone(self.casedb.get_case(parent.case_id))
        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(child.case_id)

    @run_with_all_backends
    def test_delete_sharing_form(self):
        factory = CaseFactory()
        c1, c2 = factory.create_or_update_cases([
            CaseStructure(attrs={'create': True}),
            CaseStructure(attrs={'create': True}),
        ])
        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c1)

        with self.assertRaises(CommCareCaseError):
            safe_hard_delete(c2)

        self.assertIsNotNone(self.casedb.get_case(c1.case_id))
        self.assertIsNotNone(self.casedb.get_case(c2.case_id))
예제 #28
0
class TestReprocessDuringSubmission(TestCase):
    @classmethod
    def setUpClass(cls):
        super(TestReprocessDuringSubmission, cls).setUpClass()
        cls.domain = uuid.uuid4().hex

    def setUp(self):
        super(TestReprocessDuringSubmission, self).setUp()
        self.factory = CaseFactory(domain=self.domain)
        self.formdb = FormAccessors(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.ledgerdb = LedgerAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain)
        super(TestReprocessDuringSubmission, self).tearDown()

    def test_error_saving(self):
        case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            submit_case_blocks(CaseBlock(case_id=case_id,
                                         create=True,
                                         case_type='box').as_string(),
                               self.domain,
                               form_id=form_id)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_error)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case_id)

        result = submit_form_locally(
            instance=form.get_xml(),
            domain=self.domain,
        )
        duplicate_form = result.xform
        self.assertTrue(duplicate_form.is_duplicate)

        case = self.casedb.get_case(case_id)
        self.assertIsNotNone(case)

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_normal)
        self.assertIsNone(getattr(form, 'problem', None))
        self.assertEqual(duplicate_form.orig_id, form.form_id)

    def test_processing_error(self):
        case_id = uuid.uuid4().hex
        parent_case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        form, _ = submit_case_blocks(CaseBlock(
            case_id=case_id,
            create=True,
            case_type='box',
            index={
                'cupboard': ('cupboard', parent_case_id)
            },
        ).as_string(),
                                     self.domain,
                                     form_id=form_id)

        self.assertTrue(form.is_error)
        self.assertTrue('InvalidCaseIndex' in form.problem)
        self.assertEqual(form.form_id, form_id)

        with self.assertRaises(CaseNotFound):
            self.casedb.get_case(case_id)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(0, len(stubs))

        # create parent case
        submit_case_blocks(
            CaseBlock(case_id=parent_case_id,
                      create=True,
                      case_type='cupboard').as_string(),
            self.domain,
        )

        # re-submit form
        result = submit_form_locally(
            instance=form.get_xml(),
            domain=self.domain,
        )
        duplicate_form = result.xform
        self.assertTrue(duplicate_form.is_duplicate)

        case = self.casedb.get_case(case_id)
        self.assertIsNotNone(case)

        form = self.formdb.get_form(form_id)
        self.assertTrue(form.is_normal)
        self.assertIsNone(getattr(form, 'problem', None))
        self.assertEqual(duplicate_form.orig_id, form.form_id)
예제 #29
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(TestFormArchiving, self).setUp()
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()
        super(TestFormArchiving, self).tearDown()

    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        result.xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(result.xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)
예제 #30
0
class EditFormTest(TestCase, TestFileMixin):
    ID = '7H46J37FGH3'
    domain = 'test-form-edits'

    file_path = ('data', 'deprecation')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(EditFormTest, self).setUp()
        self.interface = FormProcessorInterface(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.formdb = FormAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms(self.domain)
        FormProcessorTestUtils.delete_all_cases(self.domain)
        UnfinishedSubmissionStub.objects.all().delete()
        super(EditFormTest, self).tearDown()

    def test_basic_edit(self):
        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        xform = submit_form_locally(original_xml, self.domain).xform

        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("", xform.form_data['vitals']['height'])
        self.assertEqual("other", xform.form_data['assessment']['categories'])

        xform = submit_form_locally(edit_xml, self.domain).xform
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual("100", xform.form_data['vitals']['height'])
        self.assertEqual("Edited Baby!",
                         xform.form_data['assessment']['categories'])

        self.assertEqual(1, len(xform.history))
        self.assertEqual('edit', xform.history[0].operation)

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)

        self.assertEqual(self.ID, deprecated_xform.orig_id)
        self.assertNotEqual(self.ID, deprecated_xform.form_id)
        self.assertTrue(deprecated_xform.is_deprecated)
        self.assertEqual("", deprecated_xform.form_data['vitals']['height'])
        self.assertEqual(
            "other", deprecated_xform.form_data['assessment']['categories'])

        self.assertEqual(xform.received_on, deprecated_xform.received_on)
        self.assertEqual(xform.deprecated_form_id, deprecated_xform.form_id)
        self.assertTrue(xform.edited_on > deprecated_xform.received_on)

        self.assertEqual(deprecated_xform.get_xml().decode('utf-8'),
                         original_xml.decode('utf-8'))
        self.assertEqual(xform.get_xml().decode('utf-8'),
                         edit_xml.decode('utf-8'))

    def test_edit_form_with_attachments(self):
        attachment_source = './corehq/ex-submodules/casexml/apps/case/tests/data/attachments/fruity.jpg'
        attachment_file = open(attachment_source, 'rb')
        attachments = {
            'fruity_file':
            UploadedFile(attachment_file,
                         'fruity_file',
                         content_type='image/jpeg')
        }

        def _get_xml(date, form_id):
            return """<?xml version='1.0' ?>
               <data uiVersion="1" version="1" name="" xmlns="http://openrosa.org/formdesigner/123">
                   <name>fgg</name>
                   <date>2011-06-07</date>
                   <n1:meta xmlns:n1="http://openrosa.org/jr/xforms">
                       <n1:deviceID>354957031935664</n1:deviceID>
                       <n1:timeStart>{date}</n1:timeStart>
                       <n1:timeEnd>{date}</n1:timeEnd>
                       <n1:username>bcdemo</n1:username>
                       <n1:userID>user-abc</n1:userID>
                       <n1:instanceID>{form_id}</n1:instanceID>
                   </n1:meta>
               </data>""".format(date=date,
                                 attachment_source=attachment_source,
                                 form_id=form_id)

        form_id = uuid.uuid4().hex
        original_xml = _get_xml('2016-03-01T12:04:16Z', form_id)
        submit_form_locally(
            original_xml,
            self.domain,
            attachments=attachments,
        )
        form = self.formdb.get_form(form_id)
        self.assertIn('fruity_file', form.attachments)
        self.assertIn(original_xml, form.get_xml().decode('utf-8'))

        # edit form
        edit_xml = _get_xml('2016-04-01T12:04:16Z', form_id)
        submit_form_locally(
            edit_xml,
            self.domain,
        )
        form = self.formdb.get_form(form_id)
        self.assertIsNotNone(form.edited_on)
        self.assertIsNotNone(form.deprecated_form_id)
        self.assertIn('fruity_file', form.attachments)
        self.assertIn(edit_xml, form.get_xml().decode('utf-8'))

    def test_edit_an_error(self):
        form_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id='',  # this should cause the submission to error
            case_type='person',
            owner_id='some-owner',
        )

        form, _ = submit_case_blocks(case_block.as_text(),
                                     domain=self.domain,
                                     form_id=form_id)
        self.assertTrue(form.is_error)
        self.assertTrue('IllegalCaseId' in form.problem)

        case_block.case_id = uuid.uuid4().hex
        form, _ = submit_case_blocks(case_block.as_text(),
                                     domain=self.domain,
                                     form_id=form_id)
        self.assertFalse(form.is_error)
        self.assertEqual(None, getattr(form, 'problem', None))

    def test_broken_save(self):
        """
        Test that if the second form submission terminates unexpectedly
        and the main form isn't saved, then there are no side effects
        such as the original having been marked as deprecated.
        """

        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        result = submit_form_locally(original_xml, self.domain)
        xform = result.xform
        self.assertEqual(self.ID, xform.form_id)
        self.assertTrue(xform.is_normal)
        self.assertEqual(self.domain, xform.domain)

        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            0)

        with patch.object(self.interface.processor,
                          'save_processed_models',
                          side_effect=HTTPError):
            with self.assertRaises(HTTPError):
                submit_form_locally(edit_xml, self.domain)

        xform = self.formdb.get_form(self.ID)
        self.assertIsNotNone(xform)
        # it didn't go through, so make sure there are no edits still
        self.assertIsNone(getattr(xform, 'deprecated_form_id', None))
        self.assertEqual(
            UnfinishedSubmissionStub.objects.filter(xform_id=self.ID).count(),
            0)

    def test_case_management(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex
        case_block = CaseBlock(create=True,
                               case_id=case_id,
                               case_type='person',
                               owner_id=owner_id,
                               update={
                                   'property': 'original value'
                               }).as_text()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # validate some assumptions
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'person')
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'original value')
        self.assertEqual([form_id], case.xform_ids)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

        # submit a new form with a different case update
        case_block = CaseBlock(create=True,
                               case_id=case_id,
                               case_type='newtype',
                               owner_id=owner_id,
                               update={
                                   'property': 'edited value'
                               }).as_text()
        xform, _ = submit_case_blocks(case_block,
                                      domain=self.domain,
                                      form_id=form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(case.type, 'newtype')
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'edited value')
        self.assertEqual([form_id], case.xform_ids)
        self.assertEqual(case.server_modified_on, xform.edited_on)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertEqual(2, len(case.actions))
            for a in case.actions:
                self.assertEqual(form_id, a.xform_id)

    def test_second_edit_fails(self):
        form_id = uuid.uuid4().hex
        case_id = uuid.uuid4().hex
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
        ).as_text()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        # submit an edit form with a bad case update (for example a bad ID)
        case_block = CaseBlock(
            create=True,
            case_id='',
            case_type='person',
        ).as_text()
        submit_case_blocks(case_block, domain=self.domain, form_id=form_id)

        xform = self.formdb.get_form(form_id)
        self.assertTrue(xform.is_error)

        deprecated_xform = self.formdb.get_form(xform.deprecated_form_id)
        self.assertTrue(deprecated_xform.is_deprecated)

    def test_case_management_ordering(self):
        case_id = uuid.uuid4().hex
        owner_id = uuid.uuid4().hex

        # create a case
        case_block = CaseBlock(
            create=True,
            case_id=case_id,
            case_type='person',
            owner_id=owner_id,
        ).as_text()
        create_form_id = submit_case_blocks(case_block,
                                            domain=self.domain)[0].form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual([create_form_id], case.xform_ids)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertTrue(
                create_form_id in [a.xform_id for a in case.actions])
            for a in case.actions:
                self.assertEqual(create_form_id, a.xform_id)

        edit_date = datetime.utcnow()
        # set some property value
        case_block = CaseBlock(create=False,
                               case_id=case_id,
                               date_modified=edit_date,
                               update={
                                   'property': 'first value',
                               }).as_text()
        edit_form_id = submit_case_blocks(case_block,
                                          domain=self.domain)[0].form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'first value')
        self.assertEqual([create_form_id, edit_form_id], case.xform_ids)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertTrue(
                all(form_id in [a.xform_id for a in case.actions]
                    for form_id in [create_form_id, edit_form_id]))

        # submit a second (new) form updating the value
        case_block = CaseBlock(create=False,
                               case_id=case_id,
                               update={
                                   'property': 'final value',
                               }).as_text()
        second_edit_form_id = submit_case_blocks(case_block,
                                                 domain=self.domain)[0].form_id

        # validate that worked
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'final value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id],
                         case.xform_ids)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertTrue(
                all(form_id in [a.xform_id for a in case.actions] for form_id
                    in [create_form_id, edit_form_id, second_edit_form_id]))

        # deprecate the middle edit
        case_block = CaseBlock(
            create=False,
            case_id=case_id,
            date_modified=
            edit_date,  # need to use the previous edit date for action sort comparisons
            update={
                'property': 'edited value',
                'added_property': 'added value',
            }).as_text()
        submit_case_blocks(case_block,
                           domain=self.domain,
                           form_id=edit_form_id)

        # ensure that the middle edit stays in the right place and is applied
        # before the final one
        case = self.casedb.get_case(case_id)
        self.assertEqual(case.dynamic_case_properties()['property'],
                         'final value')
        self.assertEqual(case.dynamic_case_properties()['added_property'],
                         'added value')
        self.assertEqual([create_form_id, edit_form_id, second_edit_form_id],
                         case.xform_ids)

        if not getattr(settings, 'TESTS_SHOULD_USE_SQL_BACKEND', False):
            self.assertEqual([
                create_form_id, create_form_id, edit_form_id,
                second_edit_form_id
            ], [a.xform_id for a in case.actions])

    def test_edit_different_xmlns(self):
        form_id = uuid.uuid4().hex
        case1_id = uuid.uuid4().hex
        case2_id = uuid.uuid4().hex
        xmlns1 = 'http://commcarehq.org/xmlns1'
        xmlns2 = 'http://commcarehq.org/xmlns2'

        case_block = CaseBlock(
            create=True,
            case_id=case1_id,
            case_type='person',
            owner_id='owner1',
        ).as_text()
        xform, cases = submit_case_blocks(case_block,
                                          domain=self.domain,
                                          xmlns=xmlns1,
                                          form_id=form_id)

        self.assertTrue(xform.is_normal)
        self.assertEqual(form_id, xform.form_id)

        case_block = CaseBlock(
            create=True,
            case_id=case2_id,
            case_type='goat',
            owner_id='owner1',
        ).as_text()
        # submit new form with same form ID but different XMLNS
        xform, cases = submit_case_blocks(case_block,
                                          domain=self.domain,
                                          xmlns=xmlns2,
                                          form_id=form_id)

        self.assertTrue(xform.is_normal)
        self.assertNotEqual(form_id,
                            xform.form_id)  # form should have a different ID

    def test_copy_operations(self):
        original_xml = self.get_xml('original')
        edit_xml = self.get_xml('edit')

        xform = submit_form_locally(original_xml, self.domain).xform
        xform.archive(user_id='user1')
        xform.unarchive(user_id='user2')

        xform = submit_form_locally(edit_xml, self.domain).xform
        self.assertEqual(3, len(xform.history))
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('edit', xform.history[2].operation)
예제 #31
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()

    @run_with_all_backends
    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    @run_with_all_backends
    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)

    @override_settings(TESTS_SHOULD_USE_SQL_BACKEND=True)
    def testPublishChanges(self):
        xml_data = self.get_xml('basic')
        response, xform, cases = submit_form_locally(
            xml_data,
            'test-domain',
        )

        with capture_kafka_changes_context(topics.FORM_SQL) as change_context:
            with drop_connected_signals(xform_archived):
                xform.archive()
        self.assertEqual(1, len(change_context.changes))
        self.assertEqual(change_context.changes[0].id, xform.form_id)

        xform = self.formdb.get_form(xform.form_id)
        with capture_kafka_changes_context(topics.FORM_SQL) as change_context:
            with drop_connected_signals(xform_unarchived):
                xform.unarchive()
        self.assertEqual(1, len(change_context.changes))
        self.assertEqual(change_context.changes[0].id, xform.form_id)
예제 #32
0
class TestFormArchiving(TestCase, TestFileMixin):
    file_path = ('data', 'sample_xforms')
    root = os.path.dirname(__file__)

    def setUp(self):
        super(TestFormArchiving, self).setUp()
        self.casedb = CaseAccessors('test-domain')
        self.formdb = FormAccessors('test-domain')

    def tearDown(self):
        FormProcessorTestUtils.delete_all_xforms()
        FormProcessorTestUtils.delete_all_cases()
        super(TestFormArchiving, self).tearDown()

    def testArchive(self):
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.archive(user_id='mr. librarian')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_archived)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        self.assertEqual(case.xform_ids, [])

        [archival] = xform.history
        self.assertTrue(lower_bound <= archival.date <= upper_bound)
        self.assertEqual('archive', archival.operation)
        self.assertEqual('mr. librarian', archival.user)

        lower_bound = datetime.utcnow() - timedelta(seconds=1)
        xform.unarchive(user_id='mr. researcher')
        upper_bound = datetime.utcnow() + timedelta(seconds=1)

        xform = self.formdb.get_form(xform.form_id)
        self.assertTrue(xform.is_normal)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        self.assertEqual(case.xform_ids, [xform.form_id])

        [archival, restoration] = xform.history
        self.assertTrue(lower_bound <= restoration.date <= upper_bound)
        self.assertEqual('unarchive', restoration.operation)
        self.assertEqual('mr. researcher', restoration.user)

    def testUnfinishedArchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # Get the form with the updated history, it should be archived
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter(
        )
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStub(self):
        # Test running the celery task reprocess_archive_stubs on an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch(
                'couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # Make sure the history only has an archive and an unarchive
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should not exist because the unarchived form was not rebuilt
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter(
        )
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnarchivingWithArchiveStub(self):
        # Test a user-initiated unarchive with an existing archive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))
        # Mock the archive function throwing an error
        with mock.patch('couchforms.signals.xform_archived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.archive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished archive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Call an unarchive
        xform.unarchive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should exist because the case close was unarchived
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the case still exists (to double check that the archive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

    def testArchivingWithUnarchiveStub(self):
        # Test a user-initiated archive with an existing unarchive stub
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the unarchive function throwing an error
        with mock.patch(
                'couchforms.signals.xform_unarchived.send') as mock_send:
            try:
                mock_send.side_effect = Exception
                xform.unarchive(user_id='librarian')
            except Exception:
                pass

        # There should be a stub for the unfinished unarchive
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, True)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Call an archive
        xform.archive(user_id='librarian')

        # The unfinished archive stub should be deleted
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 0)

        # The case should not exist because the case close was archived
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # The history should not have been added to, make sure that it still only has one entry

        # Make sure the case still does not exist (to double check that the unarchive stub was deleted)
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

    def testUnfinishedArchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'archive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation'
                            ) as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.archive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it has not been archived yet
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(0, len(xform.history))
        self.assertFalse(xform.is_archived)

        # The case associated with the form should still exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, True)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case and stub should both be deleted now
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter(
        )
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testUnfinishedUnarchiveStubErrorAddingHistory(self):
        # Test running the celery task reprocess_archive_stubs on an existing archive stub where the archive
        # initially failed on updating the history
        case_id = 'ddb8e2b3-7ce0-43e4-ad45-d7a2eebe9169'
        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )
        xform = result.xform
        self.assertTrue(xform.is_normal)
        self.assertEqual(0, len(xform.history))

        # Archive the form successfully
        xform.archive(user_id='librarian')

        # Mock the couch and sql archive function throwing an error (so that this test works for both)
        with mock.patch('corehq.form_processor.backends.sql.dbaccessors.FormAccessorSQL.'
                        'unarchive_form') \
                as mock_operation_sql:
            with mock.patch('couchforms.models.XFormOperation'
                            ) as mock_operation_couch:
                try:
                    mock_operation_sql.side_effect = Exception
                    mock_operation_couch.side_effect = Exception
                    xform.unarchive(user_id='librarian')
                except Exception:
                    pass

        # Get the form with the updated history, make sure it only has one entry (the archive)
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(1, len(xform.history))
        self.assertTrue(xform.is_archived)
        [archival] = xform.history
        self.assertEqual('archive', archival.operation)
        self.assertEqual('librarian', archival.user)

        # The case associated with the form should not exist, it was not rebuilt because of the exception
        case = self.casedb.get_case(case_id)
        self.assertTrue(case.is_deleted)

        # There should be a stub for the unfinished archive, and the history should not be updated yet
        unfinished_archive_stubs = UnfinishedArchiveStub.objects.filter()
        self.assertEqual(len(unfinished_archive_stubs), 1)
        self.assertEqual(unfinished_archive_stubs[0].history_updated, False)
        self.assertEqual(unfinished_archive_stubs[0].user_id, 'librarian')
        self.assertEqual(unfinished_archive_stubs[0].domain, 'test-domain')
        self.assertEqual(unfinished_archive_stubs[0].archive, False)

        # Manually call the periodic celery task that reruns archiving/unarchiving actions
        reprocess_archive_stubs()

        # Make sure the history shows an archive and an unarchive now
        xform = self.formdb.get_form(xform.form_id)
        self.assertEqual(2, len(xform.history))
        self.assertFalse(xform.is_archived)
        self.assertEqual('archive', xform.history[0].operation)
        self.assertEqual('librarian', xform.history[0].user)
        self.assertEqual('unarchive', xform.history[1].operation)
        self.assertEqual('librarian', xform.history[1].user)

        # The case should be back, and the stub should be deleted now
        case = self.casedb.get_case(case_id)
        self.assertFalse(case.is_deleted)
        unfinished_archive_stubs_after_reprocessing = UnfinishedArchiveStub.objects.filter(
        )
        self.assertEqual(len(unfinished_archive_stubs_after_reprocessing), 0)

    def testSignal(self):
        global archive_counter, restore_counter
        archive_counter = 0
        restore_counter = 0

        def count_archive(**kwargs):
            global archive_counter
            archive_counter += 1

        def count_unarchive(**kwargs):
            global restore_counter
            restore_counter += 1

        xform_archived.connect(count_archive)
        xform_unarchived.connect(count_unarchive)

        xml_data = self.get_xml('basic')
        result = submit_form_locally(
            xml_data,
            'test-domain',
        )

        self.assertEqual(0, archive_counter)
        self.assertEqual(0, restore_counter)

        result.xform.archive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(0, restore_counter)

        xform = self.formdb.get_form(result.xform.form_id)
        xform.unarchive()
        self.assertEqual(1, archive_counter)
        self.assertEqual(1, restore_counter)
예제 #33
0
class ReprocessSubmissionStubTests(TestCase):
    @classmethod
    def setUpClass(cls):
        super(ReprocessSubmissionStubTests, cls).setUpClass()
        cls.domain = uuid.uuid4().hex
        cls.product = SQLProduct.objects.create(domain=cls.domain,
                                                product_id='product1',
                                                name='product1')

    @classmethod
    def tearDownClass(cls):
        cls.product.delete()
        super(ReprocessSubmissionStubTests, cls).tearDownClass()

    def setUp(self):
        super(ReprocessSubmissionStubTests, self).setUp()
        self.factory = CaseFactory(domain=self.domain)
        self.formdb = FormAccessors(self.domain)
        self.casedb = CaseAccessors(self.domain)
        self.ledgerdb = LedgerAccessors(self.domain)

    def tearDown(self):
        FormProcessorTestUtils.delete_all_cases_forms_ledgers(self.domain)
        super(ReprocessSubmissionStubTests, self).tearDown()

    def test_reprocess_unfinished_submission_case_create(self):
        case_id = uuid.uuid4().hex
        with _patch_save_to_raise_error(self):
            self.factory.create_or_update_cases([
                CaseStructure(case_id=case_id,
                              attrs={
                                  'case_type': 'parent',
                                  'create': True
                              })
            ])

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        # form that was saved before case error raised
        normal_form_ids = self.formdb.get_all_form_ids_in_domain(
            'XFormInstance')
        self.assertEqual(0, len(normal_form_ids))

        # shows error form (duplicate of form that was saved before case error)
        # this is saved becuase the saving was assumed to be atomic so if there was any error it's assumed
        # the form didn't get saved
        # we don't really care about this form in this test
        error_forms = self.formdb.get_forms_by_type('XFormError', 10)
        self.assertEqual(1, len(error_forms))
        self.assertIsNone(error_forms[0].orig_id)
        self.assertEqual(error_forms[0].form_id, stubs[0].xform_id)

        self.assertEqual(0,
                         len(self.casedb.get_case_ids_in_domain(self.domain)))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))

        case_ids = self.casedb.get_case_ids_in_domain()
        self.assertEqual(1, len(case_ids))
        self.assertEqual(case_id, case_ids[0])

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)

    def test_reprocess_unfinished_submission_case_update(self):
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(
            submit_case_blocks(
                CaseBlock(case_id=case_id, create=True,
                          case_type='box').as_string(),
                self.domain)[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={
                    'prop': 'a'
                }).as_string(), self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        form_ids.append(stubs[0].xform_id)

        # submit second form with case update
        form_ids.append(
            submit_case_blocks(
                CaseBlock(case_id=case_id, update={
                    'prop': 'b'
                }).as_string(), self.domain)[0].form_id)

        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        self.assertEqual('b', case.get_case_property('prop'))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(0, len(result.ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual('b', case.get_case_property(
            'prop'))  # should be property value from most recent form
        self.assertEqual(3, len(case.xform_ids))
        self.assertEqual(form_ids, case.xform_ids)

        with self.assertRaises(UnfinishedSubmissionStub.DoesNotExist):
            UnfinishedSubmissionStub.objects.get(pk=stubs[0].pk)

    def test_reprocess_unfinished_submission_ledger_create(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        self.factory.create_or_update_cases([
            CaseStructure(case_id=case_id,
                          attrs={
                              'case_type': 'parent',
                              'create': True
                          })
        ])

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 100),
                self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(0, len(ledgers))

        case = self.casedb.get_case(case_id)
        self.assertEqual(1, len(case.xform_ids))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(0, len(ledger_transactions))

        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        self.assertEqual(1, len(ledger_transactions))

        # case still only has 2 transactions
        case = self.casedb.get_case(case_id)
        self.assertEqual(2, len(case.xform_ids))
        if should_use_sql_backend(self.domain):
            self.assertTrue(case.actions[1].is_ledger_transaction)

    def test_reprocess_unfinished_submission_ledger_rebuild(self):
        from corehq.apps.commtrack.tests.util import get_single_balance_block
        case_id = uuid.uuid4().hex
        form_ids = []
        form_ids.append(
            submit_case_blocks([
                CaseBlock(case_id=case_id, create=True,
                          case_type='shop').as_string(),
                get_single_balance_block(case_id, 'product1', 100),
            ], self.domain)[0].form_id)

        with _patch_save_to_raise_error(self):
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 50), self.domain)

        stubs = UnfinishedSubmissionStub.objects.filter(domain=self.domain,
                                                        saved=False).all()
        self.assertEqual(1, len(stubs))
        form_ids.append(stubs[0].xform_id)

        # submit another form afterwards
        form_ids.append(
            submit_case_blocks(
                get_single_balance_block(case_id, 'product1', 25),
                self.domain)[0].form_id)

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(2, len(ledger_transactions))
        else:
            # includes extra consumption transaction
            self.assertEqual(3, len(ledger_transactions))

        # should rebuild ledger transactions
        result = reprocess_unfinished_stub(stubs[0])
        self.assertEqual(1, len(result.cases))
        self.assertEqual(1, len(result.ledgers))

        ledgers = self.ledgerdb.get_ledger_values_for_case(case_id)
        self.assertEqual(1, len(ledgers))  # still only 1
        self.assertEqual(25, ledgers[0].balance)

        ledger_transactions = self.ledgerdb.get_ledger_transactions_for_case(
            case_id)
        if should_use_sql_backend(self.domain):
            self.assertEqual(3, len(ledger_transactions))
            # make sure transactions are in correct order
            self.assertEqual(form_ids,
                             [trans.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].updated_balance)
            self.assertEqual(100, ledger_transactions[0].delta)
            self.assertEqual(50, ledger_transactions[1].updated_balance)
            self.assertEqual(-50, ledger_transactions[1].delta)
            self.assertEqual(25, ledger_transactions[2].updated_balance)
            self.assertEqual(-25, ledger_transactions[2].delta)

        else:
            self.assertEqual(3, len(ledger_transactions))
            self.assertEqual(
                form_ids,
                [trans.report.form_id for trans in ledger_transactions])
            self.assertEqual(100, ledger_transactions[0].stock_on_hand)
            self.assertEqual(50, ledger_transactions[1].stock_on_hand)
            self.assertEqual(25, ledger_transactions[2].stock_on_hand)

    def test_fire_signals(self):
        from corehq.apps.receiverwrapper.tests.test_submit_errors import failing_signal_handler
        case_id = uuid.uuid4().hex
        form_id = uuid.uuid4().hex
        with failing_signal_handler('signal death'):
            submit_case_blocks(CaseBlock(case_id=case_id,
                                         create=True,
                                         case_type='box').as_string(),
                               self.domain,
                               form_id=form_id)

        form = self.formdb.get_form(form_id)

        with catch_signal(
                successful_form_received) as form_handler, catch_signal(
                    case_post_save) as case_handler:
            submit_form_locally(
                instance=form.get_xml(),
                domain=self.domain,
            )

        case = self.casedb.get_case(case_id)

        if should_use_sql_backend(self.domain):
            self.assertEqual(form, form_handler.call_args[1]['xform'])
            self.assertEqual(case, case_handler.call_args[1]['case'])
        else:
            signal_form = form_handler.call_args[1]['xform']
            self.assertEqual(form.form_id, signal_form.form_id)
            self.assertEqual(form.get_rev, signal_form.get_rev)

            signal_case = case_handler.call_args[1]['case']
            self.assertEqual(case.case_id, signal_case.case_id)
            self.assertEqual(case.get_rev, signal_case.get_rev)