"""Unit tests for list_loki_labels 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_list_loki_labels_tool(): """Import list_loki_labels tool function without triggering circular imports.""" import importlib.util tool_path = project_root / "src" / "tools" / "list_loki_labels.py" spec = importlib.util.spec_from_file_location("list_loki_labels_module", str(tool_path)) if spec is None or spec.loader is None: raise ImportError("Could not load list_loki_labels module") module = importlib.util.module_from_spec(spec) sys.modules["list_loki_labels_module"] = module spec.loader.exec_module(module) return module.list_loki_labels class TestListLokiLabelsExecution: """Test list_loki_labels tool execution.""" @pytest.mark.asyncio async def test_successful_list_returns_json_response(self): """Successful list should return JSON response with label names.""" mock_labels = ["app", "namespace", "level", "component"] 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.list_labels = AsyncMock(return_value=mock_labels) mock_get_client.return_value = (mock_client, "us-east-1") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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_labels @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") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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_list_with_optional_parameters(self): """List with optional parameters (start, end, query) should work.""" mock_labels = ["app", "namespace"] 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.list_labels = AsyncMock(return_value=mock_labels) mock_get_client.return_value = (mock_client, "us-east-1") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( cluster_id="cluster-1", env="prod", deploy_type="dedicated", start="2024-01-01T00:00:00Z", end="2024-01-01T23:59:59Z", query='{app="mysql"}', ) assert len(result) == 1 assert result[0]["type"] == "text" mock_client.list_labels.assert_called_once_with( start="2024-01-01T00:00:00Z", end="2024-01-01T23:59:59Z", query='{app="mysql"}', ) @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.list_labels = AsyncMock( side_effect=LokiTimeoutError( "Label discovery request exceeded 15-second timeout" ) ) mock_get_client.return_value = (mock_client, "us-east-1") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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.list_labels = AsyncMock( side_effect=NetworkError("Loki backend is unreachable") ) mock_get_client.return_value = (mock_client, "us-east-1") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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") list_loki_labels = _import_list_loki_labels_tool() result = await list_loki_labels( 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()