"""Unit tests for get_loki_label_values MCP tool.""" import json import sys from pathlib import Path from unittest.mock import AsyncMock, patch import pytest project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from src.services.loki import LokiClient # noqa: E402 from src.utils.errors import ( # noqa: E402 AuthServiceError, LokiTimeoutError, NetworkError, ValidationError, ) def _import_get_loki_label_values_tool(): """Import get_loki_label_values tool function without triggering circular imports.""" import importlib.util tool_path = project_root / "src" / "tools" / "get_loki_label_values.py" spec = importlib.util.spec_from_file_location("get_loki_label_values_module", str(tool_path)) if spec is None or spec.loader is None: raise ImportError("Could not load get_loki_label_values module") module = importlib.util.module_from_spec(spec) sys.modules["get_loki_label_values_module"] = module spec.loader.exec_module(module) return module.get_loki_label_values class TestGetLokiLabelValuesExecution: """Test get_loki_label_values tool execution.""" @pytest.mark.asyncio async def test_successful_get_returns_json_response(self): """Successful get should return JSON response with label values.""" mock_values = ["mysql", "tidb", "tikv", "pd"] with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_client = AsyncMock(spec=LokiClient) mock_client.get_label_values = AsyncMock(return_value=mock_values) mock_get_client.return_value = (mock_client, "us-east-1") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" response_data = json.loads(result[0]["text"]) assert response_data["status"] == "success" assert "data" in response_data assert response_data["data"] == mock_values @pytest.mark.asyncio async def test_auth_service_error_returns_error(self): """Auth service error should return error message.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_get_client.side_effect = AuthServiceError("Observability URL not configured") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert "error" in result[0]["text"].lower() @pytest.mark.asyncio async def test_invalid_label_name_returns_error(self): """Empty label_name should return validation error.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster"): get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert ( "error" in result[0]["text"].lower() or "cannot be empty" in result[0]["text"].lower() ) @pytest.mark.asyncio async def test_get_with_optional_parameters(self): """Get with optional parameters (start, end, query) should work.""" mock_values = ["mysql", "tidb"] with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_client = AsyncMock(spec=LokiClient) mock_client.get_label_values = AsyncMock(return_value=mock_values) mock_get_client.return_value = (mock_client, "us-east-1") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", start="2024-01-01T00:00:00Z", end="2024-01-01T23:59:59Z", query='{namespace="production"}', ) assert len(result) == 1 assert result[0]["type"] == "text" mock_client.get_label_values.assert_called_once() call_kwargs = mock_client.get_label_values.call_args.kwargs assert call_kwargs["label_name"] == "app" @pytest.mark.asyncio async def test_timeout_returns_error(self): """Request timeout should return timeout error message.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_client = AsyncMock(spec=LokiClient) mock_client.get_label_values = AsyncMock( side_effect=LokiTimeoutError("Label values request exceeded 15-second timeout") ) mock_get_client.return_value = (mock_client, "us-east-1") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert "timeout" in result[0]["text"].lower() @pytest.mark.asyncio async def test_network_error_returns_error(self): """Network error should return error message.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_client = AsyncMock(spec=LokiClient) mock_client.get_label_values = AsyncMock( side_effect=NetworkError("Loki backend is unreachable") ) mock_get_client.return_value = (mock_client, "us-east-1") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert "unreachable" in result[0]["text"].lower() or "network" in result[0]["text"].lower() @pytest.mark.asyncio async def test_validation_error_returns_error(self): """Validation error should return error message.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_get_client.side_effect = ValidationError("Invalid parameters") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert "error" in result[0]["text"].lower() @pytest.mark.asyncio async def test_generic_exception_returns_error(self): """Generic exception should return error message.""" with patch( "src.services.auth.get_valid_server_auth", new_callable=AsyncMock, ): with patch("src.services.loki_resolve.get_loki_client_for_cluster") as mock_get_client: mock_get_client.side_effect = Exception("Unexpected error") get_loki_label_values = _import_get_loki_label_values_tool() result = await get_loki_label_values( label_name="app", cluster_id="cluster-1", env="prod", deploy_type="dedicated", ) assert len(result) == 1 assert result[0]["type"] == "text" assert "error" in result[0]["text"].lower()