Files
ss-tools/backend/tests/plugins/translate/test_service_datasource.py
busya 8a4310169b v4: 7643 tests passing, 83% raw / 96.9% real coverage (excl __tests__).
12 known failures — all from Agent 3 new unverified tests (mock setup issues):
- 3 dataset_review_routes_extended (DTO field mismatches)
- 1 settings_consolidated (dict key access)
- 1 llm_analysis_service_coverage (rate_limit mock)
- 1 migration_plugin (SessionLocal side_effect exhaustion)
- 1 preview (DB query vs dict key)
- 5 scheduler (datetime timezone + async mock mismatches)

NEW TEST FILES THIS SESSION:
- test_batch_insert_coverage.py — 3 tests
- test_storage_plugin.py — +3 tests
- test_search.py — +2 tests
- test_mapper.py — already 100%
- test_llm_analysis_migration_v1_to_v2.py — 14 tests
- test_llm_async_http.py — +1 test
- test_prompt_builder.py — +1 test
- test_service_datasource.py — +1 test
- test_lang_detect.py — +1 test
- test_scheduler.py — +6 tests
- test_llm_analysis_service_coverage.py — +15 tests
- test_dataset_review_routes_extended.py — +14 tests
- test_settings_consolidated.py — +13 tests

Modules pushed to 100%: _batch_insert, dictionary_entries, service_datasource,
_llm_async_http, prompt_builder, dictionary_crud, _batch_sizer, storage/plugin,
mapper.py

Session: 7194→7643 tests (+449), 80%→83% raw (+3pp), 93.4%→96.9% real (+3.5pp).
Remaining: 12 failures to fix + ~300 statements to reach 98% real coverage.
2026-06-16 09:34:10 +03:00

345 lines
14 KiB
Python

