def setUp(self) -> None:
        self.app = create_app(config_module_class='metadata_service.config.LocalConfig')
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()
Exemple #2
0
    def setUp(self) -> None:
        self.app = create_app(config_module_class='metadata_service.config.LocalConfig')
        self.app.config['PROGRAMMATIC_DESCRIPTIONS_EXCLUDE_FILTERS'] = ['spark.*']
        self.app.config['WATERMARK_DATE_FORMATS'] = ''
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()
class TestAtlasProxy(unittest.TestCase, Data):
    def setUp(self) -> None:
        self.app = create_app(config_module_class='metadata_service.config.LocalConfig')
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

    def to_class(self, entity: Dict) -> Any:
        class ObjectView(object):
            def __init__(self, dictionary: Dict):
                self.__dict__ = dictionary

        return ObjectView(entity)

    def _mock_get_table_entity(self, entity: Optional[Any] = None) -> Any:
        entity = cast(dict, entity or self.entity1)
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def _mock_get_bookmark_entity(self, entity: Optional[Any] = None) -> Any:
        entity = entity or self.entity1
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        self.proxy._get_bookmark_entity = MagicMock(return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def test_extract_table_uri_info(self) -> None:
        table_info = self.proxy._extract_info_from_uri(table_uri=self.table_uri)
        self.assertDictEqual(table_info, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        })

    def test_get_ids_from_basic_search(self) -> None:
        entity1 = MagicMock()
        entity1.guid = self.entity1['guid']

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity1, entity2]

        self.proxy._driver.search_basic = MagicMock(return_value=[basic_search_response])
        response = self.proxy._get_ids_from_basic_search(params={})
        expected = ['1', '2']
        self.assertListEqual(response, expected)

    def test_get_table_entity(self) -> None:
        unique_attr_response = MagicMock()

        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        ent = self.proxy._get_table_entity(table_uri=self.table_uri)

        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def _get_table(self, custom_stats_format: bool = False) -> None:
        if custom_stats_format:
            test_exp_col = self.test_exp_col_stats_formatted
        else:
            test_exp_col = self.test_exp_col_stats_raw

        self._mock_get_table_entity()
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0]['typeName']
        ent_attrs = cast(dict, self.entity1['attributes'])

        col_attrs = cast(dict, self.test_column['attributes'])
        exp_col_stats = list()

        for stats in test_exp_col:
            exp_col_stats.append(
                Statistics(
                    stat_type=stats['attributes']['stat_name'],
                    stat_val=stats['attributes']['stat_val'],
                    start_epoch=stats['attributes']['start_epoch'],
                    end_epoch=stats['attributes']['end_epoch'],
                )
            )

        exp_col = Column(name=col_attrs['name'],
                         description='column description',
                         col_type='Managed',
                         sort_order=col_attrs['position'],
                         stats=exp_col_stats)
        expected = Table(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent_attrs['name'],
                         tags=[Tag(tag_name=classif_name, tag_type="default")],
                         description=ent_attrs['description'],
                         owners=[User(email=ent_attrs['owner'])],
                         last_updated_timestamp=int(str(self.entity1['updateTime'])[:10]),
                         columns=[exp_col] * self.active_columns)

        self.assertEqual(str(expected), str(response))

    def test_get_table_without_custom_stats_format(self) -> None:
        self._get_table()

    def test_get_table_with_custom_stats_format(self) -> None:
        statistics_format_spec = {'min': {'new_name': 'minimum', 'format': '{:,.2f}'},
                                  'max': {'drop': True}}

        with patch.object(self.proxy, 'STATISTICS_FORMAT_SPEC', statistics_format_spec):
            self._get_table(custom_stats_format=True)

    def test_get_table_not_found(self) -> None:
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_unique_attribute = MagicMock(side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self) -> None:
        with self.assertRaises(BadRequest):
            local_entity = copy.deepcopy(self.entity1)
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy._driver.entity_unique_attribute = MagicMock(return_value=unique_attr_response)
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_popular_tables(self) -> None:
        ent1 = self.to_class(self.entity1)
        ent2 = self.to_class(self.entity2)

        table_collection = MagicMock()

        table_collection.entities = [ent1, ent2]

        result = MagicMock(return_value=table_collection)

        with patch.object(self.proxy._driver.search_basic, 'create', result):
            response = self.proxy.get_popular_tables(num_entries=2)

            ent1_attrs = cast(dict, self.entity1['attributes'])
            ent2_attrs = cast(dict, self.entity2['attributes'])

            expected = [
                PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                             name=ent1_attrs['name'], description=ent1_attrs['description']),
                PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                             name=ent2_attrs['name'], description=ent1_attrs['description']),
            ]

            self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_table_description(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        attributes = cast(dict, self.entity1['attributes'])
        self.assertEqual(response, attributes['description'])

    def test_put_table_description(self) -> None:
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self) -> None:
        tag_response = {
            'tagEntities': {
                'PII': 3,
                'NON_PII': 2
            }
        }

        mocked_metrics = MagicMock()
        mocked_metrics.tag = tag_response

        self.proxy._driver.admin_metrics = [mocked_metrics]

        response = self.proxy.get_tags()

        expected = [TagDetail(tag_name='PII', tag_count=3), TagDetail(tag_name='NON_PII', tag_count=2)]
        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_add_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()

        with patch.object(self.proxy._driver.entity_bulk_classification, 'create') as mock_execute:
            self.proxy.add_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with(
                data={'classification': {'typeName': tag}, 'entityGuids': [self.entity1['guid']]}
            )

    def test_delete_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()
        mocked_entity = MagicMock()
        self.proxy._driver.entity_guid = MagicMock(return_value=mocked_entity)

        with patch.object(mocked_entity.classifications(tag), 'delete') as mock_execute:
            self.proxy.delete_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with()

    def test_add_owner(self) -> None:
        owner = "OWNER"
        entity = self._mock_get_table_entity()
        with patch.object(entity, 'update') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with()

    def test_get_column(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=cast(dict, self.test_column['attributes'])['name'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self) -> None:
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_no_referred_entities(self) -> None:
        with self.assertRaises(NotFoundException):
            local_entity: Dict = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        response = self.proxy.get_column_description(
            table_uri=self.table_uri,
            column_name=attributes['name'])
        self.assertEqual(response, attributes.get('description'))

    def test_put_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        self.proxy.put_column_description(table_uri=self.table_uri,
                                          column_name=attributes['name'],
                                          description='DOESNT_MATTER')

    def test_get_table_by_user_relation(self) -> None:
        bookmark1 = copy.deepcopy(self.bookmark_entity1)
        bookmark1 = self.to_class(bookmark1)
        bookmark_collection = MagicMock()
        bookmark_collection.entities = [bookmark1]

        self.proxy._driver.search_basic.create = MagicMock(return_value=bookmark_collection)
        res = self.proxy.get_table_by_user_relation(user_email='test_user_id',
                                                    relation_type=UserResourceRel.follow)

        expected = [PopularTable(database=Data.entity_type, cluster=Data.cluster, schema=Data.db,
                                 name=Data.name, description=None)]

        self.assertEqual(res, {'table': expected})

    def test_add_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()
        with patch.object(bookmark_entity, 'update') as mock_execute:
            self.proxy.add_resource_relation_by_user(id=self.table_uri,
                                                     user_id="test_user_id",
                                                     relation_type=UserResourceRel.follow,
                                                     resource_type=ResourceType.Table)
            mock_execute.assert_called_with()

    def test_delete_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()
        with patch.object(bookmark_entity, 'update') as mock_execute:
            self.proxy.delete_resource_relation_by_user(id=self.table_uri,
                                                        user_id="test_user_id",
                                                        relation_type=UserResourceRel.follow,
                                                        resource_type=ResourceType.Table)
            mock_execute.assert_called_with()
Exemple #4
0
class TestAtlasProxy(unittest.TestCase, Data):
    def setUp(self) -> None:
        self.app = create_app(
            config_module_class='metadata_service.config.LocalConfig')
        self.app.config['PROGRAMMATIC_DESCRIPTIONS_EXCLUDE_FILTERS'] = [
            'spark.*'
        ]
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

    def to_class(self, entity: Dict) -> Any:
        class ObjectView(object):
            def __init__(self, dictionary: Dict):
                self.__dict__ = dictionary

        return ObjectView(entity)

    def _mock_get_table_entity(self, entity: Optional[Any] = None) -> Any:
        entity = cast(dict, entity or self.entity1)
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(
            return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def _mock_get_bookmark_entity(self, entity: Optional[Any] = None) -> Any:
        entity = entity or self.entity1
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        self.proxy._get_bookmark_entity = MagicMock(
            return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def test_extract_table_uri_info(self) -> None:
        table_info = self.proxy._extract_info_from_uri(
            table_uri=self.table_uri)
        self.assertDictEqual(
            table_info, {
                'entity': self.entity_type,
                'cluster': self.cluster,
                'db': self.db,
                'name': self.name
            })

    def test_get_ids_from_basic_search(self) -> None:
        entity1 = MagicMock()
        entity1.guid = self.entity1['guid']

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity1, entity2]

        self.proxy._driver.search_basic = MagicMock(
            return_value=[basic_search_response])
        response = self.proxy._get_ids_from_basic_search(params={})
        expected = ['1', '2']
        self.assertListEqual(response, expected)

    def test_get_table_entity(self) -> None:
        unique_attr_response = MagicMock()

        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        ent = self.proxy._get_table_entity(table_uri=self.table_uri)

        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def _create_mocked_report_entities_collection(self) -> None:
        mocked_report_entities_collection = MagicMock()
        mocked_report_entities_collection.entities = []
        for entity in self.report_entities:
            mocked_report_entity = MagicMock()
            mocked_report_entity.status = entity['status']
            mocked_report_entity.attributes = entity['attributes']
            mocked_report_entities_collection.entities.append(
                mocked_report_entity)

        self.report_entity_collection = [mocked_report_entities_collection]

    def _get_table(self, custom_stats_format: bool = False) -> None:
        if custom_stats_format:
            test_exp_col = self.test_exp_col_stats_formatted
        else:
            test_exp_col = self.test_exp_col_stats_raw
        ent_attrs = cast(dict, self.entity1['attributes'])
        self._mock_get_table_entity()
        self._create_mocked_report_entities_collection()
        self.proxy._get_owners = MagicMock(
            return_value=[User(email=ent_attrs['owner'])])  # type: ignore
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=self.report_entity_collection)
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0][
            'typeName']

        col_attrs = cast(dict, self.test_column['attributes'])
        exp_col_stats = list()

        for stats in test_exp_col:
            exp_col_stats.append(
                Stat(
                    stat_type=stats['attributes']['stat_name'],
                    stat_val=stats['attributes']['stat_val'],
                    start_epoch=stats['attributes']['start_epoch'],
                    end_epoch=stats['attributes']['end_epoch'],
                ))

        exp_col = Column(name=col_attrs['name'],
                         description='column description',
                         col_type='Managed',
                         sort_order=col_attrs['position'],
                         stats=exp_col_stats)
        expected = Table(
            database=self.entity_type,
            cluster=self.cluster,
            schema=self.db,
            name=ent_attrs['name'],
            tags=[Tag(tag_name=classif_name, tag_type="default")],
            description=ent_attrs['description'],
            owners=[User(email=ent_attrs['owner'])],
            resource_reports=[
                ResourceReport(name='test_report', url='http://test'),
                ResourceReport(name='test_report3', url='http://test3')
            ],
            last_updated_timestamp=int(str(self.entity1['updateTime'])[:10]),
            columns=[exp_col] * self.active_columns,
            programmatic_descriptions=[
                ProgrammaticDescription(source='test parameter key a',
                                        text='testParameterValueA'),
                ProgrammaticDescription(source='test parameter key b',
                                        text='testParameterValueB')
            ],
            is_view=False)

        self.assertEqual(str(expected), str(response))

    def test_get_table_without_custom_stats_format(self) -> None:
        self._get_table()

    def test_get_table_with_custom_stats_format(self) -> None:
        statistics_format_spec = {
            'min': {
                'new_name': 'minimum',
                'format': '{:,.2f}'
            },
            'max': {
                'drop': True
            }
        }

        with patch.object(self.proxy, 'STATISTICS_FORMAT_SPEC',
                          statistics_format_spec):
            self._get_table(custom_stats_format=True)

    def test_get_table_not_found(self) -> None:
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_unique_attribute = MagicMock(
                side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self) -> None:
        with self.assertRaises(BadRequest):
            local_entity = copy.deepcopy(self.entity1)
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy._driver.entity_unique_attribute = MagicMock(
                return_value=unique_attr_response)
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_popular_tables(self) -> None:
        ent1 = self.to_class(self.entity1)
        ent2 = self.to_class(self.entity2)

        table_collection = MagicMock()

        table_collection.entities = [ent1, ent2]

        result = MagicMock(return_value=table_collection)

        with patch.object(self.proxy._driver.search_basic, 'create', result):
            response = self.proxy.get_popular_tables(num_entries=2)

            ent1_attrs = cast(dict, self.entity1['attributes'])
            ent2_attrs = cast(dict, self.entity2['attributes'])

            expected = [
                PopularTable(database=self.entity_type,
                             cluster=self.cluster,
                             schema=self.db,
                             name=ent1_attrs['name'],
                             description=ent1_attrs['description']),
                PopularTable(database=self.entity_type,
                             cluster=self.cluster,
                             schema=self.db,
                             name=ent2_attrs['name'],
                             description=ent1_attrs['description']),
            ]

            self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_table_description(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        attributes = cast(dict, self.entity1['attributes'])
        self.assertEqual(response, attributes['description'])

    def test_put_table_description(self) -> None:
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self) -> None:
        tag_response = {'tagEntities': {'PII': 3, 'NON_PII': 2}}

        mocked_metrics = MagicMock()
        mocked_metrics.tag = tag_response

        self.proxy._driver.admin_metrics = [mocked_metrics]

        response = self.proxy.get_tags()

        expected = [
            TagDetail(tag_name='PII', tag_count=3),
            TagDetail(tag_name='NON_PII', tag_count=2)
        ]
        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_add_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()

        with patch.object(self.proxy._driver.entity_bulk_classification,
                          'create') as mock_execute:
            self.proxy.add_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with(
                data={
                    'classification': {
                        'typeName': tag
                    },
                    'entityGuids': [self.entity1['guid']]
                })

    def test_delete_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()
        mocked_entity = MagicMock()
        self.proxy._driver.entity_guid = MagicMock(return_value=mocked_entity)

        with patch.object(mocked_entity.classifications(tag),
                          'delete') as mock_execute:
            self.proxy.delete_tag(id=self.table_uri,
                                  tag=tag,
                                  tag_type='default')
            mock_execute.assert_called_with()

    def test_add_owner(self) -> None:
        owner = "OWNER"
        user_guid = 123
        self._mock_get_table_entity()
        self.proxy._driver.entity_post = MagicMock()
        self.proxy._driver.entity_post.create = MagicMock(
            return_value={"guidAssignments": {
                user_guid: user_guid
            }})

        with patch.object(self.proxy._driver.relationship,
                          'create') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with(
                data={
                    'typeName': 'DataSet_Users_Owner',
                    'end1': {
                        'guid': self.entity1['guid'],
                        'typeName': 'Table'
                    },
                    'end2': {
                        'guid': user_guid,
                        'typeName': 'User'
                    }
                })

    def test_get_column(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=cast(dict, self.test_column['attributes'])['name'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self) -> None:
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_no_referred_entities(self) -> None:
        with self.assertRaises(NotFoundException):
            local_entity: Dict = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        response = self.proxy.get_column_description(
            table_uri=self.table_uri, column_name=attributes['name'])
        self.assertEqual(response, attributes.get('description'))

    def test_put_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        self.proxy.put_column_description(table_uri=self.table_uri,
                                          column_name=attributes['name'],
                                          description='DOESNT_MATTER')

    def test_get_table_by_user_relation_follow(self) -> None:
        bookmark1 = copy.deepcopy(self.bookmark_entity1)
        bookmark1 = self.to_class(bookmark1)
        bookmark_collection = MagicMock()
        bookmark_collection.entities = [bookmark1]

        self.proxy._driver.search_basic.create = MagicMock(
            return_value=bookmark_collection)
        res = self.proxy.get_table_by_user_relation(
            user_email='test_user_id', relation_type=UserResourceRel.follow)

        expected = [
            PopularTable(database=Data.entity_type,
                         cluster=Data.cluster,
                         schema=Data.db,
                         name=Data.name,
                         description=None)
        ]

        self.assertEqual(res, {'table': expected})

    def test_get_table_by_user_relation_own(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[entity_bulk_result])

        res = self.proxy.get_table_by_user_relation(
            user_email='test_user_id', relation_type=UserResourceRel.own)

        self.assertEqual(len(res.get("table")), 1)  # type: ignore

        ent1_attrs = cast(dict, self.entity1['attributes'])

        expected = [
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent1_attrs['name'],
                         description=ent1_attrs['description'])
        ]

        self.assertEqual({'table': expected}, res)

    def test_get_resources_owned_by_user_success(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[entity_bulk_result])

        res = self.proxy._get_resources_owned_by_user(
            user_id='test_user_2', resource_type=ResourceType.Table.name)

        self.assertEqual(len(res), 1)

        ent1_attrs = cast(dict, self.entity1['attributes'])

        expected = [
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent1_attrs['name'],
                         description=ent1_attrs['description'])
        ]

        self.assertEqual(expected, res)

    def test_get_resources_owned_by_user_no_user(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = None
        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        with self.assertRaises(NotFoundException):
            self.proxy._get_resources_owned_by_user(
                user_id='test_user_2', resource_type=ResourceType.Table.name)

    def test_get_resources_owned_by_user_default_owner(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)

        basic_search_result = MagicMock()
        basic_search_result.entities = self.reader_entities

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity2]

        self.proxy._driver.search_basic.create = MagicMock(
            return_value=basic_search_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[entity_bulk_result])

        res = self.proxy._get_resources_owned_by_user(
            user_id='test_user_2', resource_type=ResourceType.Table.name)

        self.assertEqual(len(res), 1)

    def test_add_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()
        with patch.object(bookmark_entity, 'update') as mock_execute:
            self.proxy.add_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=ResourceType.Table)
            mock_execute.assert_called_with()

    def test_delete_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()
        with patch.object(bookmark_entity, 'update') as mock_execute:
            self.proxy.delete_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=ResourceType.Table)
            mock_execute.assert_called_with()

    def test_get_readers(self) -> None:
        basic_search_result = MagicMock()
        basic_search_result.entities = self.reader_entities

        self.proxy._driver.search_basic.create = MagicMock(
            return_value=basic_search_result)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = self.reader_entities
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[entity_bulk_result])

        res = self.proxy._get_readers('dummy', 1)

        expected: List[Reader] = []

        expected += [
            Reader(user=User(email='test_user_1', user_id='test_user_1'),
                   read_count=5)
        ]
        expected += [
            Reader(user=User(email='test_user_2', user_id='test_user_2'),
                   read_count=150)
        ]

        self.assertEqual(res, expected)

    def test_get_frequently_used_tables(self) -> None:
        entity_unique_attribute_result = MagicMock()
        entity_unique_attribute_result.entity = DottedDict(self.user_entity_2)
        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=entity_unique_attribute_result)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.reader_entity_1)]
        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[entity_bulk_result])

        expected = {
            'table': [
                PopularTable(cluster=self.cluster,
                             name='Table1',
                             schema=self.db,
                             database=self.entity_type)
            ]
        }

        res = self.proxy.get_frequently_used_tables(user_email='dummy')

        self.assertEqual(expected, res)

    def test_get_latest_updated_ts_when_exists(self) -> None:
        with patch.object(self.proxy._driver, 'admin_metrics',
                          self.metrics_data):
            result = self.proxy.get_latest_updated_ts()

            assert result == 1598342400

    def test_get_latest_updated_ts_when_not_exists(self) -> None:
        with patch.object(self.proxy._driver, 'admin_metrics', []):
            result = self.proxy.get_latest_updated_ts()

            assert result == 0

    def test_get_user_detail_default(self) -> None:
        user_id = "*****@*****.**"
        user_details = self.proxy._get_user_details(user_id=user_id)
        self.assertDictEqual(user_details, {
            'email': user_id,
            'user_id': user_id
        })

    def test_get_user_detail_config_method(self) -> None:
        user_id = "*****@*****.**"
        response = {
            'email': user_id,
            'user_id': user_id,
            'first_name': 'First',
            'last_name': 'Last'
        }

        def custom_function(id: str) -> Dict[str, Any]:
            return response

        self.app.config['USER_DETAIL_METHOD'] = custom_function

        user_details = self.proxy._get_user_details(user_id=user_id)
        self.assertDictEqual(user_details, response)
        self.app.config['USER_DETAIL_METHOD'] = None

    def test_get_owners_details_no_owner_no_fallback(self) -> None:
        res = self.proxy._get_owners(data_owners=list(), fallback_owner=None)
        self.assertEqual(len(res), 0)

    def test_get_owners_details_only_fallback(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        user_id = "*****@*****.**"
        res = self.proxy._get_owners(data_owners=list(),
                                     fallback_owner=user_id)
        self.assertEqual(1, len(res))
        self.assertListEqual(res,
                             [User(**{
                                 'email': user_id,
                                 'user_id': user_id
                             })])

    def test_get_owners_details_only_active(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        # pass both active and inactive as parameter
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners)
        # _get_owners should return only active
        self.assertEqual(1, len(res))
        self.assertEqual(res[0].user_id, 'active_owned_by')

    def test_get_owners_details_owner_and_fallback(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        user_id = "*****@*****.**"

        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        # pass both active and inactive as parameter
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners,
                                     fallback_owner=user_id)
        # _get_owners should return only active AND the fallback_owner
        self.assertEqual(2, len(res))
        self.assertEqual(res[1].user_id, user_id)

    def test_get_owners_details_owner_and_fallback_duplicates(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        user_id = data_owners[0]["displayText"]
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners,
                                     fallback_owner=user_id)
        # _get_owners should return only active AND the fallback_owner,
        # but in case where it is duplicate, should return only 1
        self.assertEqual(1, len(res))

    def test_get_table_watermarks(self) -> None:
        params = [(['%Y%m%d'], 2, '2020-09'), (['%Y,%m'], 2, '2020-08'),
                  (['%Y-%m-%d'], 0, None), ([], 0, None)]

        mocked_partition_entities_collection = MagicMock()
        mocked_partition_entities_collection.entities = []

        for entity in self.partitions:
            mocked_report_entity = MagicMock()
            mocked_report_entity.status = entity['status']
            mocked_report_entity.attributes = entity['attributes']
            mocked_report_entity.createTime = entity['createTime']
            mocked_partition_entities_collection.entities.append(
                mocked_report_entity)

        for supported_formats, expected_result_length, low_date_prefix in params:
            with self.subTest():
                self.app.config['WATERMARK_DATE_FORMATS'] = supported_formats
                self.proxy._driver.entity_bulk = MagicMock(
                    return_value=[mocked_partition_entities_collection])

                result = self.proxy._get_table_watermarks(
                    cast(dict, self.entity1))

                assert len(result) == expected_result_length

                if low_date_prefix:
                    low, _ = result

                    assert low.partition_value.startswith(low_date_prefix)
Exemple #5
0
class TestAtlasProxy(unittest.TestCase, Data):
    def setUp(self) -> None:
        self.app = create_app(config_module_class='metadata_service.config.LocalConfig')
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

    def to_class(self, entity: Dict) -> Any:
        class ObjectView(object):
            def __init__(self, dictionary: Dict):
                self.__dict__ = dictionary

        return ObjectView(entity)

    def _mock_get_table_entity(self, entity: Optional[Any] = None) -> Any:
        entity = cast(dict, entity or self.entity1)
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column,
                self.column_metadata_entity['guid']: self.column_metadata_entity
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(return_value=(mocked_entity, {    # type: ignore
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': entity['attributes']['name']
        }))
        return mocked_entity

    def _mock_get_reader_entity(self, entity: Optional[Any] = None) -> Any:
        entity = entity or self.entity1
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        self.proxy._get_reader_entity = MagicMock(return_value=mocked_entity)    # type: ignore
        return mocked_entity

    def test_extract_table_uri_info(self) -> None:
        table_info = self.proxy._extract_info_from_uri(table_uri=self.table_uri)
        self.assertDictEqual(table_info, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        })

    def test_get_ids_from_basic_search(self) -> None:
        entity1 = MagicMock()
        entity1.guid = self.entity1['guid']

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity1, entity2]

        self.proxy._driver.search_basic = MagicMock(return_value=[basic_search_response])
        response = self.proxy._get_ids_from_basic_search(params={})
        expected = ['1', '2']
        self.assertListEqual(response, expected)

    def test_get_table_entity(self) -> None:
        unique_attr_response = MagicMock()

        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        ent, table_info = self.proxy._get_table_entity(table_uri=self.table_uri)
        self.assertDictEqual(table_info, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        })
        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def test_get_table(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0]['typeName']
        ent_attrs = cast(dict, self.entity1['attributes'])

        col_attrs = cast(dict, self.test_column['attributes'])
        col_metadata_attrs = cast(dict, self.column_metadata_entity['attributes'])
        exp_col_stats = list()

        for stats in col_metadata_attrs['statistics']:
            exp_col_stats.append(
                Statistics(
                    stat_type=stats['attributes']['stat_name'],
                    stat_val=stats['attributes']['stat_val'],
                    start_epoch=stats['attributes']['start_epoch'],
                    end_epoch=stats['attributes']['end_epoch'],
                )
            )
        exp_col = Column(name=col_attrs['name'],
                         description='column description',
                         col_type='Managed',
                         sort_order=col_attrs['position'],
                         stats=exp_col_stats)
        expected = Table(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent_attrs['name'],
                         tags=[Tag(tag_name=classif_name, tag_type="default")],
                         description=ent_attrs['description'],
                         owners=[User(email=ent_attrs['owner'])],
                         columns=[exp_col],
                         last_updated_timestamp=cast(int, self.entity1['updateTime']))
        self.assertEqual(str(expected), str(response))

    def test_get_table_not_found(self) -> None:
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_unique_attribute = MagicMock(side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self) -> None:
        with self.assertRaises(BadRequest):
            local_entity = copy.deepcopy(self.entity1)
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy._driver.entity_unique_attribute = MagicMock(return_value=unique_attr_response)
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_popular_tables(self) -> None:
        meta1: Dict = copy.deepcopy(self.metadata1)
        meta2: Dict = copy.deepcopy(self.metadata2)

        meta1['attributes']['table'] = self.entity1
        meta2['attributes']['table'] = self.entity2

        metadata1 = self.to_class(meta1)
        metadata2 = self.to_class(meta2)

        metadata_collection = MagicMock()
        metadata_collection.entities = [metadata1, metadata2]

        result = MagicMock(return_value=metadata_collection)

        with patch.object(self.proxy._driver.search_basic, 'create', result):
            entities_collection = MagicMock()
            entities_collection.entities = [self.to_class(self.entity1), self.to_class(self.entity2)]

            self.proxy._driver.entity_bulk = MagicMock(return_value=[entities_collection])

            response = self.proxy.get_popular_tables(num_entries=2)

            # Call multiple times for cache test.
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)

            self.assertEqual(self.proxy._driver.entity_bulk.call_count, 1)

            ent1_attrs = cast(dict, self.entity1['attributes'])
            ent2_attrs = cast(dict, self.entity2['attributes'])

            expected = [
                PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                             name=ent1_attrs['name'], description=ent1_attrs['description']),
                PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                             name=ent2_attrs['name'], description=ent1_attrs['description']),
            ]

            self.assertEqual(expected.__repr__(), response.__repr__())

    # noinspection PyTypeChecker
    def test_get_popular_tables_without_db(self) -> None:
        meta1: Dict = copy.deepcopy(self.metadata1)
        meta2: Dict = copy.deepcopy(self.metadata2)

        meta1['attributes']['table'] = self.entity1
        meta2['attributes']['table'] = self.entity2

        metadata1 = self.to_class(meta1)
        metadata2 = self.to_class(meta2)

        metadata_collection = MagicMock()
        metadata_collection.entities = [metadata1, metadata2]

        result = MagicMock(return_value=metadata_collection)

        with patch.object(self.proxy._driver.search_basic, 'create', result):
            entity1: Dict = copy.deepcopy(self.entity1)
            entity2: Dict = copy.deepcopy(self.entity2)

            for entity in [entity1, entity2]:
                entity['attributes']['qualifiedName'] = entity['attributes']['name']

            entities_collection = MagicMock()
            entities_collection.entities = [self.to_class(entity1), self.to_class(entity2)]

            # Invalidate the cache to test the cache functionality
            popular_query_params = {'typeName': 'table_metadata',
                                    'sortBy': 'popularityScore',
                                    'sortOrder': 'DESCENDING',
                                    'excludeDeletedEntities': True,
                                    'limit': 2,
                                    'attributes': ['table']}
            self.proxy._CACHE.region_invalidate(self.proxy._get_metadata_entities,
                                                None, '_get_metadata_entities',
                                                popular_query_params)

            self.proxy._driver.entity_bulk = MagicMock(return_value=[entities_collection])
            response = self.proxy.get_popular_tables(num_entries=2)

            # Call multiple times for cache test.
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)
            self.proxy.get_popular_tables(num_entries=2)

            self.assertEqual(1, self.proxy._driver.entity_bulk.call_count)

            ent1_attrs = cast(dict, self.entity1['attributes'])
            ent2_attrs = cast(dict, self.entity2['attributes'])

            expected = [
                PopularTable(database=self.entity_type, cluster='default', schema='default',
                             name=ent1_attrs['name'], description=ent1_attrs['description']),
                PopularTable(database=self.entity_type, cluster='default', schema='default',
                             name=ent2_attrs['name'], description=ent1_attrs['description']),
            ]

            self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_popular_tables_search_exception(self) -> None:
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_bulk = MagicMock(return_value=None)
            self.proxy._get_metadata_entities({'query': 'test'})

    def test_get_table_description(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        attributes = cast(dict, self.entity1['attributes'])
        self.assertEqual(response, attributes['description'])

    def test_put_table_description(self) -> None:
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self) -> None:
        tag_response = {
            'tagEntities': {
                'PII': 3,
                'NON_PII': 2
            }
        }

        mocked_metrics = MagicMock()
        mocked_metrics.tag = tag_response

        self.proxy._driver.admin_metrics = [mocked_metrics]

        response = self.proxy.get_tags()

        expected = [TagDetail(tag_name='PII', tag_count=3), TagDetail(tag_name='NON_PII', tag_count=2)]
        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_add_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()

        with patch.object(self.proxy._driver.entity_bulk_classification, 'create') as mock_execute:
            self.proxy.add_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with(
                data={'classification': {'typeName': tag}, 'entityGuids': [self.entity1['guid']]}
            )

    def test_delete_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()
        mocked_entity = MagicMock()
        self.proxy._driver.entity_guid = MagicMock(return_value=mocked_entity)

        with patch.object(mocked_entity.classifications(tag), 'delete') as mock_execute:
            self.proxy.delete_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with()

    def test_add_owner(self) -> None:
        owner = "OWNER"
        entity = self._mock_get_table_entity()
        with patch.object(entity, 'update') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with()

    def test_get_column(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=cast(dict, self.test_column['attributes'])['name'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self) -> None:
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_no_referred_entities(self) -> None:
        with self.assertRaises(NotFoundException):
            local_entity: Dict = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        response = self.proxy.get_column_description(
            table_uri=self.table_uri,
            column_name=attributes['name'])
        self.assertEqual(response, attributes.get('description'))

    def test_put_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        self.proxy.put_column_description(table_uri=self.table_uri,
                                          column_name=attributes['name'],
                                          description='DOESNT_MATTER')

    def test_get_table_by_user_relation(self) -> None:
        reader1 = copy.deepcopy(self.reader_entity1)
        reader1 = self.to_class(reader1)
        reader_collection = MagicMock()
        reader_collection.entities = [reader1]

        self.proxy._driver.search_basic.create = MagicMock(return_value=reader_collection)
        res = self.proxy.get_table_by_user_relation(user_email='test_user_id',
                                                    relation_type=UserResourceRel.follow)

        expected = [PopularTable(database=Data.entity_type, cluster=Data.cluster, schema=Data.db,
                                 name=Data.name, description=None)]

        self.assertEqual(res, {'table': expected})

    def test_add_resource_relation_by_user(self) -> None:
        reader_entity = self._mock_get_reader_entity()
        with patch.object(reader_entity, 'update') as mock_execute:
            self.proxy.add_table_relation_by_user(table_uri=self.table_uri,
                                                  user_email="test_user_id",
                                                  relation_type=UserResourceRel.follow)
            mock_execute.assert_called_with()

    def test_delete_resource_relation_by_user(self) -> None:
        reader_entity = self._mock_get_reader_entity()
        with patch.object(reader_entity, 'update') as mock_execute:
            self.proxy.delete_table_relation_by_user(table_uri=self.table_uri,
                                                     user_email="test_user_id",
                                                     relation_type=UserResourceRel.follow)
            mock_execute.assert_called_with()
    def setUp(self):
        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

        self.entity_type = 'TEST_ENTITY'
        self.cluster = 'TEST_CLUSTER'
        self.db = 'TEST_DB'
        self.name = 'TEST_TABLE'
        self.table_uri = f'{self.entity_type}://{self.cluster}.{self.db}/{self.name}'

        self.classification_entity = {
            'classifications': [
                {
                    'typeName': 'PII_DATA',
                    'name': 'PII_DATA'
                },
            ]
        }

        self.test_column = {
            'guid': 'DOESNT_MATTER',
            'typeName': 'COLUMN',
            'attributes': {
                'qualifiedName': 'column@name',
                'type': 'Managed',
                'description': 'column description',
                'position': 1
            }
        }
        self.entity1 = {
            'guid': '1',
            'typeName': self.entity_type,
            'updateTime': 123,
            'attributes': {
                'qualifiedName': 'Table1_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'columns': [self.test_column],
                'db': {
                    'guid': '-100',
                    'qualifiedName': self.db,
                    'typeName': self.entity_type,
                }
            }
        }
        self.entity1.update(self.classification_entity)

        self.entity2 = {
            'guid': '2',
            'updateTime': 234,
            'typeName': self.entity_type,
            'attributes': {
                'qualifiedName': 'Table2_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'db': {
                    'guid': '-100',
                    'qualifiedName': self.db,
                    'typeName': self.entity_type,
                }
            }
        }
        self.entity2.update(self.classification_entity)
        self.entities = {
            'entities': [
                self.entity1,
                self.entity2,
            ]
        }
