test(agents): add Code Commander unit tests for Phases 1-3

Tests Phase 1: state, prompts, tools registry
Tests Phase 2: AI adapters, security classifier, direct executor
Tests Phase 3: schemas (CodeTask, CodeExecutionResult, enums)
This commit is contained in:
2026-04-05 15:02:23 +08:00
parent 5667190abe
commit f033fb5879

View File

@@ -0,0 +1,265 @@
"""
Tests for Code Commander module (Phases 1-3)
"""
import sys
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
sys.modules.setdefault("trafilatura", Mock())
from app.agents.state import AgentRole
from app.agents.prompts import (
CODE_COMMANDER_SYSTEM_PROMPT,
DIRECT_EXECUTION_PROMPT,
SANDBOX_EXECUTION_PROMPT,
TOP_LEVEL_SYSTEM_PROMPTS_BY_KEY,
)
from app.agents.tools import CODE_COMMANDER_TOOLSET_NAMES
from app.agents.registry.builtins import (
TOP_LEVEL_AGENT_DEFAULT_SUB_COMMANDERS,
TOP_LEVEL_AGENT_DISPLAY_NAMES,
TOP_LEVEL_AGENT_ROUTING_HINTS,
TOP_LEVEL_AGENT_ALLOWED_SPAWN_ROLES,
)
from app.agents.tools.ai_adapter import (
AICLIAdapter,
ADAPTER_REGISTRY,
get_adapter,
CodeExecutionResult,
ClaudeAdapter,
GeminiAdapter,
CodexAdapter,
OpenCodeAdapter,
)
from app.agents.tools.security_classifier import (
RiskLevel,
SecurityClassifier,
)
from app.agents.tools.direct_executor import DirectExecutor
from app.agents.schemas.task import (
CodeProviderType,
RiskLevelType,
CodeTaskType,
CodeTask,
CodeExecutionResultSchema,
)
# =============================================================================
# Phase 1: Infrastructure Tests
# =============================================================================
class TestPhase1State:
def test_code_commander_role_exists(self):
assert hasattr(AgentRole, "CODE_COMMANDER")
assert AgentRole.CODE_COMMANDER.value == "code_commander"
def test_code_commander_in_top_level_agents(self):
"""CODE_COMMANDER should be registered in TOP_LEVEL_AGENT entries"""
assert "code_commander" in TOP_LEVEL_AGENT_DEFAULT_SUB_COMMANDERS
assert "code_commander" in TOP_LEVEL_AGENT_DISPLAY_NAMES
assert "code_commander" in TOP_LEVEL_AGENT_ROUTING_HINTS
assert "code_commander" in TOP_LEVEL_AGENT_ALLOWED_SPAWN_ROLES
def test_code_commander_has_no_sub_commanders(self):
"""Code Commander is a top-level agent with no sub-commanders"""
idx = list(TOP_LEVEL_AGENT_DEFAULT_SUB_COMMANDERS.keys()).index("code_commander")
assert TOP_LEVEL_AGENT_DEFAULT_SUB_COMMANDERS["code_commander"] == ()
class TestPhase1Prompts:
def test_code_commander_system_prompt_exists(self):
assert CODE_COMMANDER_SYSTEM_PROMPT is not None
assert len(CODE_COMMANDER_SYSTEM_PROMPT) > 0
def test_code_commander_prompt_in_top_level_prompts(self):
assert "code_commander" in TOP_LEVEL_SYSTEM_PROMPTS_BY_KEY
assert TOP_LEVEL_SYSTEM_PROMPTS_BY_KEY["code_commander"] == CODE_COMMANDER_SYSTEM_PROMPT
def test_sandbox_execution_prompt_exists(self):
assert SANDBOX_EXECUTION_PROMPT is not None
assert len(SANDBOX_EXECUTION_PROMPT) > 0
def test_direct_execution_prompt_exists(self):
assert DIRECT_EXECUTION_PROMPT is not None
assert "直接" in DIRECT_EXECUTION_PROMPT or "direct" in DIRECT_EXECUTION_PROMPT.lower()
class TestPhase1Tools:
def test_code_commander_toolset_names_defined(self):
assert CODE_COMMANDER_TOOLSET_NAMES is not None
assert isinstance(CODE_COMMANDER_TOOLSET_NAMES, list)
assert len(CODE_COMMANDER_TOOLSET_NAMES) == 5
def test_code_commander_tools_includes_execute_code_task(self):
assert "execute_code_task" in CODE_COMMANDER_TOOLSET_NAMES
def test_code_commander_tools_includes_get_execution_status(self):
assert "get_execution_status" in CODE_COMMANDER_TOOLSET_NAMES
def test_code_commander_tools_includes_send_interactive_input(self):
assert "send_interactive_input" in CODE_COMMANDER_TOOLSET_NAMES
def test_code_commander_tools_includes_download_workspace(self):
assert "download_workspace" in CODE_COMMANDER_TOOLSET_NAMES
def test_code_commander_tools_includes_cleanup_workspace(self):
assert "cleanup_workspace" in CODE_COMMANDER_TOOLSET_NAMES
# =============================================================================
# Phase 2: Execution Engine Tests
# =============================================================================
class TestPhase2AIAdapter:
def test_adapter_registry_has_all_providers(self):
assert "claude" in ADAPTER_REGISTRY
assert "gemini" in ADAPTER_REGISTRY
assert "codex" in ADAPTER_REGISTRY
assert "opencode" in ADAPTER_REGISTRY
def test_get_adapter_returns_correct_adapter(self):
assert isinstance(get_adapter("claude"), ClaudeAdapter)
assert isinstance(get_adapter("gemini"), GeminiAdapter)
assert isinstance(get_adapter("codex"), CodexAdapter)
assert isinstance(get_adapter("opencode"), OpenCodeAdapter)
def test_get_adapter_raises_for_unknown_provider(self):
with pytest.raises(ValueError, match="Unknown AI provider"):
get_adapter("unknown")
def test_claude_adapter_properties(self):
adapter = ClaudeAdapter()
assert adapter.cli_name == "claude"
assert adapter.requires_workspace is True
assert adapter.provider == "claude"
def test_gemini_adapter_properties(self):
adapter = GeminiAdapter()
assert adapter.cli_name == "gemini"
assert adapter.requires_workspace is False
assert adapter.provider == "gemini"
def test_codex_adapter_properties(self):
adapter = CodexAdapter()
assert adapter.cli_name == "codex"
assert adapter.requires_workspace is True
assert adapter.provider == "codex"
def test_opencode_adapter_properties(self):
adapter = OpenCodeAdapter()
assert adapter.cli_name == "opencode"
assert adapter.requires_workspace is True
assert adapter.provider == "opencode"
def test_code_execution_result_dataclass(self):
result = CodeExecutionResult(
success=True,
message="Test message",
output="Test output",
files_created=["file1.py"],
)
assert result.success is True
assert result.message == "Test message"
assert result.output == "Test output"
assert result.files_created == ["file1.py"]
assert result.error is None
assert result.exit_code == 0
class TestPhase2SecurityClassifier:
def test_risk_level_enum_values(self):
assert RiskLevel.LOW.value == "low"
assert RiskLevel.HIGH.value == "high"
def test_security_classifier_classify_low_risk_demo(self):
classifier = SecurityClassifier()
result = classifier.classify("写一个贪食蛇 demo")
assert result == RiskLevel.LOW
def test_security_classifier_classify_low_risk_simple(self):
classifier = SecurityClassifier()
result = classifier.classify("帮我写一个 hello world")
assert result == RiskLevel.LOW
def test_security_classifier_classify_high_risk_dangerous(self):
classifier = SecurityClassifier()
result = classifier.classify("删除所有文件 rm -rf")
assert result == RiskLevel.HIGH
def test_security_classifier_classify_high_risk_format(self):
classifier = SecurityClassifier()
result = classifier.classify("格式化硬盘 sudo mkfs")
assert result == RiskLevel.HIGH
def test_security_classifier_get_risk_factors(self):
classifier = SecurityClassifier()
factors = classifier.get_risk_factors("删除系统文件")
assert len(factors) > 0
class TestPhase2DirectExecutor:
def test_direct_executor_initialization(self):
adapter = ClaudeAdapter()
executor = DirectExecutor(adapter)
assert executor is not None
assert executor.adapter is adapter
def test_direct_executor_is_installed_returns_bool(self):
adapter = ClaudeAdapter()
executor = DirectExecutor(adapter)
# is_installed is on the adapter, not the executor
result = executor.adapter.is_installed()
assert isinstance(result, bool)
# =============================================================================
# Phase 3: Schema Tests
# =============================================================================
class TestPhase3Schemas:
def test_code_provider_type_enum(self):
assert CodeProviderType.CLAUDE.value == "claude"
assert CodeProviderType.GEMINI.value == "gemini"
assert CodeProviderType.CODEX.value == "codex"
assert CodeProviderType.OPENCODE.value == "opencode"
def test_risk_level_type_enum(self):
assert RiskLevelType.LOW.value == "low"
assert RiskLevelType.HIGH.value == "high"
def test_code_task_type_enum(self):
assert CodeTaskType.DEMO.value == "demo"
assert CodeTaskType.PROJECT.value == "project"
assert CodeTaskType.MODIFICATION.value == "modification"
def test_code_task_model(self):
task = CodeTask(
task_type=CodeTaskType.DEMO,
ai_provider=CodeProviderType.CLAUDE,
sandbox_mode=False,
user_prompt="写一个贪食蛇",
)
assert task.task_type == CodeTaskType.DEMO
assert task.ai_provider == CodeProviderType.CLAUDE
assert task.sandbox_mode is False
assert task.user_prompt == "写一个贪食蛇"
assert task.task_id is not None
def test_code_execution_result_schema(self):
result = CodeExecutionResultSchema(
success=True,
message="执行成功",
output="Hello World",
files_created=["main.py"],
)
assert result.success is True
assert result.message == "执行成功"
assert result.output == "Hello World"
assert result.files_created == ["main.py"]