honcho/tests/test_base_agent.py

191 lines
6.4 KiB
Python

"""
Tests for BaseAgent implementation.
Test criteria:
- TC-0B.1: BaseAgent can be instantiated (with mock abstract methods)
- TC-0B.2: BaseAgent methods work correctly (run, pre_execute, post_execute, trace_execution)
"""
import pytest
from typing import Any, Dict
from unittest.mock import MagicMock, AsyncMock
from src.agents.shared import BaseAgent
class TestAgent(BaseAgent):
"""Concrete implementation of BaseAgent for testing."""
def __init__(self, db, config=None, **kwargs):
super().__init__(db, config, **kwargs)
self.execution_log = []
async def execute(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
"""Mock execute implementation."""
self.execution_log.append("execute")
return {
"result": "success",
"processed": input_data.get("data", ""),
}
def validate_input(self, input_data: Dict[str, Any]) -> bool:
"""Mock validate_input implementation."""
self.execution_log.append("validate_input")
return "data" in input_data
class TestBaseAgent:
"""Test suite for BaseAgent functionality."""
@pytest.fixture
def mock_db(self):
"""Create mock database session."""
db = MagicMock()
return db
@pytest.fixture
def test_agent(self, mock_db):
"""Create TestAgent instance."""
return TestAgent(db=mock_db, config=None)
def test_agent_initialization(self, mock_db):
"""TC-0B.1: BaseAgent can be instantiated."""
agent = TestAgent(db=mock_db, config=None)
assert agent.db == mock_db
assert agent.config is None
assert agent.agent_type == "testagent"
assert hasattr(agent, "execution_log")
def test_agent_initialization_with_config(self, mock_db):
"""TC-0B.1: BaseAgent can be instantiated with config."""
from src.agents.shared import AgentConfig
config = AgentConfig(
model="gpt-4o-mini",
temperature=0.5,
timeout=30,
)
agent = TestAgent(db=mock_db, config=config)
assert agent.config == config
assert agent.config.model == "gpt-4o-mini"
assert agent.config.temperature == 0.5
def test_agent_initialization_with_kwargs(self, mock_db):
"""TC-0B.1: BaseAgent can be instantiated with additional kwargs."""
agent = TestAgent(
db=mock_db,
config=None,
custom_param="test_value",
another_param=42,
)
assert agent.custom_param == "test_value"
assert agent.another_param == 42
@pytest.mark.asyncio
async def test_execute_method(self, test_agent):
"""TC-0B.2: Execute method works correctly."""
input_data = {"data": "test input"}
output = await test_agent.execute(input_data)
assert output["result"] == "success"
assert output["processed"] == "test input"
assert "execute" in test_agent.execution_log
def test_validate_input_method(self, test_agent):
"""TC-0B.2: Validate input method works correctly."""
valid_input = {"data": "test"}
invalid_input = {"wrong_key": "test"}
assert test_agent.validate_input(valid_input) is True
assert test_agent.validate_input(invalid_input) is False
assert test_agent.execution_log.count("validate_input") == 2
@pytest.mark.asyncio
async def test_pre_execute_hook(self, test_agent):
"""TC-0B.2: Pre-execute hook validates input."""
valid_input = {"data": "test"}
result = await test_agent.pre_execute(valid_input)
assert result == valid_input
assert "validate_input" in test_agent.execution_log
@pytest.mark.asyncio
async def test_pre_execute_hook_invalid_input(self, test_agent):
"""TC-0B.2: Pre-execute hook raises error for invalid input."""
invalid_input = {"wrong_key": "test"}
with pytest.raises(ValueError, match="Invalid input for testagent agent"):
await test_agent.pre_execute(invalid_input)
@pytest.mark.asyncio
async def test_post_execute_hook(self, test_agent, mock_db):
"""TC-0B.2: Post-execute hook traces execution."""
input_data = {"data": "test"}
output = {"result": "success"}
result = await test_agent.post_execute(input_data, output)
assert result == output
@pytest.mark.asyncio
async def test_trace_execution(self, test_agent):
"""TC-0B.2: Trace execution logs correctly."""
input_data = {"data": "test"}
output = {"result": "success"}
metadata = {"execution_time": 1.5}
# Should not raise any errors
await test_agent.trace_execution(input_data, output, metadata)
@pytest.mark.asyncio
async def test_run_pipeline_success(self, test_agent):
"""TC-0B.2: Full run pipeline executes correctly."""
input_data = {"data": "test input"}
# Clear execution log
test_agent.execution_log = []
output = await test_agent.run(input_data)
assert output["result"] == "success"
assert output["processed"] == "test input"
# Verify execution order
assert "validate_input" in test_agent.execution_log
assert "execute" in test_agent.execution_log
assert test_agent.execution_log.index("validate_input") < test_agent.execution_log.index("execute")
@pytest.mark.asyncio
async def test_run_pipeline_validation_failure(self, test_agent):
"""TC-0B.2: Run pipeline fails on invalid input."""
invalid_input = {"wrong_key": "test"}
with pytest.raises(ValueError, match="Invalid input for testagent agent"):
await test_agent.run(invalid_input)
@pytest.mark.asyncio
async def test_run_pipeline_execution_failure(self, mock_db):
"""TC-0B.2: Run pipeline handles execution errors."""
class FailingAgent(BaseAgent):
async def execute(self, input_data):
raise RuntimeError("Execution failed")
def validate_input(self, input_data):
return True
agent = FailingAgent(db=mock_db)
with pytest.raises(RuntimeError, match="Execution failed"):
await agent.run({"data": "test"})
def test_agent_repr(self, test_agent):
"""Test agent string representation."""
repr_str = repr(test_agent)
assert "TestAgent" in repr_str
assert "testagent" in repr_str