314 lines
9.3 KiB
Python
314 lines
9.3 KiB
Python
"""Tests for tools module"""
|
|
import pytest
|
|
from luxx.tools.core import (
|
|
ToolContext,
|
|
ToolDefinition,
|
|
ToolResult,
|
|
ToolRegistry,
|
|
CommandPermission
|
|
)
|
|
|
|
|
|
class TestToolContext:
|
|
"""Tests for ToolContext dataclass"""
|
|
|
|
def test_tool_context_creation(self):
|
|
"""Should create context with default values"""
|
|
ctx = ToolContext()
|
|
assert ctx.workspace is None
|
|
assert ctx.user_id is None
|
|
assert ctx.username is None
|
|
assert ctx.extra == {}
|
|
|
|
def test_tool_context_with_values(self):
|
|
"""Should create context with provided values"""
|
|
ctx = ToolContext(
|
|
workspace="/workspace/test",
|
|
user_id=1,
|
|
username="testuser",
|
|
extra={"key": "value"}
|
|
)
|
|
assert ctx.workspace == "/workspace/test"
|
|
assert ctx.user_id == 1
|
|
assert ctx.username == "testuser"
|
|
assert ctx.extra["key"] == "value"
|
|
|
|
|
|
class TestToolDefinition:
|
|
"""Tests for ToolDefinition dataclass"""
|
|
|
|
def test_tool_definition_creation(self):
|
|
"""Should create tool definition"""
|
|
def handler(args):
|
|
return {"result": "ok"}
|
|
|
|
tool = ToolDefinition(
|
|
name="test_tool",
|
|
description="A test tool",
|
|
parameters={"type": "object"},
|
|
handler=handler
|
|
)
|
|
assert tool.name == "test_tool"
|
|
assert tool.description == "A test tool"
|
|
assert tool.category == "general"
|
|
assert tool.required_permission == CommandPermission.READ_ONLY
|
|
|
|
def test_tool_definition_to_openai_format(self):
|
|
"""Should convert to OpenAI format"""
|
|
def handler(args):
|
|
return {"result": "ok"}
|
|
|
|
tool = ToolDefinition(
|
|
name="test_tool",
|
|
description="A test tool",
|
|
parameters={"type": "object", "properties": {}},
|
|
handler=handler
|
|
)
|
|
result = tool.to_openai_format()
|
|
assert result["type"] == "function"
|
|
assert result["function"]["name"] == "test_tool"
|
|
|
|
|
|
class TestToolResult:
|
|
"""Tests for ToolResult dataclass"""
|
|
|
|
def test_tool_result_success(self):
|
|
"""Should create success result"""
|
|
result = ToolResult(success=True, data={"key": "value"})
|
|
assert result.success is True
|
|
assert result.data["key"] == "value"
|
|
assert result.error is None
|
|
|
|
def test_tool_result_failure(self):
|
|
"""Should create failure result"""
|
|
result = ToolResult(success=False, error="Something went wrong")
|
|
assert result.success is False
|
|
assert result.error == "Something went wrong"
|
|
|
|
def test_tool_result_to_dict(self):
|
|
"""Should convert to dictionary"""
|
|
result = ToolResult(success=True, data={"key": "value"})
|
|
d = result.to_dict()
|
|
assert isinstance(d, dict)
|
|
assert d["success"] is True
|
|
assert d["data"]["key"] == "value"
|
|
|
|
def test_tool_result_ok_factory(self):
|
|
"""Should use ok() factory method"""
|
|
result = ToolResult.ok({"result": "success"})
|
|
assert result.success is True
|
|
assert result.data == {"result": "success"}
|
|
|
|
def test_tool_result_fail_factory(self):
|
|
"""Should use fail() factory method"""
|
|
result = ToolResult.fail("Error occurred")
|
|
assert result.success is False
|
|
assert result.error == "Error occurred"
|
|
|
|
|
|
class TestToolRegistry:
|
|
"""Tests for ToolRegistry class"""
|
|
|
|
def test_registry_singleton(self):
|
|
"""Should return same instance"""
|
|
reg1 = ToolRegistry()
|
|
reg2 = ToolRegistry()
|
|
assert reg1 is reg2
|
|
|
|
def test_register_tool(self):
|
|
"""Should register a tool"""
|
|
registry = ToolRegistry()
|
|
registry.clear() # Start fresh
|
|
|
|
def handler(args):
|
|
return {"result": "ok"}
|
|
|
|
tool = ToolDefinition(
|
|
name="my_tool",
|
|
description="My test tool",
|
|
parameters={},
|
|
handler=handler
|
|
)
|
|
registry.register(tool)
|
|
assert registry.get("my_tool") is not None
|
|
assert registry.tool_count() == 1
|
|
|
|
def test_get_nonexistent_tool(self):
|
|
"""Should return None for nonexistent tool"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
assert registry.get("nonexistent") is None
|
|
|
|
def test_list_all_tools(self):
|
|
"""Should list all registered tools"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return {}
|
|
|
|
tool1 = ToolDefinition(name="tool1", description="Tool 1", parameters={}, handler=handler)
|
|
tool2 = ToolDefinition(name="tool2", description="Tool 2", parameters={}, handler=handler)
|
|
registry.register(tool1)
|
|
registry.register(tool2)
|
|
|
|
tools = registry.list_all()
|
|
assert len(tools) == 2
|
|
|
|
def test_list_by_category(self):
|
|
"""Should filter tools by category"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return {}
|
|
|
|
tool1 = ToolDefinition(
|
|
name="tool1", description="Tool 1", parameters={},
|
|
handler=handler, category="code"
|
|
)
|
|
tool2 = ToolDefinition(
|
|
name="tool2", description="Tool 2", parameters={},
|
|
handler=handler, category="file"
|
|
)
|
|
registry.register(tool1)
|
|
registry.register(tool2)
|
|
|
|
code_tools = registry.list_by_category("code")
|
|
assert len(code_tools) == 1
|
|
|
|
def test_execute_tool(self):
|
|
"""Should execute a tool"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return {"executed": True, "args": args}
|
|
|
|
tool = ToolDefinition(
|
|
name="test_tool",
|
|
description="Test tool",
|
|
parameters={},
|
|
handler=handler
|
|
)
|
|
registry.register(tool)
|
|
|
|
result = registry.execute("test_tool", {"input": "value"})
|
|
# Direct handler returns are passed through as-is
|
|
assert result["executed"] is True
|
|
assert result["args"]["input"] == "value"
|
|
|
|
def test_execute_tool_with_tool_result(self):
|
|
"""Should return ToolResult when handler returns ToolResult"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return ToolResult.ok({"executed": True})
|
|
|
|
tool = ToolDefinition(
|
|
name="test_tool",
|
|
description="Test tool",
|
|
parameters={},
|
|
handler=handler
|
|
)
|
|
registry.register(tool)
|
|
|
|
result = registry.execute("test_tool", {})
|
|
assert result["success"] is True
|
|
assert result["data"]["executed"] is True
|
|
|
|
def test_execute_nonexistent_tool(self):
|
|
"""Should return error for nonexistent tool"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
result = registry.execute("nonexistent", {})
|
|
assert result["success"] is False
|
|
assert "not found" in result["error"]
|
|
|
|
def test_execute_with_context(self):
|
|
"""Should pass context to handler"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
received_context = None
|
|
|
|
def handler(args, context=None):
|
|
nonlocal received_context
|
|
received_context = context
|
|
return ToolResult.ok({"received": True})
|
|
|
|
tool = ToolDefinition(
|
|
name="test_tool",
|
|
description="Test tool",
|
|
parameters={},
|
|
handler=handler
|
|
)
|
|
registry.register(tool)
|
|
|
|
ctx = ToolContext(user_id=1, username="test")
|
|
registry.execute("test_tool", {}, context=ctx)
|
|
assert received_context is not None
|
|
assert received_context.user_id == 1
|
|
|
|
def test_permission_check(self):
|
|
"""Should check user permission"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return ToolResult.ok({"ok": True})
|
|
|
|
tool = ToolDefinition(
|
|
name="admin_tool",
|
|
description="Admin tool",
|
|
parameters={},
|
|
handler=handler,
|
|
required_permission=CommandPermission.ADMIN
|
|
)
|
|
registry.register(tool)
|
|
|
|
# User with low permission
|
|
ctx = ToolContext(
|
|
user_id=1,
|
|
extra={"user_permission_level": CommandPermission.READ_ONLY}
|
|
)
|
|
result = registry.execute("admin_tool", {}, context=ctx)
|
|
assert result["success"] is False
|
|
assert "Permission denied" in result["error"]
|
|
|
|
def test_remove_tool(self):
|
|
"""Should remove a tool"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return {}
|
|
|
|
tool = ToolDefinition(
|
|
name="removable",
|
|
description="To be removed",
|
|
parameters={},
|
|
handler=handler
|
|
)
|
|
registry.register(tool)
|
|
assert registry.get("removable") is not None
|
|
|
|
registry.remove("removable")
|
|
assert registry.get("removable") is None
|
|
|
|
def test_clear_tools(self):
|
|
"""Should clear all tools"""
|
|
registry = ToolRegistry()
|
|
registry.clear()
|
|
|
|
def handler(args):
|
|
return {}
|
|
|
|
tool = ToolDefinition(name="tool1", description="", parameters={}, handler=handler)
|
|
registry.register(tool)
|
|
assert registry.tool_count() > 0
|
|
|
|
registry.clear()
|
|
assert registry.tool_count() == 0
|