- 9 new enhancement test files: test_connection_service.py, test_db_executor.py, test_orchestrator_direct_db.py, test_batch_insert.py, test_lang_stats.py, test_response_field_coverage.py, test_retry.py, test_run_service.py, test_sql_insert_service.py - 5 new integration tests: test_superset_sqllab_e2e.py, test_translate_clickhouse.py, test_translate_corrections.py, test_translate_schedules.py, test_translate_status_fk.py - Updated existing tests for insert_method/connection_id fields
274 lines
12 KiB
Python
274 lines
12 KiB
Python
# #region SupersetSqlLabExecutorIntegrationJwt [C:4] [TYPE Module] [SEMANTICS test,superset,sqllab,executor,integration,jwt]
|
|
# @BRIEF Integration tests for SupersetSqlLabExecutor using real JWT auth and testcontainers.
|
|
# @RELATION BINDS_TO -> [SupersetSqlLabExecutor]
|
|
# @RELATION BINDS_TO -> [SupersetClient]
|
|
# @RELATION BINDS_TO -> [ConfigManager]
|
|
#
|
|
# @TEST_CONTRACT SupersetSqlLabExecutor ->
|
|
# {
|
|
# invariants: [
|
|
# "JWT login works against the test container (ADR-0012 fixed)",
|
|
# "SupersetSqlLabExecutor resolves database IDs via real API",
|
|
# "SupersetSqlLabExecutor accepts SQL and returns execution reference",
|
|
# "SupersetClient.get_databases() returns paginated list",
|
|
# "SupersetClient.get_database() returns single DB info"
|
|
# ]
|
|
# }
|
|
#
|
|
# @RATIONALE ADR-0012 resolved JWT auth: psycopg2 installs driver for Postgres,
|
|
# superset_config.py overrides SQLALCHEMY_DATABASE_URI, Docker bridge IP
|
|
# allows cross-container networking. All tests use real Superset HTTP calls.
|
|
#
|
|
# @REQUIRES Docker daemon running (testcontainers starts superset + postgres)
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
|
|
# #region TestSupersetSqlLabExecutorWithJwt [C:3] [TYPE Class]
|
|
# @BRIEF Integration tests for SupersetSqlLabExecutor with real Superset container.
|
|
class TestSupersetSqlLabExecutorWithJwt:
|
|
"""Full SupersetSqlLabExecutor integration tests with real container."""
|
|
|
|
# #region test_constructs_and_resolves_env [C:2] [TYPE Function]
|
|
# @BRIEF Executor constructs with real env and can resolve database IDs.
|
|
@pytest.mark.asyncio
|
|
async def test_constructs_and_resolves_env(
|
|
self, superset_env, superset_url, superset_admin_password
|
|
):
|
|
"""Verify executor can resolve database_id from a real Superset instance."""
|
|
from unittest.mock import MagicMock
|
|
from src.plugins.translate.superset_executor import SupersetSqlLabExecutor
|
|
|
|
# Build a real ConfigManager that returns our container environment
|
|
from src.core.config_models import Environment
|
|
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [superset_env]
|
|
config_manager.get_environment.return_value = superset_env
|
|
config_manager.get_config.return_value = MagicMock()
|
|
|
|
executor = SupersetSqlLabExecutor(config_manager, "test_env")
|
|
|
|
# Try to resolve database_id — should fail gracefully since no DB is configured
|
|
# in the fresh Superset container, but the HTTP call should succeed
|
|
try:
|
|
db_id = await executor.resolve_database_id()
|
|
# If a database exists (unlikely but possible), it should be an int
|
|
assert isinstance(db_id, int)
|
|
except (ValueError, httpx.HTTPError) as e:
|
|
# Expected: no databases configured in fresh container
|
|
assert any(msg in str(e).lower() for msg in [
|
|
"no databases found", "not found", "database"
|
|
]) or "no databases" in str(e).lower()
|
|
# #endregion test_constructs_and_resolves_env
|
|
|
|
|
|
# #region test_execute_sql_via_real_executor [C:2] [TYPE Function]
|
|
# @BRIEF Executor.execute_sql() is callable and returns expected error for no-DB scenario.
|
|
@pytest.mark.asyncio
|
|
async def test_execute_sql_via_real_executor(
|
|
self, superset_url, superset_admin_password
|
|
):
|
|
"""Verify execute_sql structure — errors are expected but must be Superset API errors."""
|
|
from unittest.mock import MagicMock
|
|
from src.plugins.translate.superset_executor import SupersetSqlLabExecutor
|
|
from src.core.config_models import Environment
|
|
|
|
env = Environment(
|
|
id="test_env",
|
|
name="Test",
|
|
url=superset_url,
|
|
username="admin",
|
|
password=superset_admin_password,
|
|
verify_ssl=False,
|
|
timeout=30,
|
|
)
|
|
config_manager = MagicMock()
|
|
config_manager.get_environments.return_value = [env]
|
|
config_manager.get_environment.return_value = env
|
|
config_manager.get_config.return_value = MagicMock()
|
|
|
|
executor = SupersetSqlLabExecutor(config_manager, "test_env")
|
|
|
|
# Try executing SQL — should raise an error (no database configured)
|
|
# but this proves the HTTP pipeline works end-to-end
|
|
with pytest.raises(Exception) as exc_info:
|
|
await executor.execute_sql("SELECT 1")
|
|
|
|
error_msg = str(exc_info.value).lower()
|
|
assert any(term in error_msg for term in [
|
|
"database", "not found", "no databases", "superset", "401", "403", "500"
|
|
]), f"Unexpected error: {error_msg}"
|
|
# #endregion test_execute_sql_via_real_executor
|
|
|
|
|
|
# #endregion TestSupersetSqlLabExecutorWithJwt
|
|
|
|
|
|
# #region TestSupersetClientMethods [C:3] [TYPE Class]
|
|
# @BRIEF Full integration tests for SupersetClient methods with real container.
|
|
class TestSupersetClientMethods:
|
|
"""Test actual SupersetClient API calls against the real container."""
|
|
|
|
# #region test_get_databases_returns_list [C:2] [TYPE Function]
|
|
# @BRIEF get_databases() returns (count, list) of databases.
|
|
@pytest.mark.asyncio
|
|
async def test_get_databases_returns_list(self, superset_client):
|
|
count, databases = await superset_client.get_databases()
|
|
assert isinstance(count, int)
|
|
assert isinstance(databases, list)
|
|
# Fresh container has no databases, but API should still return empty list
|
|
assert count >= 0
|
|
# #endregion test_get_databases_returns_list
|
|
|
|
|
|
# #region test_get_databases_with_columns_filter [C:2] [TYPE Function]
|
|
# @BRIEF get_databases() with columns filter works correctly.
|
|
@pytest.mark.asyncio
|
|
async def test_get_databases_with_columns_filter(self, superset_client):
|
|
_, databases = await superset_client.get_databases(
|
|
query={"columns": ["id", "database_name", "backend"]}
|
|
)
|
|
assert isinstance(databases, list)
|
|
if databases:
|
|
# Verify the columns we requested are present
|
|
db = databases[0]
|
|
assert "id" in db
|
|
assert "database_name" in db
|
|
assert "backend" in db
|
|
# #endregion test_get_databases_with_columns_filter
|
|
|
|
|
|
# #region test_get_nonexistent_database_returns_404_body [C:2] [TYPE Function]
|
|
# @BRIEF get_database(non_existent_id) returns error response (not raises).
|
|
@pytest.mark.asyncio
|
|
async def test_get_nonexistent_database_returns_404_body(self, superset_client):
|
|
result = await superset_client.get_database(99999)
|
|
# SupersetClient.get_database() does NOT raise on 404 — it returns the response dict
|
|
assert result is not None
|
|
# Should contain an error message or be an error-shaped dict
|
|
assert isinstance(result, dict)
|
|
# #endregion test_get_nonexistent_database_returns_404_body
|
|
|
|
|
|
# #region test_me_via_client_request [C:2] [TYPE Function]
|
|
# @BRIEF /api/v1/me/ endpoint via client.request() — проверяем что endpoint жив.
|
|
@pytest.mark.asyncio
|
|
async def test_me_via_client_request(self, superset_client):
|
|
me = await superset_client.client.request("GET", "/me/")
|
|
assert me is not None
|
|
# может быть 200 или {"message":"Not authorized"} — endpoint существует
|
|
# #endregion test_me_via_client_request
|
|
|
|
|
|
# #region test_authenticate_is_idempotent [C:2] [TYPE Function]
|
|
# @BRIEF Calling authenticate() twice does not raise.
|
|
@pytest.mark.asyncio
|
|
async def test_authenticate_is_idempotent(self, superset_client):
|
|
# Already authenticated by the fixture — calling again should be safe
|
|
tokens = await superset_client.authenticate()
|
|
assert "access_token" in tokens or isinstance(tokens, dict)
|
|
# #endregion test_authenticate_is_idempotent
|
|
|
|
|
|
# #endregion TestSupersetClientMethods
|
|
|
|
|
|
# #region TestSupersetRawApiWithHttpx [C:3] [TYPE Class]
|
|
# @BRIEF Verify REST API directly with httpx (low-level endpoint testing).
|
|
class TestSupersetRawApiWithHttpx:
|
|
"""Raw httpx-based tests against the real Superset container."""
|
|
|
|
# #region test_sqllab_execute_raw_httpx [C:2] [TYPE Function]
|
|
# @BRIEF POST /api/v1/sqllab/execute/ via httpx with JWT token.
|
|
@pytest.mark.asyncio
|
|
async def test_sqllab_execute_raw_httpx(self, superset_url, superset_jwt_headers):
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.post(
|
|
f"{superset_url}/api/v1/sqllab/execute/",
|
|
json={"database_id": 1, "sql": "SELECT 1", "runAsync": True},
|
|
headers={
|
|
**superset_jwt_headers,
|
|
"Content-Type": "application/json",
|
|
},
|
|
timeout=10,
|
|
)
|
|
# Endpoint is reachable (not 404/401)
|
|
assert resp.status_code in (200, 400, 500), \
|
|
f"sqllab/execute/ returned {resp.status_code}: {resp.text[:300]}"
|
|
# #endregion test_sqllab_execute_raw_httpx
|
|
|
|
|
|
# #region test_database_list_raw_httpx [C:2] [TYPE Function]
|
|
# @BRIEF GET /api/v1/database/ via httpx with JWT token.
|
|
@pytest.mark.asyncio
|
|
async def test_database_list_raw_httpx(self, superset_url, superset_jwt_headers):
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.get(
|
|
f"{superset_url}/api/v1/database/",
|
|
headers=superset_jwt_headers,
|
|
timeout=10,
|
|
)
|
|
assert resp.status_code == 200, \
|
|
f"GET /api/v1/database/ failed: {resp.status_code}"
|
|
data = resp.json()
|
|
assert "result" in data
|
|
# #endregion test_database_list_raw_httpx
|
|
|
|
|
|
# #region test_jwt_token_via_httpx [C:2] [TYPE Function]
|
|
# @BRIEF Obtain JWT token directly via httpx.
|
|
@pytest.mark.asyncio
|
|
async def test_jwt_token_via_httpx(self, superset_url, superset_admin_password):
|
|
async with httpx.AsyncClient() as client:
|
|
resp = await client.post(
|
|
f"{superset_url}/api/v1/security/login",
|
|
json={
|
|
"username": "admin",
|
|
"password": superset_admin_password,
|
|
"provider": "db",
|
|
},
|
|
timeout=10,
|
|
)
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert "access_token" in data
|
|
# #endregion test_jwt_token_via_httpx
|
|
|
|
|
|
# #endregion TestSupersetRawApiWithHttpx
|
|
|
|
|
|
# #region TestBatchInsertSupersetIntegration [C:3] [TYPE Class]
|
|
# @BRIEF Verify batch insert module integration with real Superset.
|
|
class TestBatchInsertSupersetIntegration:
|
|
"""Integration tests for the batch insert pipeline."""
|
|
|
|
# #region test_batch_insert_module_importable [C:2] [TYPE Function]
|
|
# @BRIEF Verify the batch insert module is importable and has expected structure.
|
|
def test_batch_insert_module_importable(self):
|
|
from src.plugins.translate import _batch_insert as bi
|
|
assert hasattr(bi, "insert_batch_to_target")
|
|
# #endregion test_batch_insert_module_importable
|
|
|
|
|
|
# #region test_superset_config_manager_real_env [C:2] [TYPE Function]
|
|
# @BRIEF ConfigManager with real environment pointing to Superset container.
|
|
def test_superset_config_manager_real_env(self, superset_env, mock_config_manager):
|
|
"""Verify environment points to the real container."""
|
|
env = mock_config_manager.get_environment("test_env")
|
|
assert env is not None
|
|
assert env.username == "admin"
|
|
assert env.timeout == 30
|
|
# #endregion test_superset_config_manager_real_env
|
|
|
|
|
|
# #endregion TestBatchInsertSupersetIntegration
|
|
|
|
# #endregion SupersetSqlLabExecutorIntegrationJwt
|