We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/deslicer/mcp-for-splunk'
If you have feedback or need assistance with the MCP directory API, please join our Discord server
"""
Test for job search error handling with string messages.
This test verifies the fix for the bug where job.content["messages"]
can contain strings instead of dictionaries, causing AttributeError
when trying to call .get() method on strings.
"""
from unittest.mock import AsyncMock, Mock
import pytest
from src.tools.search.job_search import JobSearch
class TestJobSearchErrorHandling:
"""Test job search error handling for different message formats."""
@pytest.fixture
def job_search_tool(self):
"""Create a JobSearch tool instance for testing."""
return JobSearch("run_splunk_search", "search")
@pytest.fixture
def mock_context(self):
"""Create a mock context for testing."""
ctx = Mock()
ctx.info = AsyncMock()
ctx.error = AsyncMock()
ctx.report_progress = AsyncMock()
return ctx
@pytest.fixture
def mock_service(self):
"""Create a mock Splunk service for testing."""
service = Mock()
return service
async def test_job_search_with_string_messages(
self, job_search_tool, mock_context, mock_service
):
"""Test that job search handles string messages without crashing."""
# Mock the check_splunk_available method to return success
job_search_tool.check_splunk_available = Mock(return_value=(True, mock_service, None))
# Create a mock job that fails with string messages
mock_job = Mock()
mock_job.sid = "test_job_123"
mock_job.is_done.return_value = True
# This simulates the bug condition: messages are strings, not dictionaries
mock_job.content = {
"isFailed": "1",
"isDone": "1",
"messages": [
"Error in 'search' command: Unable to parse the search: Encountered the following error while compiling the search: Error in 'search' command: The following search head has one or more missing search peers",
"Search not executed: The search job has failed due to an error. Review search syntax.",
],
}
# Mock the service.jobs.create to return our mock job
mock_service.jobs.create.return_value = mock_job
# Mock the format_error_response method
job_search_tool.format_error_response = Mock(
return_value={"status": "error", "error": "Test error"}
)
# Execute the search - this should not crash
result = await job_search_tool.execute(
mock_context,
query="| rest /services/authorization/roles | search title=<your_role> | table title",
earliest_time="-1h",
latest_time="now",
)
# Verify that the tool handled the error gracefully
assert result is not None
assert isinstance(result, dict)
# Verify that format_error_response was called (indicating error was handled)
job_search_tool.format_error_response.assert_called()
# Verify that the error contained the string messages
call_args = job_search_tool.format_error_response.call_args[0][0]
assert "Error in 'search' command" in call_args
assert "Search not executed" in call_args
async def test_job_search_with_dict_messages(self, job_search_tool, mock_context, mock_service):
"""Test that job search still handles dictionary messages correctly."""
# Mock the check_splunk_available method to return success
job_search_tool.check_splunk_available = Mock(return_value=(True, mock_service, None))
# Create a mock job that fails with dictionary messages (normal case)
mock_job = Mock()
mock_job.sid = "test_job_456"
mock_job.is_done.return_value = True
# This simulates the normal case: messages are dictionaries
mock_job.content = {
"isFailed": "1",
"isDone": "1",
"messages": [
{"type": "ERROR", "text": "Search syntax error: Invalid command"},
{"type": "WARN", "text": "This is a warning"},
{"type": "ERROR", "text": "Another error occurred"},
],
}
# Mock the service.jobs.create to return our mock job
mock_service.jobs.create.return_value = mock_job
# Mock the format_error_response method
job_search_tool.format_error_response = Mock(
return_value={"status": "error", "error": "Test error"}
)
# Execute the search - this should not crash
result = await job_search_tool.execute(
mock_context,
query="index=main | invalid_command",
earliest_time="-1h",
latest_time="now",
)
# Verify that the tool handled the error gracefully
assert result is not None
assert isinstance(result, dict)
# Verify that format_error_response was called
job_search_tool.format_error_response.assert_called()
# Verify that only ERROR messages were included
call_args = job_search_tool.format_error_response.call_args[0][0]
assert "Search syntax error: Invalid command" in call_args
assert "Another error occurred" in call_args
assert "This is a warning" not in call_args # WARN messages should be filtered out
async def test_job_search_with_mixed_messages(
self, job_search_tool, mock_context, mock_service
):
"""Test that job search handles mixed string and dictionary messages."""
# Mock the check_splunk_available method to return success
job_search_tool.check_splunk_available = Mock(return_value=(True, mock_service, None))
# Create a mock job that fails with mixed message types
mock_job = Mock()
mock_job.sid = "test_job_789"
mock_job.is_done.return_value = True
# This simulates a mixed case: some messages are strings, some are dictionaries
mock_job.content = {
"isFailed": "1",
"isDone": "1",
"messages": [
"String error message: Search failed",
{"type": "ERROR", "text": "Dictionary error message: Invalid syntax"},
"Another string error: Connection timeout",
{"type": "INFO", "text": "This is just info"},
],
}
# Mock the service.jobs.create to return our mock job
mock_service.jobs.create.return_value = mock_job
# Mock the format_error_response method
job_search_tool.format_error_response = Mock(
return_value={"status": "error", "error": "Test error"}
)
# Execute the search - this should not crash
result = await job_search_tool.execute(
mock_context, query="index=main | some_query", earliest_time="-1h", latest_time="now"
)
# Verify that the tool handled the error gracefully
assert result is not None
assert isinstance(result, dict)
# Verify that format_error_response was called
job_search_tool.format_error_response.assert_called()
# Verify that both string messages and ERROR dictionary messages were included
call_args = job_search_tool.format_error_response.call_args[0][0]
assert "String error message: Search failed" in call_args
assert "Dictionary error message: Invalid syntax" in call_args
assert "Another string error: Connection timeout" in call_args
assert "This is just info" not in call_args # INFO messages should be filtered out
async def test_job_search_no_messages_field(self, job_search_tool, mock_context, mock_service):
"""Test that job search handles missing messages field gracefully."""
# Mock the check_splunk_available method to return success
job_search_tool.check_splunk_available = Mock(return_value=(True, mock_service, None))
# Create a mock job that fails without messages field
mock_job = Mock()
mock_job.sid = "test_job_no_messages"
mock_job.is_done.return_value = True
# This simulates a case where there's no messages field
mock_job.content = {
"isFailed": "1",
"isDone": "1",
# No messages field
}
# Mock the service.jobs.create to return our mock job
mock_service.jobs.create.return_value = mock_job
# Mock the format_error_response method
job_search_tool.format_error_response = Mock(
return_value={"status": "error", "error": "Test error"}
)
# Execute the search - this should not crash
result = await job_search_tool.execute(
mock_context, query="index=main | some_query", earliest_time="-1h", latest_time="now"
)
# Verify that the tool handled the error gracefully
assert result is not None
assert isinstance(result, dict)
# Verify that format_error_response was called with generic message
job_search_tool.format_error_response.assert_called()
call_args = job_search_tool.format_error_response.call_args[0][0]
assert "Job failed with no specific error message" in call_args