"""Unit tests for query_loki_instant 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, LokiAPIError, LokiTimeoutError, NetworkError, ValidationError, ) def _import_query_loki_instant_tool(): """Import query_loki_instant tool function without triggering circular imports.""" import importlib.util tool_path = project_root / "src" / "tools" / "query_loki_instant.py" spec = importlib.util.spec_from_file_location("query_loki_instant_module", str(tool_path)) if spec is None or spec.loader is None: raise ImportError("Could not load query_loki_instant module") module = importlib.util.module_from_spec(spec) sys.modules["query_loki_instant_module"] = module spec.loader.exec_module(module) return module.query_loki_instant class TestQueryLokiInstantExecution: """Test query_loki_instant tool execution.""" @pytest.mark.asyncio async def test_successful_query_returns_json_response(self): """Successful query should return JSON response in Loki format.""" mock_loki_response = { "status": "success", "data": { "resultType": "streams", "result": [ { "stream": {"app": "test", "level": "info"}, "values": [["1609459200000000000", "log message 1"]], } ], }, } 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.query_instant = AsyncMock(return_value=mock_loki_response) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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"]["resultType"] == "streams" @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") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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_query_returns_error(self): """Invalid LogQL query 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") as mock_get_client: mock_client = AsyncMock(spec=LokiClient) mock_client.query_instant = AsyncMock( side_effect=LokiAPIError("Invalid log query", "parse error: missing }") ) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query="{invalid", 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_query_with_optional_parameters(self): """Query with optional parameters (time, limit, direction) should work.""" mock_loki_response = { "status": "success", "data": {"resultType": "streams", "result": []}, } 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.query_instant = AsyncMock(return_value=mock_loki_response) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', cluster_id="cluster-1", env="prod", deploy_type="dedicated", time="2024-01-01T12:00:00Z", limit=200, direction="forward", ) assert len(result) == 1 assert result[0]["type"] == "text" mock_client.query_instant.assert_called_once() call_kwargs = mock_client.query_instant.call_args.kwargs assert call_kwargs["limit"] == 200 assert call_kwargs["direction"] == "forward" @pytest.mark.asyncio async def test_timeout_returns_error(self): """Query 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.query_instant = AsyncMock( side_effect=LokiTimeoutError("Query exceeded 10-second timeout") ) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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.query_instant = AsyncMock( side_effect=NetworkError("Loki backend is unreachable") ) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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_env_added_to_query(self): """Environment should be added as label filter to query.""" mock_loki_response = { "status": "success", "data": {"resultType": "streams", "result": []}, } 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.query_instant = AsyncMock(return_value=mock_loki_response) mock_get_client.return_value = (mock_client, "us-east-1") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', cluster_id="cluster-1", env="staging", deploy_type="dedicated", ) assert len(result) == 1 mock_client.query_instant.assert_called_once() call_kwargs = mock_client.query_instant.call_args.kwargs assert 'cluster_env="staging"' in call_kwargs["query"] @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") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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") query_loki_instant = _import_query_loki_instant_tool() result = await query_loki_instant( query='{app="test"}', 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()