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:
2026-06-05 00:18:13 +03:00
parent c2578facdb
commit 58e994cd75
2 changed files with 65 additions and 65 deletions

View File

@@ -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]

View File

@@ -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