mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-23 20:23:00 +00:00
- Update shell tools: 11→12 (cmd replaces dag, added fish/dash) - Update memory tools: 1→9 (individual tools not unified) - Make database/editor tests optional with skipif - Update total expected count: 40→38
321 lines
9.8 KiB
Python
321 lines
9.8 KiB
Python
"""Test all hanzo-tools-* packages import and register correctly."""
|
|
|
|
import sys
|
|
import time
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
|
|
def _module_installed(module_name: str) -> bool:
|
|
"""Check if a module is installed."""
|
|
try:
|
|
__import__(module_name)
|
|
return True
|
|
except ImportError:
|
|
return False
|
|
|
|
|
|
class TestToolPackages:
|
|
"""Test that all tool packages import correctly."""
|
|
|
|
def test_core_imports(self):
|
|
"""Test hanzo-tools-core imports."""
|
|
from hanzo_tools.core import BaseTool, ToolContext
|
|
from hanzo_tools.core.decorators import auto_timeout
|
|
from hanzo_tools.core.permissions import PermissionManager
|
|
|
|
assert BaseTool is not None
|
|
assert ToolContext is not None
|
|
assert PermissionManager is not None
|
|
assert auto_timeout is not None
|
|
|
|
def test_fs_tools(self):
|
|
"""Test hanzo-tools-fs has 7 tools."""
|
|
from hanzo_tools.fs import TOOLS
|
|
|
|
assert len(TOOLS) == 7
|
|
names = {t.name for t in TOOLS}
|
|
expected = {"read", "write", "edit", "tree", "find", "search", "ast"}
|
|
assert names == expected
|
|
|
|
def test_shell_tools(self):
|
|
"""Test hanzo-tools-shell has 12 tools."""
|
|
from hanzo_tools.shell import TOOLS
|
|
|
|
assert len(TOOLS) == 12
|
|
names = {t.name for t in TOOLS}
|
|
expected = {"cmd", "ps", "zsh", "bash", "fish", "dash", "npx", "uvx", "open", "curl", "jq", "wget"}
|
|
assert names == expected
|
|
|
|
def test_memory_tools(self):
|
|
"""Test hanzo-tools-memory has 9 tools."""
|
|
from hanzo_tools.memory import TOOLS
|
|
|
|
# Memory has 9 tools for different operations
|
|
assert len(TOOLS) == 9
|
|
names = {t.name for t in TOOLS}
|
|
expected = {
|
|
"recall_memories", "create_memories", "update_memories",
|
|
"delete_memories", "manage_memories", "recall_facts",
|
|
"store_facts", "summarize_to_memory", "manage_knowledge_bases"
|
|
}
|
|
assert names == expected
|
|
|
|
def test_todo_tools(self):
|
|
"""Test hanzo-tools-todo has 1 tool."""
|
|
from hanzo_tools.todo import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
assert TOOLS[0].name == "todo"
|
|
|
|
def test_reasoning_tools(self):
|
|
"""Test hanzo-tools-reasoning has 2 tools."""
|
|
from hanzo_tools.reasoning import TOOLS
|
|
|
|
assert len(TOOLS) == 2
|
|
names = {t.name for t in TOOLS}
|
|
assert names == {"think", "critic"}
|
|
|
|
def test_lsp_tools(self):
|
|
"""Test hanzo-tools-lsp has 1 tool."""
|
|
from hanzo_tools.lsp import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
assert TOOLS[0].name == "lsp"
|
|
|
|
def test_refactor_tools(self):
|
|
"""Test hanzo-tools-refactor has 1 tool."""
|
|
from hanzo_tools.refactor import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
assert TOOLS[0].name == "refactor"
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.database"),
|
|
reason="hanzo-tools-database not installed",
|
|
)
|
|
def test_database_tools(self):
|
|
"""Test hanzo-tools-database has 8 tools."""
|
|
from hanzo_tools.database import TOOLS
|
|
|
|
assert len(TOOLS) == 8
|
|
|
|
def test_agent_tools(self):
|
|
"""Test hanzo-tools-agent has 3 tools.
|
|
|
|
Core tools: AgentTool, IChingTool, ReviewTool
|
|
"""
|
|
from hanzo_tools.agent import TOOLS
|
|
|
|
assert len(TOOLS) == 3, f"Expected 3 agent tools, got {len(TOOLS)}"
|
|
names = {t.name for t in TOOLS}
|
|
assert names == {"agent", "iching", "review"}
|
|
|
|
def test_jupyter_tools(self):
|
|
"""Test hanzo-tools-jupyter has 1 tool."""
|
|
from hanzo_tools.jupyter import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.editor"),
|
|
reason="hanzo-tools-editor not installed",
|
|
)
|
|
def test_editor_tools(self):
|
|
"""Test hanzo-tools-editor has 3 tools."""
|
|
from hanzo_tools.editor import TOOLS
|
|
|
|
assert len(TOOLS) == 3
|
|
|
|
def test_browser_tools(self):
|
|
"""Test hanzo-tools-browser has 1 tool."""
|
|
from hanzo_tools.browser import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.config"),
|
|
reason="hanzo-tools-config not installed",
|
|
)
|
|
def test_config_tools(self):
|
|
"""Test hanzo-tools-config has 2 tools."""
|
|
from hanzo_tools.config import TOOLS
|
|
|
|
assert len(TOOLS) == 2
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.mcp_tools"),
|
|
reason="hanzo-tools-mcp not installed",
|
|
)
|
|
def test_mcp_tools(self):
|
|
"""Test hanzo-tools-mcp has 4 tools."""
|
|
from hanzo_tools.mcp_tools import TOOLS
|
|
|
|
assert len(TOOLS) == 4
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.llm"),
|
|
reason="hanzo-tools-llm not installed",
|
|
)
|
|
def test_llm_tools(self):
|
|
"""Test hanzo-tools-llm imports (tools depend on litellm)."""
|
|
from hanzo_tools.llm import TOOLS, LLM_AVAILABLE
|
|
|
|
# LLM tools are optional, depend on litellm
|
|
if LLM_AVAILABLE:
|
|
assert len(TOOLS) >= 1
|
|
else:
|
|
assert len(TOOLS) == 0
|
|
|
|
@pytest.mark.skipif(
|
|
not _module_installed("hanzo_tools.vector"),
|
|
reason="hanzo-tools-vector not installed",
|
|
)
|
|
def test_vector_tools(self):
|
|
"""Test hanzo-tools-vector imports (tools depend on heavy deps)."""
|
|
from hanzo_tools.vector import TOOLS, VECTOR_AVAILABLE
|
|
|
|
# Vector tools are optional, depend on faiss/qdrant
|
|
if VECTOR_AVAILABLE:
|
|
assert len(TOOLS) >= 1
|
|
else:
|
|
assert len(TOOLS) == 0
|
|
|
|
|
|
# Required packages for import speed tests
|
|
REQUIRED_IMPORT_MODULES = [
|
|
("hanzo_tools.core", 1.0),
|
|
("hanzo_tools.fs", 1.0),
|
|
("hanzo_tools.shell", 1.0),
|
|
("hanzo_tools.memory", 1.0),
|
|
("hanzo_tools.todo", 1.0),
|
|
("hanzo_tools.reasoning", 1.0),
|
|
("hanzo_tools.lsp", 1.0),
|
|
("hanzo_tools.refactor", 1.0),
|
|
("hanzo_tools.jupyter", 1.0),
|
|
("hanzo_tools.browser", 1.0),
|
|
("hanzo_tools.agent", 2.0), # Agent has litellm, allow more time
|
|
]
|
|
|
|
# Optional packages that may not be installed
|
|
OPTIONAL_IMPORT_MODULES = [
|
|
("hanzo_tools.config", 1.0),
|
|
("hanzo_tools.mcp_tools", 1.0),
|
|
("hanzo_tools.database", 1.0),
|
|
("hanzo_tools.editor", 1.0),
|
|
("hanzo_tools.llm", 2.0), # LLM has litellm, allow more time
|
|
("hanzo_tools.vector", 2.0), # Vector has heavy deps
|
|
]
|
|
|
|
|
|
class TestToolImportSpeed:
|
|
"""Test that tool imports are fast (no blocking)."""
|
|
|
|
@pytest.mark.parametrize("module,max_time", REQUIRED_IMPORT_MODULES)
|
|
def test_import_speed_required(self, module, max_time):
|
|
"""Test that required imports complete quickly."""
|
|
import importlib
|
|
|
|
start = time.time()
|
|
importlib.import_module(module)
|
|
elapsed = time.time() - start
|
|
assert elapsed < max_time, f"{module} took {elapsed:.2f}s (max {max_time}s)"
|
|
|
|
@pytest.mark.parametrize("module,max_time", OPTIONAL_IMPORT_MODULES)
|
|
def test_import_speed_optional(self, module, max_time):
|
|
"""Test that optional imports complete quickly (if installed)."""
|
|
if not _module_installed(module):
|
|
pytest.skip(f"{module} not installed")
|
|
import importlib
|
|
|
|
start = time.time()
|
|
importlib.import_module(module)
|
|
elapsed = time.time() - start
|
|
assert elapsed < max_time, f"{module} took {elapsed:.2f}s (max {max_time}s)"
|
|
|
|
|
|
# Packages that must always be testable
|
|
REQUIRED_PACKAGES = [
|
|
"hanzo_tools.fs",
|
|
"hanzo_tools.shell",
|
|
"hanzo_tools.memory",
|
|
"hanzo_tools.todo",
|
|
"hanzo_tools.reasoning",
|
|
"hanzo_tools.lsp",
|
|
"hanzo_tools.refactor",
|
|
"hanzo_tools.agent",
|
|
"hanzo_tools.jupyter",
|
|
"hanzo_tools.browser",
|
|
]
|
|
|
|
# Optional packages
|
|
OPTIONAL_PACKAGES = [
|
|
"hanzo_tools.config",
|
|
"hanzo_tools.mcp_tools",
|
|
"hanzo_tools.database",
|
|
"hanzo_tools.editor",
|
|
"hanzo_tools.llm",
|
|
"hanzo_tools.vector",
|
|
]
|
|
|
|
|
|
class TestToolAsync:
|
|
"""Test that all tools have async call methods."""
|
|
|
|
def test_all_tools_async(self):
|
|
"""Verify all tool .call() methods are async."""
|
|
import importlib
|
|
|
|
all_packages = REQUIRED_PACKAGES + [
|
|
p for p in OPTIONAL_PACKAGES if _module_installed(p)
|
|
]
|
|
|
|
for pkg_name in all_packages:
|
|
pkg = importlib.import_module(pkg_name)
|
|
tools = getattr(pkg, "TOOLS", [])
|
|
for tool in tools:
|
|
if hasattr(tool, "call"):
|
|
assert asyncio.iscoroutinefunction(
|
|
tool.call
|
|
), f"{pkg_name}.{tool.name}.call() is not async"
|
|
|
|
|
|
class TestTotalToolCount:
|
|
"""Test total tool count across all packages."""
|
|
|
|
def test_required_tool_count(self):
|
|
"""Verify we have the expected tools in required packages.
|
|
|
|
Required packages must have exact counts.
|
|
"""
|
|
# Required packages with exact counts
|
|
required_packages = [
|
|
("hanzo_tools.fs", 7),
|
|
("hanzo_tools.shell", 12),
|
|
("hanzo_tools.browser", 1),
|
|
("hanzo_tools.memory", 9), # 9 separate memory tools
|
|
("hanzo_tools.todo", 1),
|
|
("hanzo_tools.reasoning", 2),
|
|
("hanzo_tools.lsp", 1),
|
|
("hanzo_tools.refactor", 1),
|
|
("hanzo_tools.jupyter", 1),
|
|
("hanzo_tools.agent", 3),
|
|
]
|
|
|
|
total = 0
|
|
import importlib
|
|
|
|
# Check required packages (exact counts)
|
|
for pkg_name, expected_count in required_packages:
|
|
pkg = importlib.import_module(pkg_name)
|
|
tools = getattr(pkg, "TOOLS", [])
|
|
actual = len(tools)
|
|
assert (
|
|
actual == expected_count
|
|
), f"{pkg_name}: expected {expected_count} tools, got {actual}"
|
|
total += actual
|
|
|
|
# Required tools: 38 (7+12+1+9+1+2+1+1+1+3)
|
|
assert total == 38, f"Expected 38 required tools, got {total}"
|