From f033fb5879c136dcf68eaea031f0791cdfee58f5 Mon Sep 17 00:00:00 2001 From: "WIN-JHFT4D3SIVT\\caoxiaozhu" Date: Sun, 5 Apr 2026 15:02:23 +0800 Subject: [PATCH] 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) --- .../backend/app/agents/test_code_commander.py | 265 ++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 backend/tests/backend/app/agents/test_code_commander.py diff --git a/backend/tests/backend/app/agents/test_code_commander.py b/backend/tests/backend/app/agents/test_code_commander.py new file mode 100644 index 0000000..d6abe6e --- /dev/null +++ b/backend/tests/backend/app/agents/test_code_commander.py @@ -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"]