class TestAtlasProxy(unittest.TestCase):
    def setUp(self):
        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

        self.entity_type = 'TEST_ENTITY'
        self.cluster = 'TEST_CLUSTER'
        self.db = 'TEST_DB'
        self.name = 'TEST_TABLE'
        self.table_uri = f'{self.entity_type}://{self.cluster}.{self.db}/{self.name}'

        self.classification_entity = {
            'classifications': [
                {
                    'typeName': 'PII_DATA',
                    'name': 'PII_DATA'
                },
            ]
        }

        self.test_column = {
            'guid': 'DOESNT_MATTER',
            'typeName': 'COLUMN',
            'attributes': {
                'qualifiedName': 'column@name',
                'type': 'Managed',
                'description': 'column description',
                'position': 1
            }
        }
        self.entity1 = {
            'guid': '1',
            'typeName': self.entity_type,
            'updateTime': 123,
            'attributes': {
                'qualifiedName': 'Table1_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'columns': [self.test_column],
                'db': {
                    'guid': '-100',
                    'qualifiedName': self.db,
                    'typeName': self.entity_type,
                }
            }
        }
        self.entity1.update(self.classification_entity)

        self.entity2 = {
            'guid': '2',
            'updateTime': 234,
            'typeName': self.entity_type,
            'attributes': {
                'qualifiedName': 'Table2_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'db': {
                    'guid': '-100',
                    'qualifiedName': self.db,
                    'typeName': self.entity_type,
                }
            }
        }
        self.entity2.update(self.classification_entity)
        self.entities = {
            'entities': [
                self.entity1,
                self.entity2,
            ]
        }

    def _mock_get_table_entity(self, entity=None):
        mocked_entity = MagicMock()
        mocked_entity.entity = entity or self.entity1
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(
            return_value=(mocked_entity, {
                'entity': self.entity_type,
                'cluster': self.cluster,
                'db': self.db,
                'name': self.name
            }))
        return mocked_entity

    def test_extract_table_uri_info(self):
        table_info = self.proxy._extract_info_from_uri(
            table_uri=self.table_uri)
        self.assertDictEqual(
            table_info, {
                'entity': self.entity_type,
                'cluster': self.cluster,
                'db': self.db,
                'name': self.name
            })

    def test_get_ids_from_basic_search(self):
        entity1 = MagicMock()
        entity1.guid = self.entity1['guid']

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity1, entity2]

        self.proxy._driver.search_basic = MagicMock(
            return_value=[basic_search_response])
        response = self.proxy._get_ids_from_basic_search(params={})
        expected = ['1', '2']
        self.assertListEqual(response, expected)

    def test_get_table_entity(self):
        unique_attr_response = MagicMock()

        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        ent, table_info = self.proxy._get_table_entity(
            table_uri=self.table_uri)
        self.assertDictEqual(
            table_info, {
                'entity': self.entity_type,
                'cluster': self.cluster,
                'db': self.db,
                'name': self.name
            })
        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def test_get_table(self):
        self._mock_get_table_entity()
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0][
            'typeName']
        ent_attrs = self.entity1['attributes']

        col_attrs = self.test_column['attributes']
        exp_col = Column(name=col_attrs['qualifiedName'],
                         description='column description',
                         col_type='Managed',
                         sort_order=col_attrs['position'])
        expected = Table(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=self.name,
                         tags=[Tag(tag_name=classif_name, tag_type="default")],
                         description=ent_attrs['description'],
                         owners=[User(email=ent_attrs['owner'])],
                         columns=[exp_col],
                         last_updated_timestamp=self.entity1['updateTime'])
        self.assertEqual(str(expected), str(response))

    def test_get_table_not_found(self):
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_unique_attribute = MagicMock(
                side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self):
        with self.assertRaises(BadRequest):
            local_entity = self.entity1
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy._driver.entity_unique_attribute = MagicMock(
                return_value=unique_attr_response)
            self.proxy.get_table(table_uri=self.table_uri)

    @patch.object(AtlasProxy, '_get_ids_from_basic_search')
    def test_get_popular_tables(self, mock_basic_search):
        entity1 = MagicMock()
        entity1.typeName = self.entity1['typeName']
        entity1.attributes = self.entity1['attributes']

        entity2 = MagicMock()
        entity2.typeName = self.entity2['typeName']
        entity2.attributes = self.entity2['attributes']

        bulk_ent_collection = MagicMock()
        bulk_ent_collection.entities = [entity1, entity2]

        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[bulk_ent_collection])

        db_entity = MagicMock()
        db_entity.entity = {
            'attributes': {
                'qualifiedName': self.db,
                'clusterName': self.cluster
            }
        }

        self.proxy._driver.entity_guid = MagicMock(return_value=db_entity)

        response = self.proxy.get_popular_tables(num_entries=2)
        ent1_attrs = self.entity1['attributes']
        ent2_attrs = self.entity2['attributes']

        expected = [
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent1_attrs['qualifiedName'],
                         description=ent1_attrs['description']),
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent2_attrs['qualifiedName'],
                         description=ent1_attrs['description']),
        ]

        self.assertEqual(response.__repr__(), expected.__repr__())

    @patch.object(AtlasProxy, '_get_ids_from_basic_search')
    def test_get_popular_tables_without_db(self, mock_basic_search):
        attrs_ent1 = self.entity1['attributes']
        attrs_ent1.pop('db')
        entity1 = MagicMock()
        entity1.typeName = self.entity1['typeName']
        entity1.attributes = attrs_ent1

        attrs_ent2 = self.entity2['attributes']
        attrs_ent2.pop('db')
        entity2 = MagicMock()
        entity2.typeName = self.entity2['typeName']
        entity2.attributes = attrs_ent2

        bulk_ent_collection = MagicMock()
        bulk_ent_collection.entities = [entity1, entity2]

        self.proxy._driver.entity_bulk = MagicMock(
            return_value=[bulk_ent_collection])
        response = self.proxy.get_popular_tables(num_entries=2)

        ent1_attrs = self.entity1['attributes']
        ent2_attrs = self.entity2['attributes']

        expected = [
            PopularTable(database=self.entity_type,
                         cluster='',
                         schema='',
                         name=ent1_attrs['qualifiedName'],
                         description=ent1_attrs['description']),
            PopularTable(database=self.entity_type,
                         cluster='',
                         schema='',
                         name=ent2_attrs['qualifiedName'],
                         description=ent1_attrs['description']),
        ]

        self.assertEqual(response.__repr__(), expected.__repr__())

    def test_get_popular_tables_search_exception(self):
        with self.assertRaises(BadRequest):
            self.proxy._driver.search_basic = MagicMock(
                side_effect=BadRequest('Boom!'))
            self.proxy.get_popular_tables(num_entries=2)

    def test_get_table_description(self):
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        self.assertEqual(response, self.entity1['attributes']['description'])

    def test_put_table_description(self):
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self):
        name = "DUMMY_CLASSIFICATION"
        mocked_classif = MagicMock()
        mocked_classif.name = name

        mocked_def = MagicMock()
        mocked_def.classificationDefs = [mocked_classif]

        self.proxy._driver.typedefs = [mocked_def]

        response = self.proxy.get_tags()

        expected = [TagDetail(tag_name=name, tag_count=0)]
        self.assertEqual(response.__repr__(), expected.__repr__())

    def test_add_tag(self):
        tag = "TAG"
        self._mock_get_table_entity()

        with patch.object(self.proxy._driver.entity_bulk_classification,
                          'create') as mock_execute:
            self.proxy.add_tag(table_uri=self.table_uri, tag=tag)
            mock_execute.assert_called_with(
                data={
                    'classification': {
                        'typeName': tag
                    },
                    'entityGuids': [self.entity1['guid']]
                })

    def test_delete_tag(self):
        tag = "TAG"
        self._mock_get_table_entity()
        mocked_entity = MagicMock()
        self.proxy._driver.entity_guid = MagicMock(return_value=mocked_entity)

        with patch.object(mocked_entity.classifications(tag),
                          'delete') as mock_execute:
            self.proxy.delete_tag(table_uri=self.table_uri, tag=tag)
            mock_execute.assert_called_with()

    def test_add_owner(self):
        owner = "OWNER"
        entity = self._mock_get_table_entity()
        with patch.object(entity, 'update') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with()

    def test_get_column(self):
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=self.test_column['attributes']['qualifiedName'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self):
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_no_referred_entities(self):
        with self.assertRaises(NotFoundException):
            local_entity = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_description(self):
        self._mock_get_table_entity()
        response = self.proxy.get_column_description(
            table_uri=self.table_uri,
            column_name=self.test_column['attributes']['qualifiedName'])
        self.assertEqual(response,
                         self.test_column['attributes'].get('description'))

    def test_put_column_description(self):
        self._mock_get_table_entity()
        self.proxy.put_column_description(
            table_uri=self.table_uri,
            column_name=self.test_column['attributes']['qualifiedName'],
            description='DOESNT_MATTER')
    def setUp(self):
        self.app = create_app(
            config_module_class='metadata_service.config.LocalConfig')
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

        self.entity_type = 'TEST_ENTITY'
        self.cluster = 'TEST_CLUSTER'
        self.db = 'TEST_DB'
        self.name = 'TEST_TABLE'
        self.table_uri = f'{self.entity_type}://{self.cluster}.{self.db}/{self.name}'

        self.classification_entity = {
            'classifications': [
                {
                    'typeName': 'PII_DATA',
                    'name': 'PII_DATA'
                },
            ]
        }

        self.test_column = {
            'guid': 'DOESNT_MATTER',
            'typeName': 'COLUMN',
            'attributes': {
                'qualifiedName': 'column@name',
                'type': 'Managed',
                'description': 'column description',
                'position': 1
            }
        }

        self.db_entity = {
            'guid': '-100',
            'updateTime': 234,
            'typeName': self.entity_type,
            'attributes': {
                'qualifiedName': self.db,
                'name': 'self.db',
                'description': 'Dummy DB Description',
                'owner': '*****@*****.**',
            }
        }

        self.entity1 = {
            'guid': '1',
            'typeName': self.entity_type,
            'updateTime': 123,
            'attributes': {
                'qualifiedName': 'Table1_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'columns': [self.test_column],
                'db': self.db_entity
            },
            'relationshipAttributes': {
                'db': self.db_entity,
                'columns': [self.test_column],
            },
        }
        self.entity1.update(self.classification_entity)

        self.entity2 = {
            'guid': '2',
            'updateTime': 234,
            'typeName': self.entity_type,
            'attributes': {
                'qualifiedName': 'Table2_Qualified',
                'name': 'Table1',
                'description': 'Dummy Description',
                'owner': '*****@*****.**',
                'db': self.db_entity
            },
            'relationshipAttributes': {
                'db': self.db_entity,
            },
        }
        self.entity2.update(self.classification_entity)
        self.entities = {
            'entities': [
                self.entity1,
                self.entity2,
            ]
        }