# #region Test.DatasourceMetadataService [C:3] [TYPE Module] [SEMANTICS test,datasource,metadata]
# @BRIEF Tests for service_datasource.py — datasource metadata fetching.
# @RELATION BINDS_TO -> [DatasourceMetadataService]
# @TEST_EDGE: empty_backend -> ValueError
# @TEST_EDGE: unsupported_dialect -> ValueError
# @TEST_EDGE: no_env_config -> ValueError
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent / "src"))
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from src.plugins.translate.service_datasource import (
get_dialect_from_database,
detect_virtual_columns,
get_datasource_columns,
fetch_datasource_metadata,
SUPPORTED_DIALECTS,
)
class TestGetDialectFromDatabase:
"""get_dialect_from_database — extract dialect from Superset DB record."""
def test_backend_key(self):
"""Uses 'backend' key."""
result = get_dialect_from_database({"backend": "postgresql"})
assert result == "postgresql"
def test_engine_fallback(self):
"""Falls back to 'engine' when 'backend' missing."""
result = get_dialect_from_database({"engine": "mysql"})
assert result == "mysql"
def test_empty_raises(self):
"""Empty backend/engine raises ValueError."""
with pytest.raises(ValueError, match="Could not determine"):
get_dialect_from_database({})
def test_dialect_mapping(self):
"""Dialect map normalizes known backends."""
assert get_dialect_from_database({"backend": "greenplum"}) == "postgresql"
assert get_dialect_from_database({"backend": "clickhousedb"}) == "clickhouse"
def test_unsupported_dialect_raises(self):
"""Unsupported dialect raises ValueError."""
with pytest.raises(ValueError, match="Unsupported database dialect"):
get_dialect_from_database({"backend": "mongodb"})
def test_case_insensitive(self):
"""Backend is case-insensitive."""
result = get_dialect_from_database({"backend": "PostgreSQL"})
assert result == "postgresql"
class TestDetectVirtualColumns:
"""detect_virtual_columns — identify computed columns."""
def test_detects_virtual(self):
"""Returns names of non-physical columns."""
columns = [
{"name": "id", "is_physical": True},
{"name": "full_name", "is_physical": False},
{"name": "email", "is_physical": True},
]
virtual = detect_virtual_columns(columns)
assert virtual == ["full_name"]
def test_all_physical(self):
"""All physical returns empty list."""
columns = [
{"name": "id", "is_physical": True},
{"name": "name", "is_physical": True},
]
assert detect_virtual_columns(columns) == []
def test_all_virtual(self):
"""All virtual returns all names."""
columns = [
{"name": "calc1", "is_physical": False},
{"name": "calc2", "is_physical": False},
]
virtual = detect_virtual_columns(columns)
assert virtual == ["calc1", "calc2"]
def test_default_is_physical(self):
"""Default is_physical is True when missing."""
columns = [{"name": "id"}]
assert detect_virtual_columns(columns) == []
class TestFetchDatasourceMetadata:
"""fetch_datasource_metadata — fetch columns + dialect."""
@pytest.mark.asyncio
async def test_success_with_database_dict(self):
"""Success when database is a dict."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [
{"name": "id", "type": "INTEGER", "is_physical": True},
{"name": "name", "type": "VARCHAR", "is_physical": True},
],
"database": {"backend": "postgresql"},
}
columns, dialect = await fetch_datasource_metadata(1, "env-1", config_manager)
assert len(columns) == 2
assert dialect == "postgresql"
assert columns[0]["name"] == "id"
@pytest.mark.asyncio
async def test_no_env_config(self):
"""Non-existent env raises ValueError."""
config_manager = MagicMock()
config_manager.get_environments.return_value = []
with pytest.raises(ValueError, match="not found"):
await fetch_datasource_metadata(1, "env-1", config_manager)
@pytest.mark.asyncio
async def test_columns_with_name_fallback(self):
"""Uses 'column_name' when 'name' is missing."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [
{"column_name": "my_col", "type": "VARCHAR"},
],
"database": {"backend": "postgresql"},
}
columns, dialect = await fetch_datasource_metadata(1, "env-1", config_manager)
assert len(columns) == 1
assert columns[0]["name"] == "my_col"
@pytest.mark.asyncio
async def test_skips_columns_without_name(self):
"""Skips columns without name or column_name."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [
{"name": "valid_col"},
{"extra": "no_name"},
{},
],
"database": {"backend": "postgresql"},
}
columns, dialect = await fetch_datasource_metadata(1, "env-1", config_manager)
assert len(columns) == 1
@pytest.mark.asyncio
async def test_database_info_as_non_dict(self):
"""When database_info is not a dict (e.g., str), fetches from database_id."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [],
"database": "not a dict",
"database_id": 123,
}
mock_client.get_database.return_value = {"result": {"backend": "mysql"}}
columns, dialect = await fetch_datasource_metadata(1, "env-1", config_manager)
assert dialect == "mysql"
@pytest.mark.asyncio
async def test_database_info_not_dict_no_db_id(self):
"""When database_info not a dict and no database_id, raises."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [],
"database": "not a dict",
}
with pytest.raises(ValueError, match="No database information"):
await fetch_datasource_metadata(1, "env-1", config_manager)
@pytest.mark.asyncio
async def test_database_info_not_dict_db_error(self):
"""Exception when fetching database info raises."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [],
"database": "not a dict",
"database_id": 123,
}
mock_client.get_database.side_effect = Exception("API error")
with pytest.raises(ValueError, match="Could not determine"):
await fetch_datasource_metadata(1, "env-1", config_manager)
class TestGetDatasourceColumns:
"""get_datasource_columns — structured column metadata response."""
@pytest.mark.asyncio
async def test_success(self):
"""Returns structured response with columns."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [
{"name": "id", "type": "INTEGER"},
],
"database": {"backend": "postgresql"},
"table_name": "my_table",
"schema": "public",
}
response = await get_datasource_columns(1, "env-1", config_manager)
assert response.datasource_id == 1
assert response.database_dialect == "postgresql"
assert response.datasource_name == "my_table"
assert len(response.columns) == 1
assert response.columns[0].name == "id"
@pytest.mark.asyncio
async def test_no_env_config(self):
"""Non-existent env raises ValueError."""
config_manager = MagicMock()
config_manager.get_environments.return_value = []
with pytest.raises(ValueError, match="not found"):
await get_datasource_columns(1, "env-1", config_manager)
@pytest.mark.asyncio
async def test_dialect_from_database_fallback(self):
"""When dialect from database dict fails, fetches from database_id."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
# get_dialect_from_database will fail on empty backend
mock_client.get_dataset_detail.return_value = {
"columns": [],
"database": {"not_backend": "mysql"},
"database_id": 456,
}
mock_client.get_database.return_value = {"result": {"backend": "mysql"}}
response = await get_datasource_columns(1, "env-1", config_manager)
assert response.database_dialect == "mysql"
@pytest.mark.asyncio
async def test_no_database_id_raises(self):
"""When dialect fails and no database_id, raises."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [],
"database": {"not_backend": "xxx"},
}
with pytest.raises(ValueError, match="Could not determine"):
await get_datasource_columns(1, "env-1", config_manager)
@pytest.mark.asyncio
async def test_skips_column_without_name(self):
"""Columns without name or column_name are skipped (line 159)."""
config_manager = MagicMock()
env_config = MagicMock()
env_config.id = "env-1"
config_manager.get_environments.return_value = [env_config]
with patch("src.plugins.translate.service_datasource.get_superset_client") as mock_get:
mock_client = AsyncMock()
mock_get.return_value = mock_client
mock_client.get_dataset_detail.return_value = {
"columns": [
{"name": "valid_col", "type": "INTEGER"},
{"not_name": "should_skip", "type": "VARCHAR"},
{},
{"column_name": "also_valid", "type": "TEXT"},
],
"database": {"backend": "postgresql"},
"table_name": "t1",
"schema": "public",
}
response = await get_datasource_columns(1, "env-1", config_manager)
assert len(response.columns) == 2
assert response.columns[0].name == "valid_col"
assert response.columns[1].name == "also_valid"
# #endregion Test.DatasourceMetadataService