Stack-2-9-finetuned / samples /unit /test_tools.py
walidsobhie-code
refactor: Squeeze folders further - cleaner structure
65888d5
#!/usr/bin/env python3
"""
Unit Tests for Stack 2.9 Tools Module
Tests all 37+ tools: file operations, git, code execution, web, memory, and task planning.
"""
import pytest
import sys
import json
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch, mock_open
# Add stack_cli to path
sys.path.insert(0, str(Path(__file__).parent.parent / "stack_cli"))
from stack_cli.tools import (
TOOLS,
get_tool,
list_tools,
get_tool_schemas,
tool_read_file,
tool_write_file,
tool_edit_file,
tool_search_files,
tool_grep,
tool_copy_file,
tool_move_file,
tool_delete_file,
tool_git_status,
tool_git_commit,
tool_git_push,
tool_git_pull,
tool_git_branch,
tool_git_log,
tool_git_diff,
tool_run_command,
tool_run_tests,
tool_lint_code,
tool_format_code,
tool_check_type,
tool_start_server,
tool_install_dependencies,
tool_web_search,
tool_web_fetch,
tool_download_file,
tool_check_url,
tool_screenshot,
tool_memory_recall,
tool_memory_save,
tool_memory_list,
tool_context_load,
tool_project_scan,
tool_create_task,
tool_list_tasks,
tool_update_task,
tool_delete_task,
tool_create_plan,
tool_execute_plan,
)
class TestToolsRegistry:
"""Test tools registry."""
def test_tools_count(self):
"""Verify we have 37+ tools."""
tools = list_tools()
assert len(tools) >= 37, f"Expected 37+ tools, got {len(tools)}"
def test_get_tool_valid(self):
"""Test getting a valid tool."""
tool = get_tool("read")
assert tool is not None
assert callable(tool)
def test_get_tool_invalid(self):
"""Test getting an invalid tool."""
tool = get_tool("nonexistent_tool")
assert tool is None
def test_get_tool_schemas(self):
"""Test getting tool schemas."""
schemas = get_tool_schemas()
assert isinstance(schemas, list)
assert len(schemas) > 0
class TestFileOperations:
"""Test file operation tools."""
def test_read_file_success(self, temp_file):
"""Test reading a file."""
result = tool_read_file(str(temp_file))
assert result["success"] is True
assert "content" in result
assert "Line 1" in result["content"]
def test_read_file_not_found(self):
"""Test reading nonexistent file."""
result = tool_read_file("/nonexistent/file.txt")
assert result["success"] is False
assert "error" in result
def test_read_file_with_limit(self, temp_file):
"""Test reading file with limit."""
result = tool_read_file(str(temp_file), limit=2)
assert result["success"] is True
lines = result["content"].split('\n')
assert len(lines) <= 3
def test_write_file_success(self, temp_workspace):
"""Test writing a file."""
path = temp_workspace / "written.txt"
result = tool_write_file(str(path), "Hello World")
assert result["success"] is True
assert path.exists()
assert path.read_text() == "Hello World"
def test_write_file_creates_dirs(self, temp_workspace):
"""Test writing creates parent directories."""
path = temp_workspace / "subdir" / "nested" / "file.txt"
result = tool_write_file(str(path), "content")
assert result["success"] is True
assert path.exists()
def test_edit_file_success(self, temp_file):
"""Test editing a file."""
result = tool_edit_file(str(temp_file), "Line 1", "Line ONE")
assert result["success"] is True
content = temp_file.read_text()
assert "Line ONE" in content
def test_edit_file_not_found(self):
"""Test editing nonexistent file."""
result = tool_edit_file("/nonexistent/file.txt", "old", "new")
assert result["success"] is False
def test_edit_file_text_not_found(self, temp_file):
"""Test editing with non-existent text."""
result = tool_edit_file(str(temp_file), "NonExistentText", "new")
assert result["success"] is False
def test_search_files(self, temp_project):
"""Test searching for files."""
result = tool_search_files(str(temp_project), "*.py")
assert result["success"] is True
assert "matches" in result
def test_grep_basic(self, temp_file):
"""Test grep functionality."""
result = tool_grep(str(temp_file), "Line 1")
assert result["success"] is True
assert "matches" in result
assert result["count"] > 0
def test_grep_with_context(self, temp_file):
"""Test grep with context."""
result = tool_grep(str(temp_file), "Line", context=1)
assert result["success"] is True
if result["matches"]:
assert "context" in result["matches"][0]
def test_copy_file(self, temp_file, temp_workspace):
"""Test copying a file."""
dest = temp_workspace / "copied.txt"
result = tool_copy_file(str(temp_file), str(dest))
assert result["success"] is True
assert dest.exists()
def test_move_file(self, temp_file, temp_workspace):
"""Test moving a file."""
dest = temp_workspace / "moved.txt"
result = tool_move_file(str(temp_file), str(dest))
assert result["success"] is True
assert dest.exists()
def test_delete_file_without_force(self, temp_file):
"""Test delete without force."""
result = tool_delete_file(str(temp_file))
assert result["success"] is True
assert "would_delete" in result
def test_delete_file_with_force(self, temp_file):
"""Test delete with force."""
result = tool_delete_file(str(temp_file), force=True)
assert result["success"] is True
assert not temp_file.exists()
class TestGitOperations:
"""Test git operation tools."""
def test_git_status_no_repo(self):
"""Test git status on non-repo."""
result = tool_git_status("/nonexistent")
assert result["success"] is False
assert "error" in result
@patch('subprocess.run')
def test_git_status_success(self, mock_run, temp_git_repo):
"""Test git status success."""
mock_result = MagicMock()
mock_result.stdout = " M modified.py\nA added.py\n"
mock_run.return_value = mock_result
result = tool_git_status(str(temp_git_repo))
assert result["success"] is True
@patch('subprocess.run')
def test_git_commit(self, mock_run, temp_git_repo):
"""Test git commit."""
mock_result = MagicMock()
mock_result.stdout = "[main abc123] Test commit"
mock_result.stderr = ""
mock_run.return_value = mock_result
result = tool_git_commit(str(temp_git_repo), "Test commit")
assert result["success"] is True
@patch('subprocess.run')
def test_git_push(self, mock_run):
"""Test git push."""
mock_result = MagicMock()
mock_result.stdout = "To github.com:test/test.git\n abc123..def456 main -> main\n"
mock_run.return_value = mock_result
result = tool_git_push(str(temp_git_repo))
assert result["success"] is True
@patch('subprocess.run')
def test_git_pull(self, mock_run):
"""Test git pull."""
mock_result = MagicMock()
mock_result.stdout = "Updating abc123..def456\n"
mock_run.return_value = mock_result
result = tool_git_pull(str(temp_git_repo))
assert result["success"] is True
@patch('subprocess.run')
def test_git_branch_list(self, mock_run):
"""Test listing branches."""
mock_result = MagicMock()
mock_result.stdout = "* main\n develop\n feature/test\n"
mock_run.return_value = mock_result
result = tool_git_branch(str(temp_git_repo))
assert result["success"] is True
assert "branches" in result
@patch('subprocess.run')
def test_git_log(self, mock_run):
"""Test git log."""
mock_result = MagicMock()
mock_result.stdout = "abc123 Commit message 1\ndef456 Commit message 2\n"
mock_run.return_value = mock_result
result = tool_git_log(str(temp_git_repo))
assert result["success"] is True
@patch('subprocess.run')
def test_git_diff(self, mock_run):
"""Test git diff."""
mock_result = MagicMock()
mock_result.stdout = "diff --git a/test.py b/test.py\n+new line\n"
mock_run.return_value = mock_result
result = tool_git_diff(str(temp_git_repo))
assert result["success"] is True
class TestCodeExecution:
"""Test code execution tools."""
@patch('subprocess.run')
def test_run_command_success(self, mock_run):
"""Test running a command successfully."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "output"
mock_result.stderr = ""
mock_run.return_value = mock_result
result = tool_run_command("echo hello")
assert result["success"] is True
assert result["stdout"] == "output"
@patch('subprocess.run')
def test_run_command_failure(self, mock_run):
"""Test running a failing command."""
mock_result = MagicMock()
mock_result.returncode = 1
mock_result.stdout = ""
mock_result.stderr = "error"
mock_run.return_value = mock_result
result = tool_run_command("false")
assert result["success"] is False
@patch('subprocess.run')
def test_run_command_timeout(self, mock_run):
"""Test command timeout."""
mock_run.side_effect = subprocess.TimeoutExpired("cmd", 1)
result = tool_run_command("sleep 100", timeout=1)
assert result["success"] is False
assert "timeout" in result["error"].lower()
@patch('subprocess.run')
def test_run_tests(self, mock_run):
"""Test running tests."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "test output"
mock_result.stderr = ""
mock_run.return_value = mock_result
result = tool_run_tests(".")
assert "success" in result
@patch('subprocess.run')
def test_lint_code(self, mock_run):
"""Test linting code."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "lint output"
mock_run.return_value = mock_result
result = tool_lint_code(".")
assert "success" in result
@patch('subprocess.run')
def test_format_code(self, mock_run):
"""Test formatting code."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_run.return_value = mock_result
result = tool_format_code(".")
assert "success" in result
@patch('subprocess.run')
def test_check_type(self, mock_run):
"""Test type checking."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_run.return_value = mock_result
result = tool_check_type(".")
assert "success" in result
@patch('subprocess.Popen')
def test_start_server_background(self, mock_popen):
"""Test starting server in background."""
mock_proc = MagicMock()
mock_proc.pid = 12345
mock_popen.return_value = mock_proc
result = tool_start_server("python server.py", 8000, background=True)
assert result["success"] is True
assert "pid" in result
class TestWebTools:
"""Test web tools."""
@patch('subprocess.run')
def test_web_search(self, mock_run):
"""Test web search."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = '[{"title": "Result", "url": "http://example.com"}]'
mock_run.return_value = mock_result
result = tool_web_search("python")
assert result["success"] is True
assert "results" in result
@patch('subprocess.run')
def test_web_fetch(self, mock_run):
"""Test web fetch."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "<html>test</html>"
mock_run.return_value = mock_result
result = tool_web_fetch("http://example.com")
assert result["success"] is True
assert "content" in result
@patch('subprocess.run')
def test_check_url(self, mock_run):
"""Test URL check."""
mock_result = MagicMock()
mock_result.stdout = "200"
mock_run.return_value = mock_result
result = tool_check_url("http://example.com")
assert result["success"] is True
class TestMemoryTools:
"""Test memory tools."""
@patch('pathlib.Path.read_text')
def test_memory_recall(self, mock_read):
"""Test memory recall."""
mock_read.return_value = "### test\ntest content"
result = tool_memory_recall("test")
assert result["success"] is True
@patch('pathlib.Path.write_text')
def test_memory_save(self, mock_write):
"""Test memory save."""
with patch('pathlib.Path.exists', return_value=True):
result = tool_memory_save("test_key", "test_value")
assert result["success"] is True
@patch('pathlib.Path.exists')
def test_memory_list(self, mock_exists):
"""Test memory list."""
mock_exists.return_value = False
result = tool_memory_list()
assert result["success"] is True
@patch('pathlib.Path.read_text')
def test_context_load(self, mock_read):
"""Test context load."""
mock_read.return_value = "# Context"
result = tool_context_load()
assert result["success"] is True
def test_project_scan(self, temp_project):
"""Test project scan."""
result = tool_project_scan(str(temp_project))
assert result["success"] is True
assert "project" in result
class TestTaskPlanningTools:
"""Test task planning tools."""
@patch('pathlib.Path.write_text')
def test_create_task(self, mock_write):
"""Test creating a task."""
with patch('pathlib.Path.exists', return_value=False):
result = tool_create_task("Test task", "Description", "high")
assert result["success"] is True
assert "task" in result
@patch('pathlib.Path.read_text')
def test_list_tasks(self, mock_read):
"""Test listing tasks."""
mock_read.return_value = "[]"
result = tool_list_tasks()
assert result["success"] is True
@patch('pathlib.Path.read_text')
@patch('pathlib.Path.write_text')
def test_update_task(self, mock_write, mock_read):
"""Test updating a task."""
mock_read.return_value = '[{"id": "test123", "title": "Test"}]'
result = tool_update_task("test123", status="completed")
assert result["success"] is True
@patch('pathlib.Path.read_text')
@patch('pathlib.Path.write_text')
def test_delete_task(self, mock_write, mock_read):
"""Test deleting a task."""
mock_read.return_value = '[{"id": "test123", "title": "Test"}]'
result = tool_delete_task("test123")
assert result["success"] is True
@patch('pathlib.Path.write_text')
def test_create_plan(self, mock_write):
"""Test creating a plan."""
with patch('pathlib.Path.exists', return_value=False):
result = tool_create_plan("Goal", ["step1", "step2"])
assert result["success"] is True
@patch('pathlib.Path.read_text')
@patch('pathlib.Path.write_text')
def test_execute_plan(self, mock_write, mock_read):
"""Test executing a plan."""
mock_read.return_value = '[{"id": "plan1", "goal": "Goal", "steps": ["step1"]}]'
result = tool_execute_plan("plan1")
assert result["success"] is True
if __name__ == "__main__":
pytest.main([__file__, "-v"])