class TestAtlasProxy(unittest.TestCase, Data):
    def setUp(self):
        self.app = create_app(config_module_class='metadata_service.config.LocalConfig')
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.Atlas'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy._driver = MagicMock()

    def to_class(self, entity):
        class ObjectView(object):
            def __init__(self, dictionary):
                self.__dict__ = dictionary

        return ObjectView(entity)

    def _mock_get_table_entity(self, entity=None):
        mocked_entity = MagicMock()
        mocked_entity.entity = entity or self.entity1
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(return_value=(mocked_entity, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        }))
        return mocked_entity

    def test_extract_table_uri_info(self):
        table_info = self.proxy._extract_info_from_uri(table_uri=self.table_uri)
        self.assertDictEqual(table_info, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        })

    def test_get_ids_from_basic_search(self):
        entity1 = MagicMock()
        entity1.guid = self.entity1['guid']

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity1, entity2]

        self.proxy._driver.search_basic = MagicMock(return_value=[basic_search_response])
        response = self.proxy._get_ids_from_basic_search(params={})
        expected = ['1', '2']
        self.assertListEqual(response, expected)

    def test_get_table_entity(self):
        unique_attr_response = MagicMock()

        self.proxy._driver.entity_unique_attribute = MagicMock(
            return_value=unique_attr_response)
        ent, table_info = self.proxy._get_table_entity(table_uri=self.table_uri)
        self.assertDictEqual(table_info, {
            'entity': self.entity_type,
            'cluster': self.cluster,
            'db': self.db,
            'name': self.name
        })
        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def test_get_table(self):
        self._mock_get_table_entity()
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0]['typeName']
        ent_attrs = self.entity1['attributes']

        col_attrs = self.test_column['attributes']
        exp_col_stats = list()

        for stats in col_attrs['stats']:
            exp_col_stats.append(
                Statistics(
                    stat_type=stats['attributes']['stat_name'],
                    stat_val=stats['attributes']['stat_val'],
                    start_epoch=stats['attributes']['start_epoch'],
                    end_epoch=stats['attributes']['end_epoch'],
                )
            )
        exp_col = Column(name=col_attrs['qualifiedName'],
                         description='column description',
                         col_type='Managed',
                         sort_order=col_attrs['position'],
                         stats=exp_col_stats)
        expected = Table(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=self.name,
                         tags=[Tag(tag_name=classif_name, tag_type="default")],
                         description=ent_attrs['description'],
                         owners=[User(email=ent_attrs['owner'])],
                         columns=[exp_col],
                         last_updated_timestamp=self.entity1['updateTime'])
        self.assertEqual(str(expected), str(response))

    def test_get_table_not_found(self):
        with self.assertRaises(NotFoundException):
            self.proxy._driver.entity_unique_attribute = MagicMock(side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self):
        with self.assertRaises(BadRequest):
            local_entity = self.entity1.copy()
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy._driver.entity_unique_attribute = MagicMock(return_value=unique_attr_response)
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_popular_tables(self):
        metadata1 = self.to_class(self.metadata1)
        metadata2 = self.to_class(self.metadata2)
        self.proxy._get_flat_values_from_dsl = MagicMock(return_value=[])

        metadata_collection = MagicMock()
        metadata_collection.entities_with_relationships = MagicMock(return_value=[metadata1, metadata2])

        self.proxy._driver.entity_bulk = MagicMock(return_value=[metadata_collection])
        response = self.proxy.get_popular_tables(num_entries=2)
        ent1_attrs = self.entity1['attributes']
        ent2_attrs = self.entity2['attributes']

        expected = [
            PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                         name=ent1_attrs['qualifiedName'], description=ent1_attrs['description']),
            PopularTable(database=self.entity_type, cluster=self.cluster, schema=self.db,
                         name=ent2_attrs['qualifiedName'], description=ent1_attrs['description']),
        ]

        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_popular_tables_without_db(self):
        meta1 = self.metadata1.copy()
        meta2 = self.metadata2.copy()

        for meta in [meta1, meta2]:
            meta['relationshipAttributes']['parentEntity']['attributes']['qualifiedName'] = 'meta@cluster'

        metadata1 = self.to_class(meta1)
        metadata2 = self.to_class(meta2)
        self.proxy._get_flat_values_from_dsl = MagicMock(return_value=[])

        metadata_collection = MagicMock()
        metadata_collection.entities_with_relationships = MagicMock(return_value=[metadata1, metadata2])

        self.proxy._driver.entity_bulk = MagicMock(return_value=[metadata_collection])
        response = self.proxy.get_popular_tables(num_entries=2)
        ent1_attrs = self.entity1['attributes']
        ent2_attrs = self.entity2['attributes']

        expected = [
            PopularTable(database=self.entity_type, cluster='', schema='',
                         name=ent1_attrs['qualifiedName'], description=ent1_attrs['description']),
            PopularTable(database=self.entity_type, cluster='', schema='',
                         name=ent2_attrs['qualifiedName'], description=ent1_attrs['description']),
        ]

        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_popular_tables_search_exception(self):
        with self.assertRaises(NotFoundException):
            self.proxy._get_flat_values_from_dsl = MagicMock(return_value=[])
            self.proxy._driver.entity_bulk = MagicMock(return_value=None)

            self.proxy.get_popular_tables(num_entries=2)

    def test_get_table_description(self):
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        self.assertEqual(response, self.entity1['attributes']['description'])

    def test_put_table_description(self):
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self):
        name = "DUMMY_CLASSIFICATION"
        mocked_classif = MagicMock()
        mocked_classif.name = name

        mocked_def = MagicMock()
        mocked_def.classificationDefs = [mocked_classif]

        self.proxy._driver.typedefs = [mocked_def]

        response = self.proxy.get_tags()

        expected = [TagDetail(tag_name=name, tag_count=0)]
        self.assertEqual(response.__repr__(), expected.__repr__())

    def test_add_tag(self):
        tag = "TAG"
        self._mock_get_table_entity()

        with patch.object(self.proxy._driver.entity_bulk_classification, 'create') as mock_execute:
            self.proxy.add_tag(table_uri=self.table_uri, tag=tag)
            mock_execute.assert_called_with(
                data={'classification': {'typeName': tag}, 'entityGuids': [self.entity1['guid']]}
            )

    def test_delete_tag(self):
        tag = "TAG"
        self._mock_get_table_entity()
        mocked_entity = MagicMock()
        self.proxy._driver.entity_guid = MagicMock(return_value=mocked_entity)

        with patch.object(mocked_entity.classifications(tag), 'delete') as mock_execute:
            self.proxy.delete_tag(table_uri=self.table_uri, tag=tag)
            mock_execute.assert_called_with()

    def test_add_owner(self):
        owner = "OWNER"
        entity = self._mock_get_table_entity()
        with patch.object(entity, 'update') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with()

    def test_get_column(self):
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=self.test_column['attributes']['qualifiedName'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self):
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_no_referred_entities(self):
        with self.assertRaises(NotFoundException):
            local_entity = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri, column_name='FAKE')

    def test_get_column_description(self):
        self._mock_get_table_entity()
        response = self.proxy.get_column_description(
            table_uri=self.table_uri,
            column_name=self.test_column['attributes']['qualifiedName'])
        self.assertEqual(response, self.test_column['attributes'].get('description'))

    def test_put_column_description(self):
        self._mock_get_table_entity()
        self.proxy.put_column_description(table_uri=self.table_uri,
                                          column_name=self.test_column['attributes']['qualifiedName'],
                                          description='DOESNT_MATTER')
