mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-08 13:40:08 +00:00
* feat: add owner assignment support at metadata ingestion level * docs: Translate comments to English in test_owner * refactor: move the test_owner-related files into correct positions * feat: Add support for more source types * Revert "feat: Add support for more source types" This reverts commit a7649dcb3204cf98b7f4f9be02fbb982d2532193. * feat: Add owner field support in sourceConfig for Database and Dashboard ingestion (fixes #22392) * refactor code with the required style * add owner field in related json file * feat: add topology-based owner config for database/schema/table * Format the code by the pre-commit tools * fix some errors * add a doc to explain this feature * translate all Chinese comments to English and consolidate documentation * remove redundant code * refactor code * refactor code * refactor code * refactor code * Add some tests for owner-config and enhance this feat * Add some tests for owner-config and enhance this feat * fix some error * fix some error * refactor code * Remove the yaml and bash test files and test owner config with pytest style * format the python code * refactor ingestion code * refactor code * fix some error in test_owner_utils --------- Co-authored-by: Ma,Yutao <yutao.ma@sap.com>
427 lines
15 KiB
Python
427 lines
15 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""
|
|
Unit tests for owner_utils module
|
|
"""
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock
|
|
|
|
from metadata.generated.schema.type.entityReference import EntityReference
|
|
from metadata.generated.schema.type.entityReferenceList import EntityReferenceList
|
|
from metadata.utils.owner_utils import OwnerResolver, get_owner_from_config
|
|
|
|
|
|
class TestOwnerResolver(unittest.TestCase):
|
|
"""Test cases for OwnerResolver class"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.mock_metadata = MagicMock()
|
|
|
|
# Mock successful owner lookup
|
|
mock_owner = EntityReference(
|
|
id="123e4567-e89b-12d3-a456-426614174000",
|
|
type="user",
|
|
name="test-user",
|
|
fullyQualifiedName="test-user",
|
|
)
|
|
self.mock_owner_list = EntityReferenceList(root=[mock_owner])
|
|
|
|
def test_simple_default_owner(self):
|
|
"""Test simple default owner configuration"""
|
|
config = {"default": "data-team"}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="test_table")
|
|
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="data-team", is_owner=True
|
|
)
|
|
|
|
def test_level_specific_owner(self):
|
|
"""Test level-specific owner configuration"""
|
|
config = {
|
|
"default": "default-team",
|
|
"database": "db-team",
|
|
"databaseSchema": "schema-team",
|
|
"table": "table-team",
|
|
}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Test database level
|
|
result = resolver.resolve_owner(entity_type="database", entity_name="test_db")
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="db-team", is_owner=True
|
|
)
|
|
|
|
# Test databaseSchema level
|
|
result = resolver.resolve_owner(
|
|
entity_type="databaseSchema", entity_name="test_schema"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="schema-team", is_owner=True
|
|
)
|
|
|
|
# Test table level
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="test_table")
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="table-team", is_owner=True
|
|
)
|
|
|
|
def test_specific_entity_mapping(self):
|
|
"""Test specific entity name mapping"""
|
|
config = {
|
|
"default": "default-team",
|
|
"table": {"orders": "sales-team", "customers": "customer-team"},
|
|
}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Test specific table mapping
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="orders")
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="sales-team", is_owner=True
|
|
)
|
|
|
|
# Test unmapped table falls back to default
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="products")
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="default-team", is_owner=True
|
|
)
|
|
|
|
def test_fqn_matching(self):
|
|
"""Test FQN matching for entities"""
|
|
config = {
|
|
"default": "default-team",
|
|
"table": {
|
|
"sales_db.public.orders": "sales-team",
|
|
"analytics_db.public.reports": "analytics-team",
|
|
},
|
|
}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Test FQN match
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="sales_db.public.orders"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="sales-team", is_owner=True
|
|
)
|
|
|
|
def test_simple_name_fallback(self):
|
|
"""Test fallback to simple name when FQN doesn't match"""
|
|
config = {"default": "default-team", "table": {"orders": "sales-team"}}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Test FQN that falls back to simple name
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="sales_db.public.orders"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
# Should match on simple name "orders"
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="sales-team", is_owner=True
|
|
)
|
|
|
|
def test_inheritance_enabled(self):
|
|
"""Test owner inheritance from parent"""
|
|
config = {"default": "default-team", "enableInheritance": True, "table": {}}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Table should inherit from schema owner
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="test_table", parent_owner="schema-team"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="schema-team", is_owner=True
|
|
)
|
|
|
|
def test_inheritance_disabled(self):
|
|
"""Test that inheritance can be disabled"""
|
|
config = {"default": "default-team", "enableInheritance": False, "table": {}}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Table should NOT inherit, should use default
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="test_table", parent_owner="schema-team"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
# Should use default, not parent
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="default-team", is_owner=True
|
|
)
|
|
|
|
def test_priority_order(self):
|
|
"""Test priority order: specific > level > inheritance > default"""
|
|
config = {
|
|
"default": "default-team",
|
|
"enableInheritance": True,
|
|
"table": {"orders": "specific-team"},
|
|
}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
|
|
# Specific configuration should have highest priority
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="orders", parent_owner="parent-team"
|
|
)
|
|
self.assertIsNotNone(result)
|
|
# Should use specific, not parent or default
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="specific-team", is_owner=True
|
|
)
|
|
|
|
def test_owner_not_found(self):
|
|
"""Test handling when owner is not found"""
|
|
config = {"default": "nonexistent-team"}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = None
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="test_table")
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_empty_config(self):
|
|
"""Test with empty configuration"""
|
|
resolver = OwnerResolver(self.mock_metadata, {})
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="test_table")
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_email_lookup(self):
|
|
"""Test owner lookup by email"""
|
|
config = {"default": "admin@company.com"}
|
|
|
|
# First call (by name) returns None, second call (by email) succeeds
|
|
self.mock_metadata.get_reference_by_name.return_value = None
|
|
self.mock_metadata.get_reference_by_email.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="test_table")
|
|
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_email.assert_called_with(
|
|
"admin@company.com"
|
|
)
|
|
|
|
def test_multiple_owners_array(self):
|
|
"""Test multiple owners specified as array (users, not teams)"""
|
|
config = {
|
|
"default": "default-team",
|
|
"table": {"orders": ["john.doe", "jane.smith"]},
|
|
}
|
|
|
|
mock_john_owner = EntityReference(
|
|
id="923e4567-e89b-12d3-a456-426614174008",
|
|
type="user",
|
|
name="john.doe",
|
|
fullyQualifiedName="john.doe",
|
|
)
|
|
mock_jane_owner = EntityReference(
|
|
id="a23e4567-e89b-12d3-a456-426614174009",
|
|
type="user",
|
|
name="jane.smith",
|
|
fullyQualifiedName="jane.smith",
|
|
)
|
|
|
|
def mock_get_reference(name, is_owner=False):
|
|
if name == "john.doe":
|
|
return EntityReferenceList(root=[mock_john_owner])
|
|
elif name == "jane.smith":
|
|
return EntityReferenceList(root=[mock_jane_owner])
|
|
return None
|
|
|
|
self.mock_metadata.get_reference_by_name.side_effect = mock_get_reference
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="orders")
|
|
|
|
self.assertIsNotNone(result)
|
|
self.assertEqual(len(result.root), 2)
|
|
self.assertEqual(result.root[0].name, "john.doe")
|
|
self.assertEqual(result.root[1].name, "jane.smith")
|
|
|
|
def test_multiple_owners_partial_success(self):
|
|
"""Test that partial success works when some owners are not found (users)"""
|
|
config = {"table": {"orders": ["john.doe", "nonexistent-user", "jane.smith"]}}
|
|
|
|
mock_john_owner = EntityReference(
|
|
id="423e4567-e89b-12d3-a456-426614174003",
|
|
type="user",
|
|
name="john.doe",
|
|
fullyQualifiedName="john.doe",
|
|
)
|
|
mock_jane_owner = EntityReference(
|
|
id="523e4567-e89b-12d3-a456-426614174004",
|
|
type="user",
|
|
name="jane.smith",
|
|
fullyQualifiedName="jane.smith",
|
|
)
|
|
|
|
def mock_get_reference(name, is_owner=False):
|
|
if name == "john.doe":
|
|
return EntityReferenceList(root=[mock_john_owner])
|
|
elif name == "jane.smith":
|
|
return EntityReferenceList(root=[mock_jane_owner])
|
|
return None
|
|
|
|
self.mock_metadata.get_reference_by_name.side_effect = mock_get_reference
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="orders")
|
|
|
|
self.assertIsNotNone(result)
|
|
self.assertEqual(len(result.root), 2)
|
|
|
|
def test_multiple_owners_all_fail(self):
|
|
"""Test that None is returned when all owners fail"""
|
|
config = {"table": {"orders": ["nonexistent-1", "nonexistent-2"]}}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = None
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="orders")
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_backward_compatibility_single_string(self):
|
|
"""Test backward compatibility with single string owner"""
|
|
config = {"table": {"orders": "sales-team"}}
|
|
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(entity_type="table", entity_name="orders")
|
|
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="sales-team", is_owner=True
|
|
)
|
|
|
|
def test_multiple_owners_with_fqn(self):
|
|
"""Test multiple owners with FQN matching (users)"""
|
|
config = {"table": {"sales_db.public.orders": ["john.doe", "jane.smith"]}}
|
|
|
|
mock_john_owner = EntityReference(
|
|
id="623e4567-e89b-12d3-a456-426614174005",
|
|
type="user",
|
|
name="john.doe",
|
|
fullyQualifiedName="john.doe",
|
|
)
|
|
mock_jane_owner = EntityReference(
|
|
id="723e4567-e89b-12d3-a456-426614174006",
|
|
type="user",
|
|
name="jane.smith",
|
|
fullyQualifiedName="jane.smith",
|
|
)
|
|
|
|
def mock_get_reference(name, is_owner=False):
|
|
if name == "john.doe":
|
|
return EntityReferenceList(root=[mock_john_owner])
|
|
elif name == "jane.smith":
|
|
return EntityReferenceList(root=[mock_jane_owner])
|
|
return None
|
|
|
|
self.mock_metadata.get_reference_by_name.side_effect = mock_get_reference
|
|
|
|
resolver = OwnerResolver(self.mock_metadata, config)
|
|
result = resolver.resolve_owner(
|
|
entity_type="table", entity_name="sales_db.public.orders"
|
|
)
|
|
|
|
self.assertIsNotNone(result)
|
|
self.assertEqual(len(result.root), 2)
|
|
|
|
|
|
class TestGetOwnerFromConfig(unittest.TestCase):
|
|
"""Test cases for get_owner_from_config function"""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures"""
|
|
self.mock_metadata = MagicMock()
|
|
mock_owner = EntityReference(
|
|
id="823e4567-e89b-12d3-a456-426614174007",
|
|
type="user",
|
|
name="test-user",
|
|
fullyQualifiedName="test-user",
|
|
)
|
|
self.mock_owner_list = EntityReferenceList(root=[mock_owner])
|
|
|
|
def test_string_config(self):
|
|
"""Test with string configuration (simple mode)"""
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
result = get_owner_from_config(
|
|
metadata=self.mock_metadata,
|
|
owner_config="data-team",
|
|
entity_type="table",
|
|
entity_name="test_table",
|
|
)
|
|
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="data-team", is_owner=True
|
|
)
|
|
|
|
def test_dict_config(self):
|
|
"""Test with dict configuration"""
|
|
config = {"default": "data-team"}
|
|
self.mock_metadata.get_reference_by_name.return_value = self.mock_owner_list
|
|
|
|
result = get_owner_from_config(
|
|
metadata=self.mock_metadata,
|
|
owner_config=config,
|
|
entity_type="table",
|
|
entity_name="test_table",
|
|
)
|
|
|
|
self.assertIsNotNone(result)
|
|
self.mock_metadata.get_reference_by_name.assert_called_with(
|
|
name="data-team", is_owner=True
|
|
)
|
|
|
|
def test_none_config(self):
|
|
"""Test with None configuration"""
|
|
result = get_owner_from_config(
|
|
metadata=self.mock_metadata,
|
|
owner_config=None,
|
|
entity_type="table",
|
|
entity_name="test_table",
|
|
)
|
|
|
|
self.assertIsNone(result)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|