mirror of
https://github.com/hanzoai/python-sdk.git
synced 2026-10-26 11:49:36 +00:00
Packaging metadata (twine check was PASSED-with-warnings -> clean PASS): - config/llm/mcp/vector: add missing 'readme = README.md' ([project] key); README files existed but were never wired into the dist metadata. - net/plan/vcs: fill empty (0-byte) README.md files referenced by the readme key, so long_description is no longer missing. Tests (hanzo-tools-core/test_all_tools.py drifted from current sources): - browser: 1 -> 3 tools (browser, cdp, playwright after the zapd split) - mcp_tools: 4 -> 5 tools - required-tool total: 21 -> 23
311 lines
9.4 KiB
Python
311 lines
9.4 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 exports unified HIP-0300 tool."""
|
|
from hanzo_tools.fs import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
|
|
def test_shell_tools(self):
|
|
"""Test hanzo-tools-shell exports detected shell + core shell tools."""
|
|
from hanzo_tools.shell import TOOLS
|
|
|
|
# First tool is detected shell (environment-dependent), then fixed base tools.
|
|
assert len(TOOLS) >= 8
|
|
names = {t.name for t in TOOLS}
|
|
required = {"ps", "npx", "uvx", "open", "curl", "jq", "wget"}
|
|
assert required.issubset(names)
|
|
|
|
def test_memory_tools(self):
|
|
"""Test hanzo-tools-memory exports unified memory tool."""
|
|
from hanzo_tools.memory import TOOLS
|
|
|
|
assert len(TOOLS) == 1
|
|
|
|
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 == "tasks"
|
|
|
|
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 9 tools."""
|
|
from hanzo_tools.database import TOOLS
|
|
|
|
assert len(TOOLS) == 9
|
|
|
|
def test_agent_tools(self):
|
|
"""Test hanzo-tools-agent has 3 tools.
|
|
|
|
Core tools: AgentTool, ZenTool, 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", "zen", "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 3 tools (browser, cdp, playwright)."""
|
|
from hanzo_tools.browser import TOOLS
|
|
|
|
assert len(TOOLS) == 3
|
|
|
|
@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 at least 1 tool (graceful degradation)."""
|
|
from hanzo_tools.config import TOOLS
|
|
|
|
assert len(TOOLS) >= 1
|
|
|
|
@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 5 tools."""
|
|
from hanzo_tools.mcp_tools import TOOLS
|
|
|
|
assert len(TOOLS) == 5
|
|
|
|
@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 llm)."""
|
|
from hanzo_tools.llm import TOOLS, LLM_AVAILABLE
|
|
|
|
# LLM tools are optional, depend on llm
|
|
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 llm, 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 llm, 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", 1),
|
|
("hanzo_tools.shell", 9),
|
|
("hanzo_tools.browser", 3),
|
|
("hanzo_tools.memory", 1), # unified memory tool
|
|
("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: 23 (1+9+3+1+1+2+1+1+1+3)
|
|
assert total == 23, f"Expected 23 required tools, got {total}"
|