Files
evotraders/backend/tests/test_evo_agent_integration.py
cillin 45c3996434 refactor(cleanup): remove legacy agent classes and complete EvoAgent migration
Remove deprecated AnalystAgent, PMAgent, and RiskAgent classes.
All agent creation now goes through UnifiedAgentFactory creating EvoAgent instances.

- Delete backend/agents/analyst.py (169 lines)
- Delete backend/agents/portfolio_manager.py (420 lines)
- Delete backend/agents/risk_manager.py (139 lines)
- Update all imports to use EvoAgent exclusively
- Clean up unused imports across 25 files
- Update tests to work with simplified agent structure

Constraint: EvoAgent is now the single source of truth for all agent roles
Constraint: UnifiedAgentFactory handles runtime agent creation
Rejected: Keep legacy aliases | creates maintenance burden
Confidence: high
Scope-risk: moderate (affects agent instantiation paths)
Directive: All new agent features must be added to EvoAgent, not legacy classes
Not-tested: Kubernetes sandbox executor (marked with TODO)
2026-04-02 10:51:14 +08:00

285 lines
10 KiB
Python

# -*- coding: utf-8 -*-
"""Integration tests for EvoAgent system.
These tests verify the integration between:
- UnifiedAgentFactory
- EvoAgent
- ToolGuardMixin
- Workspace-driven configuration
"""
from unittest.mock import MagicMock
class TestUnifiedAgentFactoryIntegration:
"""Test UnifiedAgentFactory creates agents correctly."""
def test_factory_creates_analyst_with_workspace_config(self, tmp_path):
"""Test that factory creates EvoAgent with workspace config."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
# Setup mock skills manager
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
return path
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
# Create workspace config
workspace_dir = tmp_path / "runs" / "test_config" / "agents" / "fundamentals_analyst"
workspace_dir.mkdir(parents=True, exist_ok=True)
(workspace_dir / "agent.yaml").write_text(
"prompt_files:\n - SOUL.md\n - CUSTOM.md\n",
encoding="utf-8",
)
(workspace_dir / "SOUL.md").write_text("System prompt content", encoding="utf-8")
(workspace_dir / "CUSTOM.md").write_text("Custom instructions", encoding="utf-8")
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
# Verify factory creates EvoAgent
agent = factory.create_analyst(
analyst_type="fundamentals_analyst",
model=MagicMock(),
formatter=MagicMock(),
)
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "fundamentals_analyst"
assert agent.config_name == "test_config"
def test_factory_creates_risk_manager(self, tmp_path):
"""Test that factory creates risk manager EvoAgent."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
return path
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
from unittest.mock import MagicMock
agent = factory.create_risk_manager(
model=MagicMock(),
formatter=MagicMock(),
)
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "risk_manager"
def test_factory_creates_portfolio_manager(self, tmp_path):
"""Test that factory creates portfolio manager EvoAgent with financial params."""
from backend.agents.unified_factory import UnifiedAgentFactory
from backend.agents.base.evo_agent import EvoAgent
class MockSkillsManager:
def get_agent_asset_dir(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id
path.mkdir(parents=True, exist_ok=True)
return path
def get_agent_active_root(self, config_name, agent_id):
path = tmp_path / "runs" / config_name / "agents" / agent_id / "skills" / "active"
path.mkdir(parents=True, exist_ok=True)
return path
def list_active_skill_metadata(self, config_name, agent_id):
return []
factory = UnifiedAgentFactory(
config_name="test_config",
skills_manager=MockSkillsManager(),
)
from unittest.mock import MagicMock
agent = factory.create_portfolio_manager(
model=MagicMock(),
formatter=MagicMock(),
initial_cash=50000.0,
margin_requirement=0.3,
)
assert isinstance(agent, EvoAgent)
assert agent.agent_id == "portfolio_manager"
class TestToolGuardIntegration:
"""Test ToolGuardMixin integration with EvoAgent."""
def test_tool_guard_intercepts_guarded_tools(self):
"""Test that ToolGuard intercepts tools requiring approval."""
from backend.agents.base.tool_guard import ToolGuardMixin
class TestAgent(ToolGuardMixin):
def __init__(self):
self._init_tool_guard()
self.agent_id = "test_agent"
self.workspace_id = "test_workspace"
self.session_id = "test_session"
agent = TestAgent()
# Verify place_order is in guarded tools
assert agent._is_tool_guarded("place_order") is True
assert agent._is_tool_denied("execute_shell_command") is True
def test_tool_guard_approval_flow(self):
"""Test the full approval flow for a guarded tool."""
from backend.agents.base.tool_guard import (
ToolGuardStore,
ApprovalStatus,
)
store = ToolGuardStore()
# Create a pending approval record
record = store.create_pending(
tool_name="place_order",
tool_input={"ticker": "AAPL", "quantity": 100},
agent_id="test_agent",
workspace_id="test_workspace",
)
assert record.status == ApprovalStatus.PENDING
assert record.tool_name == "place_order"
# Approve the request with resolved_by
updated = store.set_status(record.approval_id, ApprovalStatus.APPROVED, resolved_by="test_user")
assert updated.status == ApprovalStatus.APPROVED
assert updated.resolved_by == "test_user"
def test_tool_guard_default_lists(self):
"""Test default guarded and denied tool lists."""
from backend.agents.base.tool_guard import (
DEFAULT_GUARDED_TOOLS,
DEFAULT_DENIED_TOOLS,
)
# Critical tools should be guarded
assert "place_order" in DEFAULT_GUARDED_TOOLS
assert "modify_position" in DEFAULT_GUARDED_TOOLS
assert "write_file" in DEFAULT_GUARDED_TOOLS
assert "edit_file" in DEFAULT_GUARDED_TOOLS
# Dangerous tools should be denied
assert "execute_shell_command" in DEFAULT_DENIED_TOOLS
class TestEvoAgentWorkspaceIntegration:
"""Test EvoAgent workspace-driven configuration."""
def test_evo_agent_loads_prompt_files_from_workspace(self, tmp_path, monkeypatch):
"""Test that EvoAgent loads prompt files from workspace directory."""
from backend.agents.base.evo_agent import EvoAgent
workspace_dir = tmp_path / "runs" / "demo" / "agents" / "test_analyst"
workspace_dir.mkdir(parents=True, exist_ok=True)
# Create prompt files
(workspace_dir / "SOUL.md").write_text(
"You are a test analyst.", encoding="utf-8"
)
(workspace_dir / "INSTRUCTIONS.md").write_text(
"Additional instructions.", encoding="utf-8"
)
class MockToolkit:
def __init__(self, *args, **kwargs):
pass
def register_agent_skill(self, path):
pass
monkeypatch.setattr(
"backend.agents.base.evo_agent.Toolkit",
MockToolkit,
)
class MockSkillsManager:
def get_agent_active_root(self, config_name, agent_id):
return workspace_dir / "skills" / "active"
def list_active_skill_metadata(self, config_name, agent_id):
return []
agent = EvoAgent(
agent_id="test_analyst",
config_name="demo",
workspace_dir=workspace_dir,
model=MagicMock(),
formatter=MagicMock(),
skills_manager=MockSkillsManager(),
prompt_files=["SOUL.md", "INSTRUCTIONS.md"],
)
# Verify prompts are loaded into system prompt
assert "You are a test analyst." in agent._sys_prompt
assert "Additional instructions." in agent._sys_prompt
class TestFactoryCaching:
"""Test UnifiedAgentFactory caching behavior."""
def test_factory_cache_per_config(self, monkeypatch):
"""Test that factory is cached per config name."""
from backend.agents.unified_factory import (
get_agent_factory,
clear_factory_cache,
)
# Clear any existing cache
clear_factory_cache()
mock_skills_manager = MagicMock()
factory1 = get_agent_factory("config_a", mock_skills_manager)
factory2 = get_agent_factory("config_a", mock_skills_manager)
factory3 = get_agent_factory("config_b", mock_skills_manager)
# Same config should return same instance
assert factory1 is factory2
# Different config should return different instance
assert factory1 is not factory3
def test_clear_factory_cache(self):
"""Test that clear_factory_cache removes all cached factories."""
from backend.agents.unified_factory import (
get_agent_factory,
clear_factory_cache,
)
mock_skills_manager = MagicMock()
factory1 = get_agent_factory("config_c", mock_skills_manager)
clear_factory_cache()
factory2 = get_agent_factory("config_c", mock_skills_manager)
# After clearing cache, should be new instance
assert factory1 is not factory2