239 lines
8.5 KiB
Python
239 lines
8.5 KiB
Python
# #region Test.DebugPlugin [C:3] [TYPE Module] [SEMANTICS test, debug, plugin, coverage]
|
|
# @BRIEF Unit tests for DebugPlugin — properties, get_schema, execute, and edge cases.
|
|
# @RELATION BINDS_TO -> [DebugPlugin]
|
|
# @TEST_EDGE: unknown_action -> Raises ValueError
|
|
# @TEST_EDGE: missing_source_target_env -> Raises ValueError
|
|
# @TEST_EDGE: missing_env_dataset_id -> Raises ValueError
|
|
# @TEST_EDGE: env_not_found -> Raises ValueError
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch, PropertyMock
|
|
|
|
from src.plugins.debug import DebugPlugin
|
|
|
|
|
|
# ── Helpers ──
|
|
|
|
|
|
def _make_env(id_val="env-1", name="Test Env"):
|
|
env = MagicMock()
|
|
env.id = id_val
|
|
env.name = name
|
|
return env
|
|
|
|
|
|
def _make_task_context():
|
|
ctx = MagicMock()
|
|
ctx.logger = MagicMock()
|
|
ctx.logger.with_source.return_value = MagicMock()
|
|
return ctx
|
|
|
|
|
|
class TestDebugPluginProperties:
|
|
"""Verify static property values."""
|
|
|
|
def test_id(self):
|
|
plugin = DebugPlugin()
|
|
assert plugin.id == "system-debug"
|
|
|
|
def test_name(self):
|
|
plugin = DebugPlugin()
|
|
assert plugin.name == "System Debug"
|
|
|
|
def test_description(self):
|
|
plugin = DebugPlugin()
|
|
assert plugin.description == "Run system diagnostics and debug Superset API responses."
|
|
|
|
def test_version(self):
|
|
plugin = DebugPlugin()
|
|
assert plugin.version == "1.0.0"
|
|
|
|
def test_ui_route(self):
|
|
plugin = DebugPlugin()
|
|
assert plugin.ui_route == "/tools/debug"
|
|
|
|
|
|
class TestDebugPluginGetSchema:
|
|
"""Verify get_schema returns correct structure."""
|
|
|
|
def test_get_schema(self):
|
|
plugin = DebugPlugin()
|
|
schema = plugin.get_schema()
|
|
|
|
assert schema["type"] == "object"
|
|
assert "action" in schema["properties"]
|
|
assert schema["properties"]["action"]["enum"] == ["test-db-api", "get-dataset-structure"]
|
|
assert "action" in schema["required"]
|
|
|
|
|
|
class TestDebugPluginExecute:
|
|
"""Verify DebugPlugin.execute with various actions."""
|
|
|
|
# ── Unknown action → ValueError ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_unknown_action(self):
|
|
"""Unknown action raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
with pytest.raises(ValueError, match="Unknown action: bogus"):
|
|
await plugin.execute({"action": "bogus"})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_unknown_action_with_ctx(self):
|
|
"""Unknown action raises ValueError even with context."""
|
|
plugin = DebugPlugin()
|
|
ctx = _make_task_context()
|
|
with pytest.raises(ValueError, match="Unknown action: bogus"):
|
|
await plugin.execute({"action": "bogus"}, context=ctx)
|
|
|
|
# ── test-db-api: missing source/target ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_test_db_api_missing_params(self):
|
|
"""test-db-api without source_env/target_env raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
with pytest.raises(ValueError, match="source_env and target_env are required"):
|
|
await plugin.execute({"action": "test-db-api", "source_env": "src"})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_test_db_api_no_params(self):
|
|
"""test-db-api without any params raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
with pytest.raises(ValueError, match="source_env and target_env are required"):
|
|
await plugin.execute({"action": "test-db-api"})
|
|
|
|
# ── test-db-api: env not found ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_test_db_api_env_not_found(self):
|
|
"""test-db-api with non-existent env raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
mock_cm = MagicMock()
|
|
mock_cm.get_environment.return_value = None
|
|
|
|
with patch('src.dependencies.get_config_manager', return_value=mock_cm):
|
|
with pytest.raises(ValueError, match="not found"):
|
|
await plugin.execute({
|
|
"action": "test-db-api",
|
|
"source_env": "NonExistent",
|
|
"target_env": "AlsoMissing",
|
|
})
|
|
|
|
# ── test-db-api: happy path ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_test_db_api_success(self):
|
|
"""test-db-api completes successfully with both envs."""
|
|
plugin = DebugPlugin()
|
|
src_env = _make_env("src-id", "Source")
|
|
tgt_env = _make_env("tgt-id", "Target")
|
|
|
|
mock_cm = MagicMock()
|
|
mock_cm.get_environment.side_effect = [src_env, tgt_env]
|
|
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_databases = AsyncMock(return_value=(3, [{"id": 1, "name": "db1"}]))
|
|
|
|
with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
|
|
patch('src.plugins.debug.get_superset_client') as mock_get_client:
|
|
mock_get_client.side_effect = [mock_client, mock_client]
|
|
|
|
result = await plugin.execute({
|
|
"action": "test-db-api",
|
|
"source_env": "Source",
|
|
"target_env": "Target",
|
|
})
|
|
|
|
assert "Source" in result
|
|
assert "Target" in result
|
|
assert result["Source"]["count"] == 3
|
|
assert result["Target"]["count"] == 3
|
|
|
|
# ── get-dataset-structure: missing params ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_get_dataset_structure_missing_env(self):
|
|
"""get-dataset-structure without env raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
with pytest.raises(ValueError, match="env and dataset_id are required"):
|
|
await plugin.execute({"action": "get-dataset-structure", "dataset_id": 42})
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_get_dataset_structure_missing_dataset_id(self):
|
|
"""get-dataset-structure without dataset_id raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
with pytest.raises(ValueError, match="env and dataset_id are required"):
|
|
await plugin.execute({"action": "get-dataset-structure", "env": "Dev"})
|
|
|
|
# ── get-dataset-structure: env not found ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_get_dataset_structure_env_not_found(self):
|
|
"""get-dataset-structure with non-existent env raises ValueError."""
|
|
plugin = DebugPlugin()
|
|
mock_cm = MagicMock()
|
|
mock_cm.get_environment.return_value = None
|
|
|
|
with patch('src.dependencies.get_config_manager', return_value=mock_cm):
|
|
with pytest.raises(ValueError, match="not found"):
|
|
await plugin.execute({
|
|
"action": "get-dataset-structure",
|
|
"env": "Missing",
|
|
"dataset_id": 1,
|
|
})
|
|
|
|
# ── get-dataset-structure: happy path ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_get_dataset_structure_success(self):
|
|
"""get-dataset-structure returns dataset result."""
|
|
plugin = DebugPlugin()
|
|
env = _make_env("dev-id", "Dev")
|
|
mock_cm = MagicMock()
|
|
mock_cm.get_environment.return_value = env
|
|
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dataset = AsyncMock(return_value={
|
|
"result": {"id": 42, "table_name": "my_table"},
|
|
})
|
|
|
|
with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
|
|
patch('src.plugins.debug.get_superset_client') as mock_get_client:
|
|
mock_get_client.return_value = mock_client
|
|
|
|
result = await plugin.execute({
|
|
"action": "get-dataset-structure",
|
|
"env": "Dev",
|
|
"dataset_id": 42,
|
|
})
|
|
|
|
assert result == {"id": 42, "table_name": "my_table"}
|
|
|
|
# ── get-dataset-structure: empty result ──
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_execute_get_dataset_structure_empty_result(self):
|
|
"""get-dataset-structure returns empty dict when result field is missing."""
|
|
plugin = DebugPlugin()
|
|
env = _make_env("dev-id", "Dev")
|
|
mock_cm = MagicMock()
|
|
mock_cm.get_environment.return_value = env
|
|
|
|
mock_client = MagicMock()
|
|
mock_client.authenticate = AsyncMock()
|
|
mock_client.get_dataset = AsyncMock(return_value={"some_other_key": "value"})
|
|
|
|
with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
|
|
patch('src.plugins.debug.get_superset_client') as mock_get_client:
|
|
mock_get_client.return_value = mock_client
|
|
|
|
result = await plugin.execute({
|
|
"action": "get-dataset-structure",
|
|
"env": "Dev",
|
|
"dataset_id": 99,
|
|
})
|
|
|
|
assert result == {}
|