Files
temp/tests/unit/agents/test_tool_coverage.py

156 lines
5.7 KiB
Python

"""
Additional coverage tests for agents/tool.py
Covers missing lines for tool agent edge cases and error handling.
Coverage targets:
- Lines 108, 119, 129: JSON extraction returns None for non-dict types
- Lines 180, 205-212, 228-231: Shell command and tool validation errors
- Lines 326-327: HTTP tool edge cases
- Lines 417-418: Math tool edge cases
"""
import pytest
from cleveragents.agents.tool import ToolAgent
from cleveragents.core.exceptions import ExecutionError
from cleveragents.templates.renderer import TemplateRenderer, TemplateEngine
class TestToolAgentJSONExtractionEdgeCases:
"""Test JSON extraction edge cases (lines 108, 119, 129)."""
def test_extract_json_non_dict_returns_none(self):
"""Test extracting non-dict JSON returns None (line 108)."""
config = {"tools": ["echo"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# JSON array instead of object
message = '["item1", "item2", "item3"]'
result = agent._extract_json_from_message(message)
# Should return None for non-dict JSON
assert result is None
def test_extract_json_code_block_non_dict_returns_none(self):
"""Test extracting non-dict from code block returns None (line 119)."""
config = {"tools": ["echo"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Code block with array instead of object
message = '```json\n[1, 2, 3]\n```'
result = agent._extract_json_from_message(message)
# Should return None for non-dict
assert result is None
def test_extract_json_pattern_non_dict_returns_none(self):
"""Test pattern match non-dict returns None (line 129)."""
config = {"tools": ["echo"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Text with embedded array
message = 'Some text [1, 2, 3] more text'
result = agent._extract_json_from_message(message)
# Should return None or not find it
assert result is None or isinstance(result, dict)
class TestToolAgentShellCommandValidation:
"""Test shell command validation (lines 180, 205-212, 228-231)."""
@pytest.mark.asyncio
async def test_dangerous_command_blocked_in_safe_mode(self):
"""Test dangerous commands are blocked in safe mode (line 180)."""
config = {
"tools": ["shell"],
"allow_shell": True,
"safe_mode": True
}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Dangerous command should be blocked
with pytest.raises(ExecutionError, match="blocked in safe mode"):
await agent._execute_shell_command("rm", {"args": ["-rf", "/"]})
@pytest.mark.asyncio
async def test_tool_not_in_allowed_list_raises_error(self):
"""Test executing tool not in allowed list raises error (lines 205-212)."""
config = {"tools": ["echo", "math"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Try to execute tool not in allowed list
with pytest.raises(ExecutionError, match="not in allowed tools list"):
await agent._execute_tool("forbidden_tool", {}, None)
@pytest.mark.asyncio
async def test_shell_execution_disabled_raises_error(self):
"""Test shell execution when not allowed raises error (lines 228-231)."""
config = {
"tools": ["echo"],
"allow_shell": False # Shell disabled
}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Try to execute unknown tool (not builtin, shell disabled)
with pytest.raises(ExecutionError, match="not in allowed tools list"):
await agent._execute_tool("unknown_tool", {}, None)
class TestToolAgentHTTPToolEdgeCases:
"""Test HTTP tool edge cases (lines 326-327)."""
@pytest.mark.asyncio
async def test_http_request_without_url_raises_error(self):
"""Test HTTP request without URL raises error (line 327)."""
config = {"tools": ["http_request"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# HTTP request without URL should fail
args = {"method": "GET"} # Missing URL
with pytest.raises(ExecutionError, match="requires a URL"):
await agent._http_request_tool(args, None)
class TestToolAgentMathToolEdgeCases:
"""Test math tool edge cases (lines 417-418)."""
@pytest.mark.asyncio
async def test_math_tool_without_expression_raises_error(self):
"""Test math tool without expression raises error (line 417)."""
config = {"tools": ["math"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Math without expression should fail
args = {} # No expression provided
with pytest.raises(ExecutionError, match="requires an expression"):
await agent._math_tool(args, None)
@pytest.mark.asyncio
async def test_math_tool_with_args_list(self):
"""Test math tool with expression in args list (line 418)."""
config = {"tools": ["math"]}
renderer = TemplateRenderer(TemplateEngine.JINJA2)
agent = ToolAgent("test", config, renderer)
# Math with expression in args list format
args = {"args": ["5 * 5"]}
result = await agent._math_tool(args, None)
# Should evaluate the expression
assert result == "25"
if __name__ == "__main__":
pytest.main([__file__, "-v"])