test_job_search_error_handling.pyβ’9.23 kB
"""
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