From e6d6a07d4dd127a6a72ed5497e2022e529f4a1c0 Mon Sep 17 00:00:00 2001 From: AuraCrimsonRose <66587640+AuroraCrimsonRose@users.noreply.github.com> Date: Wed, 3 Jun 2026 04:46:51 -0500 Subject: [PATCH] Add comprehensive test suites for all new tools (20+ tests) --- tests/test_code_analysis.py | 82 ++++++++++++++++++++++++++ tests/test_environment.py | 76 ++++++++++++++++++++++++ tests/test_git_ops.py | 111 ++++++++++++++++++++++++++++++++++++ tests/test_python_exec.py | 103 +++++++++++++++++++++++++++++++++ tests/test_search_code.py | 91 +++++++++++++++++++++++++++++ 5 files changed, 463 insertions(+) create mode 100644 tests/test_code_analysis.py create mode 100644 tests/test_environment.py create mode 100644 tests/test_git_ops.py create mode 100644 tests/test_python_exec.py create mode 100644 tests/test_search_code.py diff --git a/tests/test_code_analysis.py b/tests/test_code_analysis.py new file mode 100644 index 0000000..c931189 --- /dev/null +++ b/tests/test_code_analysis.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import pytest +from pathlib import Path +import tempfile + +from core.tools.base import ToolContext +from tools.code_analysis import CodeAnalysisTool + + +class TestCodeAnalysisTool: + """Test code analysis tool.""" + + @pytest.fixture + def tool(self) -> CodeAnalysisTool: + return CodeAnalysisTool() + + @pytest.fixture + def sample_py_file(self) -> Path: + """Create a sample Python file for testing.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write( + '''"""Sample module.""" + +def hello(name: str) -> str: + """Say hello.""" + return f"Hello {name}" + +class Calculator: + """Simple calculator.""" + + def add(self, a: int, b: int) -> int: + """Add two numbers.""" + return a + b + +import os +import sys +''' + ) + return Path(f.name) + + def test_analyze_file_valid(self, tool: CodeAnalysisTool, sample_py_file: Path) -> None: + """Test analyzing a valid Python file.""" + result = tool.analyze_file({"path": str(sample_py_file)}) + assert result["valid_syntax"] is True + assert result["functions"] >= 1 + assert result["classes"] >= 1 + assert result["imports"] >= 2 + + def test_analyze_file_invalid_type(self, tool: CodeAnalysisTool) -> None: + """Test with invalid path type.""" + with pytest.raises(ValueError, match="path must be string"): + tool.analyze_file({"path": 123}) + + def test_extract_functions(self, tool: CodeAnalysisTool, sample_py_file: Path) -> None: + """Test extracting functions from file.""" + result = tool.extract_functions({"path": str(sample_py_file)}) + assert result["count"] >= 2 + assert any(f["name"] == "hello" for f in result["functions"]) + assert any(f["name"] == "add" for f in result["functions"]) + + def test_check_complexity(self, tool: CodeAnalysisTool, sample_py_file: Path) -> None: + """Test complexity calculation.""" + result = tool.check_complexity({"path": str(sample_py_file)}) + assert "functions" in result + assert "avg_complexity" in result + assert result["avg_complexity"] > 0 + + def test_execute_analyze(self, tool: CodeAnalysisTool, sample_py_file: Path) -> None: + """Test execute with analyze_file action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute( + {"action": "analyze_file", "path": str(sample_py_file)}, + ctx + ) + assert result["valid_syntax"] is True + + def test_execute_invalid_action(self, tool: CodeAnalysisTool) -> None: + """Test with invalid action.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="Unknown analysis action"): + tool.execute({"action": "invalid_action"}, ctx) diff --git a/tests/test_environment.py b/tests/test_environment.py new file mode 100644 index 0000000..fb7f286 --- /dev/null +++ b/tests/test_environment.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import pytest +import sys +import os +import platform + +from core.tools.base import ToolContext +from tools.environment import EnvironmentTool + + +class TestEnvironmentTool: + """Test environment tool.""" + + @pytest.fixture + def tool(self) -> EnvironmentTool: + return EnvironmentTool() + + def test_get_info(self, tool: EnvironmentTool) -> None: + """Test getting system info.""" + result = tool.get_info() + assert "system" in result + assert "machine" in result + assert "hostname" in result + assert result["system"] in ["Windows", "Linux", "Darwin"] + + def test_python_info(self, tool: EnvironmentTool) -> None: + """Test getting Python info.""" + result = tool.get_python_info() + assert "version" in result + assert "implementation" in result + assert "executable" in result + assert result["version"] == platform.python_version() + + def test_env_var(self, tool: EnvironmentTool) -> None: + """Test getting environment variable.""" + os.environ["TEST_VAR"] = "test_value" + result = tool.get_env_var({"name": "TEST_VAR"}) + assert result["exists"] is True + assert result["value"] == "test_value" + + def test_env_var_nonexistent(self, tool: EnvironmentTool) -> None: + """Test getting nonexistent environment variable.""" + result = tool.get_env_var({"name": "NONEXISTENT_VAR_12345"}) + assert result["exists"] is False + assert result["value"] is None + + def test_env_var_invalid_type(self, tool: EnvironmentTool) -> None: + """Test with invalid name type.""" + with pytest.raises(ValueError, match="name must be string"): + tool.get_env_var({"name": 123}) + + def test_all_env_vars(self, tool: EnvironmentTool) -> None: + """Test getting all environment variables.""" + result = tool.get_all_env_vars() + assert "count" in result + assert "vars" in result + assert result["count"] > 0 + + def test_execute_info(self, tool: EnvironmentTool) -> None: + """Test execute with info action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute({"action": "info"}, ctx) + assert "system" in result + + def test_execute_python_info(self, tool: EnvironmentTool) -> None: + """Test execute with python_info action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute({"action": "python_info"}, ctx) + assert "version" in result + + def test_execute_invalid_action(self, tool: EnvironmentTool) -> None: + """Test with invalid action.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="Unknown environment action"): + tool.execute({"action": "invalid_action"}, ctx) diff --git a/tests/test_git_ops.py b/tests/test_git_ops.py new file mode 100644 index 0000000..6bc4da2 --- /dev/null +++ b/tests/test_git_ops.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import pytest +from pathlib import Path +import tempfile +import subprocess + +from core.tools.base import ToolContext +from tools.git_ops import GitOpsTool + + +class TestGitOpsTool: + """Test git operations tool.""" + + @pytest.fixture + def tool(self) -> GitOpsTool: + return GitOpsTool() + + @pytest.fixture + def git_repo(self) -> Path: + """Create a temporary git repository.""" + tmpdir = Path(tempfile.mkdtemp()) + + subprocess.run( + ["git", "init"], + cwd=str(tmpdir), + capture_output=True + ) + subprocess.run( + ["git", "config", "user.email", "test@test.com"], + cwd=str(tmpdir), + capture_output=True + ) + subprocess.run( + ["git", "config", "user.name", "Test User"], + cwd=str(tmpdir), + capture_output=True + ) + + (tmpdir / "test.txt").write_text("test content") + subprocess.run( + ["git", "add", "test.txt"], + cwd=str(tmpdir), + capture_output=True + ) + subprocess.run( + ["git", "commit", "-m", "Initial commit"], + cwd=str(tmpdir), + capture_output=True + ) + + yield tmpdir + + def test_git_status(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test git status.""" + result = tool.git_status({"repo": str(git_repo)}) + assert "repo" in result + assert "status_lines" in result + assert "has_changes" in result + + def test_git_status_not_repo(self, tool: GitOpsTool) -> None: + """Test git status on non-git directory.""" + with tempfile.TemporaryDirectory() as tmpdir: + with pytest.raises(ValueError, match="Not a git repository"): + tool.git_status({"repo": tmpdir}) + + def test_git_log(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test git log.""" + result = tool.git_log({"repo": str(git_repo), "limit": 5}) + assert "commits" in result + assert "count" in result + assert result["count"] >= 1 + + def test_git_current_branch(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test getting current branch.""" + result = tool.git_current_branch({"repo": str(git_repo)}) + assert "branch" in result + assert result["branch"] in ["master", "main"] + + def test_git_list_branches(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test listing branches.""" + result = tool.git_list_branches({"repo": str(git_repo)}) + assert "branches" in result + assert "count" in result + assert result["count"] >= 1 + + def test_git_diff(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test git diff.""" + (git_repo / "test.txt").write_text("modified content") + + result = tool.git_diff({"repo": str(git_repo)}) + assert "repo" in result + assert "diff" in result + + def test_execute_status(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test execute with status action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute( + {"action": "status", "repo": str(git_repo)}, + ctx + ) + assert "status_lines" in result + + def test_execute_invalid_action(self, tool: GitOpsTool, git_repo: Path) -> None: + """Test with invalid action.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="Unknown git action"): + tool.execute( + {"action": "invalid_action", "repo": str(git_repo)}, + ctx + ) diff --git a/tests/test_python_exec.py b/tests/test_python_exec.py new file mode 100644 index 0000000..12de70d --- /dev/null +++ b/tests/test_python_exec.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import pytest +from pathlib import Path +import tempfile + +from core.tools.base import ToolContext +from tools.python_exec import PythonExecTool + + +class TestPythonExecTool: + """Test Python execution tool.""" + + @pytest.fixture + def tool(self) -> PythonExecTool: + return PythonExecTool() + + def test_execute_snippet_simple(self, tool: PythonExecTool) -> None: + """Test executing a simple code snippet.""" + ctx = ToolContext(dry_run=False) + result = tool.execute_snippet( + {"code": "x = 1 + 1\nprint(x)"}, + ctx + ) + assert result["status"] == "success" + assert "2" in result["output"] + + def test_execute_snippet_with_error(self, tool: PythonExecTool) -> None: + """Test executing code with error.""" + ctx = ToolContext(dry_run=False) + result = tool.execute_snippet( + {"code": "x = 1 / 0"}, + ctx + ) + assert result["status"] == "error" + assert "ZeroDivisionError" in result["error_type"] + + def test_execute_snippet_invalid_code_type(self, tool: PythonExecTool) -> None: + """Test with invalid code type.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="code must be string"): + tool.execute_snippet({"code": 123}, ctx) + + def test_execute_snippet_dry_run(self, tool: PythonExecTool) -> None: + """Test snippet execution in dry-run mode.""" + ctx = ToolContext(dry_run=True) + result = tool.execute_snippet( + {"code": "x = 1 + 1"}, + ctx + ) + assert result["dry_run"] is True + assert result["message"] == "Would execute code (dry-run mode)" + + def test_execute_script(self, tool: PythonExecTool) -> None: + """Test executing a Python script.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write("print('hello from script')\n") + script_path = f.name + + try: + ctx = ToolContext(dry_run=False) + result = tool.execute_script( + {"script": script_path, "timeout": 10}, + ctx + ) + assert result["status"] == "success" + assert "hello from script" in result["stdout"] + finally: + Path(script_path).unlink() + + def test_execute_script_nonexistent(self, tool: PythonExecTool) -> None: + """Test executing nonexistent script.""" + with pytest.raises(ValueError, match="Script not found"): + tool.execute_script({"script": "/nonexistent/script.py"}, ToolContext()) + + def test_execute_script_dry_run(self, tool: PythonExecTool) -> None: + """Test script execution in dry-run mode.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write("print('test')\n") + script_path = f.name + + try: + ctx = ToolContext(dry_run=True) + result = tool.execute_script({"script": script_path}, ctx) + assert result["dry_run"] is True + finally: + Path(script_path).unlink() + + def test_execute_action_snippet(self, tool: PythonExecTool) -> None: + """Test execute with snippet action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute( + {"action": "snippet", "code": "x = 42\nprint(x)"}, + ctx + ) + assert result["status"] == "success" + assert "42" in result["output"] + + def test_execute_invalid_action(self, tool: PythonExecTool) -> None: + """Test with invalid action.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="Unknown exec action"): + tool.execute({"action": "invalid_action"}, ctx) diff --git a/tests/test_search_code.py b/tests/test_search_code.py new file mode 100644 index 0000000..9795cdf --- /dev/null +++ b/tests/test_search_code.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import pytest +from pathlib import Path +import tempfile + +from core.tools.base import ToolContext +from tools.search_code import SearchCodeTool + + +class TestSearchCodeTool: + """Test code search tool.""" + + @pytest.fixture + def tool(self) -> SearchCodeTool: + return SearchCodeTool() + + @pytest.fixture + def temp_dir(self) -> Path: + """Create a temporary directory with test files.""" + tmpdir = Path(tempfile.mkdtemp()) + + (tmpdir / "test1.py").write_text("def hello():\n print('hello')\n") + (tmpdir / "test2.py").write_text("def world():\n print('world')\n") + (tmpdir / "data.txt").write_text("some data\n") + + yield tmpdir + + def test_search_literal(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test literal string search.""" + result = tool.search_literal({ + "pattern": "def", + "path": str(temp_dir), + "file_pattern": "*.py" + }) + assert result["pattern"] == "def" + assert result["matches_found"] >= 2 + + def test_search_literal_no_matches(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test literal search with no matches.""" + result = tool.search_literal({ + "pattern": "nonexistent_pattern", + "path": str(temp_dir), + "file_pattern": "*.py" + }) + assert result["matches_found"] == 0 + + def test_search_regex(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test regex search.""" + result = tool.search_regex({ + "pattern": r"def \w+", + "path": str(temp_dir), + "file_pattern": "*.py" + }) + assert result["matches_found"] >= 2 + + def test_search_regex_invalid(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test regex search with invalid pattern.""" + with pytest.raises(ValueError, match="Invalid regex"): + tool.search_regex({ + "pattern": "[invalid", + "path": str(temp_dir) + }) + + def test_search_in_file(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test search within a specific file.""" + test_file = temp_dir / "test1.py" + result = tool.search_in_file({ + "path": str(test_file), + "pattern": "hello" + }) + assert result["count"] >= 1 + + def test_execute_literal(self, tool: SearchCodeTool, temp_dir: Path) -> None: + """Test execute with literal action.""" + ctx = ToolContext(dry_run=False) + result = tool.execute( + { + "action": "literal", + "pattern": "def", + "path": str(temp_dir) + }, + ctx + ) + assert result["matches_found"] >= 2 + + def test_execute_invalid_action(self, tool: SearchCodeTool) -> None: + """Test with invalid action.""" + ctx = ToolContext(dry_run=False) + with pytest.raises(ValueError, match="Unknown search action"): + tool.execute({"action": "invalid_action"}, ctx)