Exemple #10
0
class TestAtlasProxy(unittest.TestCase, Data):
    def setUp(self) -> None:
        self.app = create_app(
            config_module_class='metadata_service.config.LocalConfig')
        self.app.config['PROGRAMMATIC_DESCRIPTIONS_EXCLUDE_FILTERS'] = [
            'spark.*'
        ]
        self.app.config['WATERMARK_DATE_FORMATS'] = ''
        self.app.config['POPULAR_RESOURCES_MINIMUM_READER_COUNT'] = 0
        self.app_context = self.app.app_context()
        self.app_context.push()

        with patch('metadata_service.proxy.atlas_proxy.AtlasClient'):
            # Importing here to make app context work before
            # importing `current_app` indirectly using the AtlasProxy
            from metadata_service.proxy.atlas_proxy import AtlasProxy
            self.proxy = AtlasProxy(host='DOES_NOT_MATTER', port=0000)
            self.proxy.client = MagicMock()

    def to_class(self, entity: Dict) -> Any:
        class ObjectView(object):
            def __init__(self, dictionary: Dict):
                self.__dict__ = dictionary

        return ObjectView(entity)

    def _mock_get_table_entity(self, entity: Optional[Any] = None) -> Any:
        entity = cast(dict, entity or self.entity1)
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        if mocked_entity.entity == self.entity1:
            mocked_entity.referredEntities = {
                self.test_column['guid']: self.test_column
            }
        else:
            mocked_entity.referredEntities = {}
        self.proxy._get_table_entity = MagicMock(
            return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def _mock_get_create_glossary_term(self,
                                       tag: str,
                                       assigned_ent: Optional[Any] = None,
                                       guid: str = None) -> Any:
        term = MagicMock()
        term.guid = guid or 123
        if assigned_ent:
            term.attributes = {"assignedEntities": [assigned_ent]}
        self.proxy._get_create_glossary_term = MagicMock(return_value=term)
        return term

    def _mock_get_bookmark_entity(self, entity: Optional[Any] = None) -> Any:
        entity = entity or self.entity1
        mocked_entity = MagicMock()
        mocked_entity.entity = entity
        self.proxy._get_bookmark_entity = MagicMock(
            return_value=mocked_entity)  # type: ignore
        return mocked_entity

    def test_get_table_entity(self) -> None:
        unique_attr_response = MagicMock()

        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)
        ent = self.proxy._get_table_entity(table_uri=self.table_uri)

        self.assertEqual(ent.__repr__(), unique_attr_response.__repr__())

    def _create_mocked_report_entities_collection(self) -> None:
        mocked_report_entities_collection = MagicMock()
        mocked_report_entities_collection.entities = []
        for entity in self.report_entities:
            mocked_report_entity = MagicMock()
            mocked_report_entity.status = entity['status']
            mocked_report_entity.attributes = entity['attributes']
            mocked_report_entities_collection.entities.append(
                mocked_report_entity)

        self.report_entity_collection = mocked_report_entities_collection

    def test_get_sorted_reports(self) -> None:
        self._create_mocked_report_entities_collection()
        self.report_entity_collection.entities.sort(
            key=lambda x: x.attributes['name'], reverse=True)
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=self.report_entity_collection)
        reports_guid = cast(dict, self.entity1)['attributes']['reports']
        sorted_reports = self.proxy._get_reports(reports_guid)
        expected = [
            ResourceReport(name="test_report", url="http://test"),
            ResourceReport(name="test_report3", url="http://test3")
        ]
        self.assertEqual(sorted_reports, expected)

    def _get_table(self, custom_stats_format: bool = False) -> None:
        if custom_stats_format:
            test_exp_col = self.test_exp_col_stats_formatted
        else:
            test_exp_col = self.test_exp_col_stats_raw
        ent_attrs = cast(dict, self.entity1['attributes'])
        self._mock_get_table_entity()
        self._create_mocked_report_entities_collection()
        self.proxy._get_owners = MagicMock(
            return_value=[User(email=ent_attrs['owner'])])  # type: ignore
        response = self.proxy.get_table(table_uri=self.table_uri)

        classif_name = self.classification_entity['classifications'][0][
            'typeName']

        col_attrs = cast(dict, self.test_column['attributes'])
        exp_col_stats = list()

        for stats in test_exp_col:
            exp_col_stats.append(
                Stat(
                    stat_type=stats['attributes']['stat_name'],
                    stat_val=stats['attributes']['stat_val'],
                    start_epoch=stats['attributes']['start_epoch'],
                    end_epoch=stats['attributes']['end_epoch'],
                ))

        exp_col = Column(
            name=col_attrs['name'],
            description='column description',
            col_type='Managed',
            sort_order=col_attrs['position'],
            stats=exp_col_stats,
            badges=[Badge(category='default', badge_name='active_col_badge')])

        expected = Table(
            database=self.entity_type,
            cluster=self.cluster,
            schema=self.db,
            name=ent_attrs['name'],
            tags=[],
            badges=[Badge(badge_name=classif_name, category="default")],
            description=ent_attrs['description'],
            owners=[User(email=ent_attrs['owner'])],
            resource_reports=[],
            last_updated_timestamp=int(str(self.entity1['updateTime'])[:10]),
            columns=[exp_col] * self.active_columns,
            watermarks=[],
            programmatic_descriptions=[
                ProgrammaticDescription(source='test parameter key a',
                                        text='testParameterValueA'),
                ProgrammaticDescription(source='test parameter key b',
                                        text='testParameterValueB')
            ],
            is_view=False)

        self.assertEqual(str(expected), str(response))

    def test_health_atlas(self) -> None:
        health_actual = self.proxy.health()
        expected_checks = {'AtlasProxy:connection': {'status': 'not checked'}}
        health_expected = health_check.HealthCheck(status='ok',
                                                   checks=expected_checks)
        self.assertEqual(health_actual.status, health_expected.status)
        self.assertDictEqual(health_actual.checks, health_expected.checks)

    def test_get_table_without_custom_stats_format(self) -> None:
        self._get_table()

    def test_get_table_with_custom_stats_format(self) -> None:
        statistics_format_spec = {
            'min': {
                'new_name': 'minimum',
                'format': '{:,.2f}'
            },
            'max': {
                'drop': True
            }
        }

        with patch.object(self.proxy, 'STATISTICS_FORMAT_SPEC',
                          statistics_format_spec):
            self._get_table(custom_stats_format=True)

    def test_get_table_not_found(self) -> None:
        with self.assertRaises(NotFoundException):
            self.proxy.client.entity.get_entity_by_attribute = MagicMock(
                side_effect=Exception('Boom!'))
            self.proxy.get_table(table_uri=self.table_uri)

    def test_get_table_missing_info(self) -> None:
        with self.assertRaises(BadRequest):
            local_entity = copy.deepcopy(self.entity1)
            local_entity.pop('attributes')
            unique_attr_response = MagicMock()
            unique_attr_response.entity = local_entity

            self.proxy.client.entity.get_entity_by_attribute = MagicMock(
                return_value=unique_attr_response)
            self.proxy.get_table(table_uri=cast(str, self.table_uri))

    def test_get_popular_tables(self) -> None:
        ent1 = self.to_class(self.entity1)
        ent2 = self.to_class(self.entity2)

        table_collection = MagicMock()

        table_collection.entities = [ent1, ent2]

        result = MagicMock(return_value=table_collection)

        with patch.object(self.proxy.client.discovery, 'faceted_search',
                          result):
            response = self.proxy.get_popular_tables(num_entries=2)

            ent1_attrs = cast(dict, self.entity1['attributes'])
            ent2_attrs = cast(dict, self.entity2['attributes'])

            expected = [
                PopularTable(database=self.entity_type,
                             cluster=self.cluster,
                             schema=self.db,
                             name=ent1_attrs['name'],
                             description=ent1_attrs['description']),
                PopularTable(database=self.entity_type,
                             cluster=self.cluster,
                             schema=self.db,
                             name=ent2_attrs['name'],
                             description=ent1_attrs['description']),
            ]

            self.assertEqual(expected.__repr__(), response.__repr__())

    def test_get_table_description(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy.get_table_description(table_uri=self.table_uri)
        attributes = cast(dict, self.entity1['attributes'])
        self.assertEqual(response, attributes['description'])

    def test_put_table_description(self) -> None:
        self._mock_get_table_entity()
        self.proxy.put_table_description(table_uri=self.table_uri,
                                         description="DOESNT_MATTER")

    def test_get_tags(self) -> None:
        mocked_term = MagicMock()
        mocked_term.attributes = {
            "name": "PII",
            "assignedEntities": ["a", "b"]
        }

        result = MagicMock()
        result.entities = [mocked_term]

        self.proxy.client.discovery.faceted_search = MagicMock(
            return_value=result)

        response = self.proxy.get_tags()

        expected = [TagDetail(tag_name='PII', tag_count=2)]
        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_add_tag(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()
        term = self._mock_get_create_glossary_term(tag)

        with patch.object(self.proxy.client.glossary,
                          'assign_term_to_entities') as mock_execute:
            self.proxy.add_tag(id=self.table_uri, tag=tag, tag_type='default')
            mock_execute.assert_called_with(term.guid, [
                AtlasRelatedObjectId({
                    'guid': self.entity1['guid'],
                    "typeName": "Table"
                })
            ])

    def test_delete_tag(self) -> None:
        tag = "TAG"
        ent = self._mock_get_table_entity()
        term = self._mock_get_create_glossary_term(tag, ent.entity)
        self.proxy.client.glossary.get_entities_assigned_with_term = MagicMock(
            return_value=[ent.entity])

        with patch.object(self.proxy.client.glossary,
                          'disassociate_term_from_entities') as mock_execute:
            self.proxy.delete_tag(id=self.table_uri,
                                  tag=tag,
                                  tag_type='default')
            mock_execute.assert_called_with(
                term.guid, [AtlasRelatedObjectId(self.entity1)])

    def test_delete_tag_no_term(self) -> None:
        tag = "TAG"
        self._mock_get_table_entity()
        self.proxy._get_create_glossary_term = MagicMock(return_value={})

        with patch.object(self.proxy.client.glossary,
                          'disassociate_term_from_entities') as mock_execute:
            self.proxy.delete_tag(id=self.table_uri,
                                  tag=tag,
                                  tag_type='default')
            mock_execute.assert_not_called()

    def test_get_badges(self) -> None:
        expected = [
            Badge(badge_name=item, category="default")
            for item in self.metrics_data['tag'].get("tagEntities").keys()
        ]

        self.proxy.client.admin.get_metrics = MagicMock(
            return_value=self.metrics_data)
        response = self.proxy.get_badges()
        self.assertEqual(expected.__repr__(), response.__repr__())

    def test_add_owner(self) -> None:
        owner = "OWNER"
        user_guid = 123
        self._mock_get_table_entity()
        mocked_user_entity = MagicMock()
        mocked_user_entity.guidAssignments = dict(user_guid=user_guid)
        self.proxy.client.entity.create_entity = MagicMock(
            return_value=mocked_user_entity)

        with patch.object(self.proxy.client.relationship,
                          'create_relationship') as mock_execute:
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with(relationship=type_coerce(
                {
                    'typeName': 'DataSet_Users_Owner',
                    'end1': {
                        'guid': self.entity1['guid'],
                        'typeName': 'Table'
                    },
                    'end2': {
                        'guid': user_guid,
                        'typeName': 'User'
                    }
                }, AtlasRelationship))

    def test_add_owner_no_user(self) -> None:
        owner = "OWNER"
        self.proxy._get_user_details = MagicMock(
            return_value={})  # type: ignore
        with self.assertRaises(NotFoundException):
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)

    def test_add_owner_existing_owner(self) -> None:
        owner = "OWNER"
        user_guid = 123
        self._mock_get_table_entity()
        mocked_user_entity = MagicMock()
        mocked_user_entity.guidAssignments = dict(user_guid=user_guid)
        self.proxy.client.entity.create_entity = MagicMock(
            return_value=mocked_user_entity)
        self.proxy.client.relationship.create_relationship = MagicMock(
            side_effect=Exception())

        with self.assertRaises(BadRequest):
            self.proxy.add_owner(table_uri=self.table_uri, owner=owner)

    def test_delete_owner(self) -> None:
        owner = 'active_owned_by'
        self._mock_get_table_entity()
        self.proxy.client.call_api = MagicMock(return_value='DOESNT_MATTER')

        with patch.object(self.proxy.client.relationship,
                          'delete_relationship_by_guid') as mock_execute:
            self.proxy.delete_owner(table_uri=self.table_uri, owner=owner)
            mock_execute.assert_called_with(guid="relationshipGuid-1")

    def test_get_user_entity(self) -> None:
        user_id = '123'
        with patch.object(self.proxy.client.entity,
                          'get_entity_by_attribute') as mock_execute:
            self.proxy._get_user_entity(user_id)
            mock_execute.assert_called_with(
                type_name=AtlasCommonTypes.user,
                uniq_attributes=[(AtlasCommonParams.qualified_name, user_id)])

    def test_get_user_entity_no_user(self) -> None:
        user_id = '123'
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            side_effect=Exception())
        with self.assertRaises(NotFoundException):
            self.proxy._get_user_entity(user_id)

    def test_get_bookmark_entity_not_found(self) -> None:
        user_entity = MagicMock()
        user_entity.entity = MagicMock(return_value=self.user_entity_1)
        self.proxy._get_user_entity = MagicMock(
            return_value=user_entity)  # type: ignore
        self._mock_get_table_entity()
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            side_effect=[Exception('Mayday!'), None])

        with patch.object(self.proxy, '_create_bookmark') as mock_execute:
            user_id = self.user_entity_1['attributes']['qualifiedName']
            self.proxy._get_bookmark_entity(entity_uri=self.table_uri,
                                            user_id=user_id,
                                            resource_type=ResourceType.Table)

            expected_table_entity = self.proxy._get_table_entity(
                table_uri=self.table_uri)
            expected_user_guid = user_entity.entity[AtlasCommonParams.guid]
            expected_bookmark_qn = f'{self.db}.{self.name}.{self.entity_type}.{user_id}.bookmark@{self.cluster}'
            expected_table_uri = self.table_uri

            mock_execute.assert_called_with(expected_table_entity,
                                            expected_user_guid,
                                            expected_bookmark_qn,
                                            expected_table_uri)

    def test_get_column(self) -> None:
        self._mock_get_table_entity()
        response = self.proxy._get_column(
            table_uri=self.table_uri,
            column_name=cast(dict, self.test_column['attributes'])['name'])
        self.assertDictEqual(response, self.test_column)

    def test_get_column_wrong_name(self) -> None:
        with self.assertRaises(NotFoundException):
            self._mock_get_table_entity()
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_no_referred_entities(self) -> None:
        with self.assertRaises(NotFoundException):
            local_entity: Dict = self.entity2
            local_entity['attributes']['columns'] = [{'guid': 'ent_2_col'}]
            self._mock_get_table_entity(local_entity)
            self.proxy._get_column(table_uri=self.table_uri,
                                   column_name='FAKE')

    def test_get_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        response = self.proxy.get_column_description(
            table_uri=self.table_uri, column_name=attributes['name'])
        self.assertEqual(response, attributes.get('description'))

    def test_put_column_description(self) -> None:
        self._mock_get_table_entity()
        attributes = cast(dict, self.test_column['attributes'])
        self.proxy.put_column_description(table_uri=self.table_uri,
                                          column_name=attributes['name'],
                                          description='DOESNT_MATTER')

    def test_get_table_by_user_relation_follow(self) -> None:
        bookmark1 = copy.deepcopy(self.bookmark_entity1)
        bookmark1 = self.to_class(bookmark1)
        bookmark_collection = MagicMock()
        bookmark_collection.entities = [bookmark1]

        self.proxy.client.discovery.faceted_search = MagicMock(
            return_value=bookmark_collection)
        res = self.proxy.get_table_by_user_relation(
            user_email='test_user_id', relation_type=UserResourceRel.follow)

        expected = [
            PopularTable(database=Data.entity_type,
                         cluster=Data.cluster,
                         schema=Data.db,
                         name=Data.name,
                         description=None)
        ]

        self.assertEqual(res, {'table': expected})

    def test_get_table_by_user_relation_own(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        res = self.proxy.get_table_by_user_relation(
            user_email='test_user_id', relation_type=UserResourceRel.own)

        self.assertEqual(len(res.get("table")), 1)  # type: ignore

        ent1_attrs = cast(dict, self.entity1['attributes'])

        expected = [
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent1_attrs['name'],
                         description=ent1_attrs['description'])
        ]

        self.assertEqual({'table': expected}, res)

    def test_get_resources_owned_by_user_success(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        res_table = self.proxy._get_resources_owned_by_user(
            user_id='test_user_2', resource_type=ResourceType.Table.name)
        self.assertEqual(len(res_table), 1)
        ent1_attrs = cast(dict, self.entity1['attributes'])
        expected_table = [
            PopularTable(database=self.entity_type,
                         cluster=self.cluster,
                         schema=self.db,
                         name=ent1_attrs['name'],
                         description=ent1_attrs['description'])
        ]
        self.assertEqual(expected_table, res_table)

    def test_get_resources_owned_by_user_no_user(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = None
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)
        with self.assertRaises(NotFoundException):
            self.proxy._get_resources_owned_by_user(
                user_id='test_user_2', resource_type=ResourceType.Table.name)

    def test_get_resources_owned_by_user_default_owner(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = Data.user_entity_2
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)

        basic_search_result = MagicMock()
        basic_search_result.entities = self.reader_entities

        entity2 = MagicMock()
        entity2.guid = self.entity2['guid']

        basic_search_response = MagicMock()
        basic_search_response.entities = [entity2]

        self.proxy.client.discovery.faceted_search = MagicMock(
            return_value=basic_search_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.entity1)]
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        res = self.proxy._get_resources_owned_by_user(
            user_id='test_user_2', resource_type=ResourceType.Table.name)

        self.assertEqual(len(res), 1)

    def test_get_resources_owned_by_user_dashboard(self) -> None:
        unique_attr_response = MagicMock()
        unique_attr_response.entity = self.user_entity_1
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=unique_attr_response)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [
            DottedDict(self.dashboard_data['entity'])
        ]
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        res_dashboard = self.proxy._get_resources_owned_by_user(
            user_id='test_user_1', resource_type=ResourceType.Dashboard.name)
        self.assertEqual(len(res_dashboard), 1)
        expected_dashboard = [
            DashboardSummary(uri='superset_dashboard://datalab.prod/1',
                             cluster='datalab',
                             group_name='prod superset',
                             group_url='https://prod.superset/dashboards/1',
                             product='superset',
                             name='Prod Usage',
                             url='https://prod.superset/dashboards/1',
                             description='Robs famous dashboard',
                             last_successful_run_timestamp=0,
                             chart_names=[])
        ]
        self.assertEqual(expected_dashboard, res_dashboard)

    def test_get_resources_owned_by_user_unimplemented_resource(self) -> None:
        user_id = '123'
        resource_type = 'UNIMPLEMENTED'
        with self.assertRaises(NotImplementedError):
            self.proxy._get_resources_owned_by_user(user_id, resource_type)

    def test_get_resources_using_table(self) -> None:
        self._mock_get_table_entity()

        expected_resource_type = 'dashboards'
        expected_resources_count = 1
        expected_resource_detail = [
            DashboardSummary(uri='superset_dashboard://datalab.prod/1',
                             cluster='datalab',
                             group_name='prod superset',
                             group_url='https://prod.superset/dashboards/1',
                             product='superset',
                             name='Prod Usage',
                             url='https://prod.superset/dashboards/1',
                             description='Robs famous dashboard',
                             last_successful_run_timestamp=1619517099,
                             chart_names=[])
        ]

        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=DottedDict({
                'entities': [DottedDict(self.dashboard_data['entity'])],
                'referredEntities':
                self.dashboard_data['referredEntities']
            }))

        result = self.proxy.get_resources_using_table(
            id='DOESNT_MATTER', resource_type=ResourceType.Dashboard)

        self.assertIn(expected_resource_type, result)
        self.assertEqual(expected_resources_count,
                         len(result[expected_resource_type]))
        self.assertEqual(expected_resource_detail,
                         result[expected_resource_type])

    def test_get_resources_using_table_unimplemented_resource(self) -> None:
        id = 'DOESNT_MATTER'
        resource_type = 'UNIMPLEMENTED'

        with self.assertRaises(NotImplementedError):
            self.proxy.get_resources_using_table(
                id=id, resource_type=resource_type)  # type: ignore

    def test_add_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()
        with patch.object(bookmark_entity, 'entity') as mock_execute:
            self.proxy.add_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=ResourceType.Table)
            set_call = str(mock_execute.mock_calls[1]).split('__')[-1]
            self.assertEqual("('active', True)", set_call)

    def test_add_resource_relation_by_user_unimplemented_resource(
            self) -> None:
        self._mock_get_bookmark_entity()
        resource_type = 'UNIMPLEMENTED'
        with self.assertRaises(NotImplementedError):
            self.proxy.add_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=resource_type)  # type: ignore

    def test_delete_resource_relation_by_user(self) -> None:
        bookmark_entity = self._mock_get_bookmark_entity()

        with patch.object(bookmark_entity, 'entity') as mock_execute:
            self.proxy.delete_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=ResourceType.Table)

            set_call = str(mock_execute.mock_calls[1]).split('__')[-1]
            self.assertEqual("('active', False)", set_call)

    def test_delete_resource_relation_by_user_unimplemented_resource(
            self) -> None:
        self._mock_get_bookmark_entity()
        resource_type = 'UNIMPLEMENTED'
        with self.assertRaises(NotImplementedError):
            self.proxy.delete_resource_relation_by_user(
                id=self.table_uri,
                user_id="test_user_id",
                relation_type=UserResourceRel.follow,
                resource_type=resource_type)  # type: ignore

    def test_get_readers(self) -> None:
        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = self.reader_entities
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        res = self.proxy._get_readers(
            dict(relationshipAttributes=dict(readers=[
                dict(
                    guid=1, entityStatus='ACTIVE', relationshipStatus='ACTIVE')
            ])), Reader, 1)
        expected_readers = [
            Reader(user=User(email='test_user_2', user_id='test_user_2'),
                   read_count=150)
        ]
        self.assertEqual(expected_readers, res)

        res = self.proxy._get_readers(
            dict(relationshipAttributes=dict(readers=[
                dict(
                    guid=1, entityStatus='ACTIVE', relationshipStatus='ACTIVE')
            ])), User, 1)
        expected_users = [User(email='test_user_1', user_id='test_user_1')]
        self.assertEqual(expected_users, res)

        res = self.proxy._get_readers(
            dict(relationshipAttributes=dict(readers=[
                dict(
                    guid=1, entityStatus='ACTIVE', relationshipStatus='ACTIVE')
            ])), 'WRONG_MODEL', 1)
        expected = []  # type: ignore
        self.assertEqual(expected, res)

    def test_get_frequently_used_tables(self) -> None:
        entity_unique_attribute_result = MagicMock()
        entity_unique_attribute_result.entity = DottedDict(self.user_entity_2)
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=entity_unique_attribute_result)

        entity_bulk_result = MagicMock()
        entity_bulk_result.entities = [DottedDict(self.reader_entity_1)]
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=entity_bulk_result)

        expected = {
            'table': [
                PopularTable(cluster=self.cluster,
                             name='Table1',
                             schema=self.db,
                             database=self.entity_type)
            ]
        }

        res = self.proxy.get_frequently_used_tables(user_email='dummy')

        self.assertEqual(expected, res)

    def test_get_latest_updated_ts_when_exists(self) -> None:
        self.proxy.client.admin.get_metrics = MagicMock(
            return_value=self.metrics_data)
        result = self.proxy.get_latest_updated_ts()
        assert result == 1598342400

    def test_get_latest_updated_ts_when_not_exists(self) -> None:
        self.proxy.client.admin.get_metrics = MagicMock()
        result = self.proxy.get_latest_updated_ts()
        assert result == 0

    def test_get_user_detail_default(self) -> None:
        user_id = "*****@*****.**"
        user_details = self.proxy._get_user_details(user_id=user_id)
        self.assertDictEqual(user_details, {
            'email': user_id,
            'user_id': user_id
        })

    def test_get_user_detail_config_method(self) -> None:
        user_id = "*****@*****.**"
        response = {
            'email': user_id,
            'user_id': user_id,
            'first_name': 'First',
            'last_name': 'Last'
        }

        def custom_function(id: str) -> Dict[str, Any]:
            return response

        self.app.config['USER_DETAIL_METHOD'] = custom_function

        user_details = self.proxy._get_user_details(user_id=user_id)
        self.assertDictEqual(user_details, response)
        self.app.config['USER_DETAIL_METHOD'] = None

    def test_get_owners_details_no_owner_no_fallback(self) -> None:
        res = self.proxy._get_owners(data_owners=list(), fallback_owner=None)
        self.assertEqual(len(res), 0)

    def test_get_owners_details_only_fallback(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        user_id = "*****@*****.**"
        res = self.proxy._get_owners(data_owners=list(),
                                     fallback_owner=user_id)
        self.assertEqual(1, len(res))
        self.assertListEqual(res,
                             [User(**{
                                 'email': user_id,
                                 'user_id': user_id
                             })])

    def test_get_owners_details_only_active(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        # pass both active and inactive as parameter
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners)
        # _get_owners should return only active
        self.assertEqual(1, len(res))
        self.assertEqual(res[0].user_id, 'active_owned_by')

    def test_get_owners_details_owner_and_fallback(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        user_id = "*****@*****.**"

        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        # pass both active and inactive as parameter
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners,
                                     fallback_owner=user_id)
        # _get_owners should return only active AND the fallback_owner
        self.assertEqual(2, len(res))
        self.assertEqual(res[1].user_id, user_id)

    def test_get_owners_details_owner_and_fallback_duplicates(self) -> None:
        self.app.config['USER_DETAIL_METHOD'] = None
        data_owners = cast(dict,
                           self.entity1)["relationshipAttributes"]["ownedBy"]
        user_id = data_owners[0]["displayText"]
        self.assertEqual(len(data_owners), 2)

        res = self.proxy._get_owners(data_owners=data_owners,
                                     fallback_owner=user_id)
        # _get_owners should return only active AND the fallback_owner,
        # but in case where it is duplicate, should return only 1
        self.assertEqual(1, len(res))

    def test_get_table_watermarks(self) -> None:
        params = [(['%Y%m%d'], 2, '2020-09'), (['%Y,%m'], 2, '2020-08'),
                  (['%Y-%m-%d'], 0, None), ([], 0, None)]

        for supported_formats, expected_result_length, low_date_prefix in params:
            with self.subTest():
                self.app.config['WATERMARK_DATE_FORMATS'] = supported_formats

                result = self.proxy._get_table_watermarks(
                    cast(dict, self.entity1))

                assert len(result) == expected_result_length

                if low_date_prefix:
                    low, _ = result

                    assert low.partition_value.startswith(low_date_prefix)

    def test_get_table_watermarks_no_partitions(self) -> None:
        expected = []  # type: ignore
        result = self.proxy._get_table_watermarks(cast(dict, self.entity2))
        self.assertEqual(expected, result)

    def test_get_dashboard(self) -> None:
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=self.dashboard_data)  # type: ignore
        self.proxy._get_dashboard_group = MagicMock(
            return_value=self.dashboard_group_data)  # type: ignore
        self.proxy.client.entity.get_entities_by_guids = MagicMock(
            return_value=DottedDict({'entities': [DottedDict(self.entity1)]}))

        expected = DashboardDetail(
            uri='superset_dashboard://datalab.prod/1',
            cluster='datalab',
            group_name='prod superset',
            group_url='https://superset.prod',
            product='superset',
            name='Prod Usage',
            url='https://prod.superset/dashboards/1',
            description='Robs famous dashboard',
            created_timestamp=1619517099,
            updated_timestamp=1619626531,
            last_successful_run_timestamp=1619517099,
            last_run_timestamp=1619517150,
            last_run_state='failed',
            owners=[
                User(user_id='lisa_salinas',
                     email='lisa_salinas',
                     first_name=None,
                     last_name=None,
                     full_name=None,
                     display_name=None,
                     is_active=True,
                     github_username=None,
                     team_name=None,
                     slack_id=None,
                     employee_type=None,
                     manager_fullname=None,
                     manager_email=None,
                     manager_id=None,
                     role_name=None,
                     profile_url=None,
                     other_key_values={})
            ],
            frequent_users=[],
            chart_names=['Count Users by Time', 'Total Count'],
            query_names=['User Count By Time', 'Total Count'],
            queries=[
                DashboardQuery(
                    name='User Count By Time',
                    url='https://prod.superset/dashboards/1/query/1',
                    query_text='SELECT date, COUNT(1) FROM db.table GROUP BY 1'
                ),
                DashboardQuery(
                    name='Total Count',
                    url='https://prod.superset/dashboards/1/query/2',
                    query_text='SELECT COUNT(1) FROM db.table')
            ],
            tables=[
                PopularTable(database='hive_table',
                             cluster='TEST_CLUSTER',
                             schema='TEST_DB',
                             name='Table1',
                             description='Dummy Description')
            ],
            tags=[],
            badges=[],
            recent_view_count=0)

        result = self.proxy.get_dashboard(
            id='superset_dashboard://datalab.prod/1')

        self.assertEqual(expected, result)

    def test_get_dashboard_description(self) -> None:
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=self.dashboard_data)  # type: ignore
        result = self.proxy.get_dashboard_description(id="DOESNT_MATTER")
        expected = self.dashboard_data['entity'][
            AtlasCommonParams.attributes].get('description')
        self.assertEqual(expected, result)

    def test_put_dashboard_description(self) -> None:
        dashboard_id = self.dashboard_data['entity'].get('attributes').get(
            'qualifiedName')
        dashboard_description = 'description_1'
        self.proxy.client.entity.get_entity_by_attribute = MagicMock(
            return_value=self.dashboard_data)
        with patch.object(self.proxy.client.entity,
                          'partial_update_entity_by_guid') as mock_execute:
            self.proxy.put_dashboard_description(
                id=dashboard_id, description=dashboard_description)
            expected_guid = self.dashboard_data['entity'].get(
                AtlasCommonParams.guid)
            expected_attr_value = dashboard_description
            expected_attr_name = 'description'
            mock_execute.assert_called_with(entity_guid=expected_guid,
                                            attr_value=expected_attr_value,
                                            attr_name=expected_attr_name)

    def test_get_lineage_table(self) -> None:
        key = 'hive_table://demo.sample/table_2'
        resource_type = ResourceType.Table
        direction = 'both'
        depth = 3
        source = 'hive_table'

        expected_upstream = [
            LineageItem(key='hive_table://demo.sample/table_0',
                        parent='',
                        level=2,
                        source=source,
                        badges=[],
                        usage=0),
            LineageItem(key='hive_table://demo.sample/table_1',
                        parent='hive_table://demo.sample/table_0',
                        level=1,
                        source=source,
                        badges=[],
                        usage=0),
            LineageItem(key='hive_table://demo.sample/table_4',
                        parent='',
                        level=1,
                        source=source,
                        badges=[],
                        usage=0)
        ]

        expected_downstream = [
            LineageItem(key='hive_table://demo.sample/table_3',
                        parent='hive_table://demo.sample/table_2',
                        level=1,
                        source=source,
                        badges=[],
                        usage=0)
        ]

        expected = Lineage(key=key,
                           direction=direction,
                           depth=depth,
                           upstream_entities=expected_upstream,
                           downstream_entities=expected_downstream)

        self.proxy.client.lineage.get_lineage_info = MagicMock(side_effect=[
            self.lineage_upstream_table_2, self.lineage_downstream_table_2
        ])

        result = self.proxy.get_lineage(id=key,
                                        resource_type=resource_type,
                                        direction='both',
                                        depth=3)

        self.assertIsInstance(result, Lineage)
        self.assertEqual(expected, result)

    def test_test_get_lineage_table_unimplemented_resource(self) -> None:
        unimplemented_resource_type = ResourceType.Feature
        key = 'hive_table://demo.sample/table_2'
        direction = 'both'
        depth = 3
        with self.assertRaises(NotImplementedError):
            self.proxy.get_lineage(resource_type=unimplemented_resource_type,
                                   id=key,
                                   direction=direction,
                                   depth=depth)

    def test_parse_table_bookmark_qn(self) -> None:
        bookmark_qn = f'{self.db}.{self.name}.hive_table.test_user_id.bookmark@{self.cluster}'
        expected = {
            'db': 'TEST_DB',
            'table': 'TEST_TABLE',
            'entity_type': 'hive_table',
            'user_id': 'test_user_id',
            'cluster': 'TEST_CLUSTER'
        }
        result = self.proxy._parse_table_bookmark_qn(bookmark_qn)
        self.assertEqual(expected, result)

    def test_parse_dashboard_bookmark_qn(self) -> None:
        bookmark_qn = f'superset_dashboard://{self.cluster}.{self.name}/dashboard_id/dashboard/bookmark/test_user_id'
        expected = {
            'cluster': 'TEST_CLUSTER',
            'dashboard_group': 'TEST_TABLE',
            'dashboard_id': 'dashboard_id',
            'product': 'superset',
            'type': 'dashboard',
            'user_id': 'test_user_id'
        }
        result = self.proxy._parse_dashboard_bookmark_qn(bookmark_qn)
        self.assertEqual(expected, result)

    def test_get_user_defined_glossary_guid(self) -> None:
        has_amundsen_glossary = [self.glossary_1, self.glossary_amundsen]
        no_amundsen_glossary = [self.glossary_1]
        expected_guid = self.glossary_amundsen[AtlasCommonParams.guid]

        self.proxy.client.glossary.get_all_glossaries = MagicMock(
            side_effect=[has_amundsen_glossary, no_amundsen_glossary])

        glossary_guid = self.proxy._get_user_defined_glossary_guid()
        self.assertEqual(glossary_guid, expected_guid)

        self.proxy._CACHE.invalidate(
            self.proxy._get_user_defined_glossary_guid,
            '_get_user_defined_glossary_guid')
        with patch.object(self.proxy.client.glossary,
                          'create_glossary') as mock_execute:
            self.proxy._get_user_defined_glossary_guid()
            _, args, _ = mock_execute.mock_calls[0]
            self.assertIn('name', args[0])
            self.assertEqual(self.proxy.AMUNDSEN_USER_TAGS, args[0]['name'])

            self.assertIn('shortDescription', args[0])
            self.assertEqual('Amundsen User Defined Terms',
                             args[0]['shortDescription'])

    def test_get_create_glossary_term(self) -> None:
        faceted_search_result = MagicMock
        faceted_search_result.approximateCount = 0
        self.proxy.client.discovery.faceted_search = MagicMock(
            return_value=faceted_search_result)
        self.proxy._get_user_defined_glossary_guid = MagicMock(
            return_value=self.glossary_amundsen.get('guid'))
        self.proxy.client.call_api = MagicMock(return_value=None)

        with patch.object(self.proxy.client.glossary,
                          'create_glossary_term') as mock_execute:
            self.proxy._get_create_glossary_term(term_name='DOESNT_MATTER')
            _, args, _ = mock_execute.mock_calls[0]
            self.assertIn('anchor', args[0])
            self.assertIn('glossaryGuid', args[0]['anchor'])
            self.assertEqual(args[0]['anchor']['glossaryGuid'],
                             self.glossary_amundsen.get('guid'))