032: fix tests — validate_target_table_schema became async (add await + AsyncMock)
All test methods calling validate_target_table_schema now async def + await. Mocked async methods (resolve_database_id, execute_and_poll) use AsyncMock instead of MagicMock since await on MagicMock raises TypeError.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
import pytest
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from src.plugins.translate.service_utils import _extract_dialect
|
||||
from src.plugins.translate.service_datasource import (
|
||||
@@ -648,7 +648,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_clickhouse_uses_system_columns [TYPE Function]
|
||||
# @PURPOSE: ClickHouse backend generates system.columns query.
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_clickhouse_uses_system_columns(self, mock_executor) -> None:
|
||||
async def test_clickhouse_uses_system_columns(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -663,10 +663,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "clickhouse"
|
||||
instance.get_database_name.return_value = "test_ch"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -676,9 +676,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"name": "is_original", "type": "UInt8"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
# Verify the SQL query used system.columns
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
@@ -690,7 +690,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_postgresql_uses_information_schema [TYPE Function]
|
||||
# @PURPOSE: PostgreSQL backend generates information_schema.columns query.
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_postgresql_uses_information_schema(self, mock_executor) -> None:
|
||||
async def test_postgresql_uses_information_schema(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -705,10 +705,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_pg"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -718,9 +718,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"column_name": "is_original", "data_type": "boolean"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
sql = call_args.kwargs.get("sql") or call_args.args[0]
|
||||
@@ -731,7 +731,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_clickhousedb_normalized_to_clickhouse [TYPE Function]
|
||||
# @PURPOSE: "clickhousedb" backend is normalized to clickhouse -> uses system.columns.
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_clickhousedb_normalized_to_clickhouse(self, mock_executor) -> None:
|
||||
async def test_clickhousedb_normalized_to_clickhouse(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -746,10 +746,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "clickhousedb"
|
||||
instance.get_database_name.return_value = "test_ch"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -759,9 +759,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"name": "is_original", "type": "UInt8"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
sql = call_args.kwargs.get("sql") or call_args.args[0]
|
||||
@@ -771,7 +771,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_greenplum_normalized_to_postgresql [TYPE Function]
|
||||
# @PURPOSE: "greenplum" backend is normalized to postgresql -> uses information_schema.
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_greenplum_normalized_to_postgresql(self, mock_executor) -> None:
|
||||
async def test_greenplum_normalized_to_postgresql(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -786,10 +786,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "greenplum"
|
||||
instance.get_database_name.return_value = "test_gp"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -799,9 +799,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"name": "is_original", "type": "boolean"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
sql = call_args.kwargs.get("sql") or call_args.args[0]
|
||||
@@ -811,7 +811,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_redshift_uses_information_schema [TYPE Function]
|
||||
# @PURPOSE: Redshift uses information_schema (it's PostgreSQL-derived).
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_redshift_uses_information_schema(self, mock_executor) -> None:
|
||||
async def test_redshift_uses_information_schema(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -826,10 +826,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "redshift"
|
||||
instance.get_database_name.return_value = "test_rs"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -839,9 +839,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"column_name": "is_original", "data_type": "boolean"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
sql = call_args.kwargs.get("sql") or call_args.args[0]
|
||||
@@ -851,7 +851,7 @@ class TestSchemaValidationDialectRouting:
|
||||
# region test_case_insensitive_backend_normalization [TYPE Function]
|
||||
# @PURPOSE: Backend normalization is case-insensitive.
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_case_insensitive_backend_normalization(self, mock_executor) -> None:
|
||||
async def test_case_insensitive_backend_normalization(self, mock_executor) -> None:
|
||||
from src.schemas.translate import TargetSchemaValidationRequest
|
||||
from src.plugins.translate.service_target_schema import validate_target_table_schema
|
||||
|
||||
@@ -866,10 +866,10 @@ class TestSchemaValidationDialectRouting:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "ClickHouseDB"
|
||||
instance.get_database_name.return_value = "test_ch"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"data": [
|
||||
@@ -879,9 +879,9 @@ class TestSchemaValidationDialectRouting:
|
||||
{"name": "is_original", "type": "UInt8"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
validate_target_table_schema(req, config_manager)
|
||||
await validate_target_table_schema(req, config_manager)
|
||||
|
||||
call_args = instance.execute_and_poll.call_args
|
||||
sql = call_args.kwargs.get("sql") or call_args.args[0]
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# @TEST_CONTRACT validate_target_table_schema -> TargetSchemaValidationResponse
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from src.plugins.translate.service_target_schema import (
|
||||
_build_expected_columns,
|
||||
@@ -271,16 +271,16 @@ class TestParseSqlLabResult:
|
||||
class TestValidateTargetTableSchema:
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_all_columns_present(self, mock_executor, build_request) -> None:
|
||||
async def test_all_columns_present(self, mock_executor, build_request) -> None:
|
||||
"""Все ожидаемые колонки присутствуют в таблице."""
|
||||
req = build_request()
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_db"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"columns": [{"name": "column_name"}, {"name": "data_type"}],
|
||||
@@ -291,9 +291,9 @@ class TestValidateTargetTableSchema:
|
||||
{"column_name": "is_original", "data_type": "boolean"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is True
|
||||
assert response.error is None
|
||||
assert response.all_present is True
|
||||
@@ -304,16 +304,16 @@ class TestValidateTargetTableSchema:
|
||||
assert response.database_backend == "postgresql"
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_missing_columns(self, mock_executor, build_request) -> None:
|
||||
async def test_missing_columns(self, mock_executor, build_request) -> None:
|
||||
"""В таблице не хватает некоторых ожидаемых колонок."""
|
||||
req = build_request()
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_db"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"columns": [{"name": "column_name"}],
|
||||
@@ -322,9 +322,9 @@ class TestValidateTargetTableSchema:
|
||||
{"column_name": "context"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is True
|
||||
assert response.all_present is False
|
||||
missing_names = {c.name for c in response.missing_columns}
|
||||
@@ -333,7 +333,7 @@ class TestValidateTargetTableSchema:
|
||||
assert "id" not in missing_names
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_clickhouse_describe_table(self, mock_executor, build_request) -> None:
|
||||
async def test_clickhouse_describe_table(self, mock_executor, build_request) -> None:
|
||||
"""ClickHouse использует DESCRIBE TABLE — другой формат ответа."""
|
||||
req = build_request(
|
||||
target_key_cols=["report_date", "document_number"],
|
||||
@@ -345,10 +345,10 @@ class TestValidateTargetTableSchema:
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "clickhouse"
|
||||
instance.get_database_name.return_value = "test_clickhouse"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"columns": [{"name": "name"}, {"name": "type"}],
|
||||
@@ -363,9 +363,9 @@ class TestValidateTargetTableSchema:
|
||||
{"name": "is_original", "type": "UInt8"},
|
||||
],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is True
|
||||
assert response.error is None
|
||||
assert response.all_present is True
|
||||
@@ -375,85 +375,85 @@ class TestValidateTargetTableSchema:
|
||||
assert response.database_backend == "clickhouse"
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_superset_failed_status(self, mock_executor, build_request) -> None:
|
||||
async def test_superset_failed_status(self, mock_executor, build_request) -> None:
|
||||
"""Superset возвращает failed статус."""
|
||||
req = build_request()
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_db"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "failed",
|
||||
"error_message": "Query timeout",
|
||||
}
|
||||
})
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is False
|
||||
assert response.error is not None
|
||||
assert "Query timeout" in response.error
|
||||
assert response.all_present is False
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_executor_exception(self, mock_executor, build_request) -> None:
|
||||
async def test_executor_exception(self, mock_executor, build_request) -> None:
|
||||
"""Executor бросает исключение."""
|
||||
req = build_request()
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_db"
|
||||
instance.execute_and_poll.side_effect = ConnectionError("Superset API unavailable")
|
||||
instance.execute_and_poll = AsyncMock(side_effect=ConnectionError("Superset API unavailable"))
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is False
|
||||
assert response.error is not None
|
||||
assert "Superset API unavailable" in response.error
|
||||
assert response.all_present is False
|
||||
|
||||
def test_sql_injection_table_name(self, build_request) -> None:
|
||||
async def test_sql_injection_table_name(self, build_request) -> None:
|
||||
"""Невалидное имя таблицы с пробелом — ошибка."""
|
||||
req = build_request(target_table="my table; DROP TABLE students; --")
|
||||
config_manager = MagicMock()
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is False
|
||||
assert response.error is not None
|
||||
assert "Invalid target table name" in response.error
|
||||
assert response.all_present is False
|
||||
|
||||
def test_sql_injection_schema_name(self, build_request) -> None:
|
||||
async def test_sql_injection_schema_name(self, build_request) -> None:
|
||||
"""Невалидное имя схемы с DROP — ошибка."""
|
||||
req = build_request(target_schema="public; DROP TABLE students; --")
|
||||
config_manager = MagicMock()
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is False
|
||||
assert response.error is not None
|
||||
assert "Invalid target schema name" in response.error
|
||||
assert response.all_present is False
|
||||
|
||||
@patch("src.plugins.translate.service_target_schema.SupersetSqlLabExecutor")
|
||||
def test_table_not_exists(self, mock_executor, build_request) -> None:
|
||||
async def test_table_not_exists(self, mock_executor, build_request) -> None:
|
||||
"""Таблица не существует — пустой data."""
|
||||
req = build_request()
|
||||
config_manager = MagicMock()
|
||||
|
||||
instance = mock_executor.return_value
|
||||
instance.resolve_database_id.return_value = None
|
||||
instance.resolve_database_id = AsyncMock(return_value=None)
|
||||
instance.get_database_backend.return_value = "postgresql"
|
||||
instance.get_database_name.return_value = "test_db"
|
||||
instance.execute_and_poll.return_value = {
|
||||
instance.execute_and_poll = AsyncMock(return_value={
|
||||
"status": "success",
|
||||
"raw_response": {
|
||||
"columns": [{"name": "column_name"}],
|
||||
"data": [],
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
response = validate_target_table_schema(req, config_manager)
|
||||
response = await validate_target_table_schema(req, config_manager)
|
||||
assert response.table_exists is False
|
||||
assert response.error is None
|
||||
assert response.all_present is False
|
||||
|
||||
Reference in New Issue
Block a user