# #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 == {}