Files
2026-07-13 12:38:34 +08:00

311 lines
10 KiB
Python

"""
Tests for Claude Code Agents Provider
"""
import asyncio
import json
from unittest.mock import MagicMock, patch
import pytest
from composio_claude_agent_sdk.provider import ClaudeAgentSDKProvider
@pytest.fixture
def provider():
"""Create a ClaudeAgentSDKProvider instance."""
return ClaudeAgentSDKProvider()
@pytest.fixture
def custom_provider():
"""Create a ClaudeAgentSDKProvider with custom options."""
return ClaudeAgentSDKProvider(
server_name="custom-server",
server_version="2.0.0",
)
@pytest.fixture
def mock_tool():
"""Create a mock Composio tool."""
return MagicMock(
slug="GMAIL_SEND_EMAIL",
name="Gmail Send Email",
description="Send an email via Gmail",
input_parameters={
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address",
},
"subject": {
"type": "string",
"description": "Email subject",
},
"body": {
"type": "string",
"description": "Email body content",
},
},
"required": ["to", "subject", "body"],
},
)
@pytest.fixture
def mock_execute_tool():
"""Create a mock execute_tool function."""
return MagicMock(
return_value={
"data": {"result": "success"},
"error": None,
"successful": True,
}
)
class TestClaudeAgentSDKProviderInit:
"""Tests for provider initialization."""
def test_default_options(self, provider):
"""Test provider initializes with default options."""
assert provider.server_name == "composio"
assert provider.server_version == "1.0.0"
def test_custom_options(self, custom_provider):
"""Test provider initializes with custom options."""
assert custom_provider.server_name == "custom-server"
assert custom_provider.server_version == "2.0.0"
def test_name_property(self, provider):
"""Test provider has correct name."""
assert provider.name == "claude_agent_sdk"
class TestJsonSchemaConversion:
"""Tests for JSON Schema conversion."""
def test_simple_schema_passthrough(self, provider):
"""Test that JSON Schema is passed through unchanged."""
schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"count": {"type": "integer"},
},
}
result = provider._json_schema_to_simple_schema(schema)
assert result == schema
def test_empty_schema(self, provider):
"""Test handling of empty schema."""
result = provider._json_schema_to_simple_schema({})
assert result == {}
class TestWrapTool:
"""Tests for wrap_tool method."""
def test_wrap_tool_returns_sdk_mcp_tool(
self, provider, mock_tool, mock_execute_tool
):
"""Test that wrap_tool returns an SdkMcpTool."""
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
mock_sdk_tool.return_value = lambda fn: fn
provider.wrap_tool(mock_tool, mock_execute_tool)
# Verify sdk_tool was called with correct arguments
mock_sdk_tool.assert_called_once_with(
mock_tool.slug,
mock_tool.description,
mock_tool.input_parameters,
)
def test_wrap_tool_without_description(self, provider, mock_execute_tool):
"""Test wrapping a tool without description."""
tool_without_desc = MagicMock(
slug="TEST_TOOL",
description=None,
input_parameters={"type": "object", "properties": {}},
)
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
mock_sdk_tool.return_value = lambda fn: fn
provider.wrap_tool(tool_without_desc, mock_execute_tool)
# Should use default description
mock_sdk_tool.assert_called_once()
call_args = mock_sdk_tool.call_args[0]
assert call_args[1] == "Execute TEST_TOOL"
def test_wrap_tool_without_input_parameters(self, provider, mock_execute_tool):
"""Test wrapping a tool without input parameters."""
tool_without_params = MagicMock(
slug="TEST_TOOL",
description="Test tool",
input_parameters=None,
)
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
mock_sdk_tool.return_value = lambda fn: fn
provider.wrap_tool(tool_without_params, mock_execute_tool)
# Should use empty schema
mock_sdk_tool.assert_called_once()
call_args = mock_sdk_tool.call_args[0]
assert call_args[2] == {}
class TestWrapTools:
"""Tests for wrap_tools method."""
def test_wrap_multiple_tools(self, provider, mock_tool, mock_execute_tool):
"""Test wrapping multiple tools."""
another_tool = MagicMock(
slug="SLACK_POST_MESSAGE",
description="Post a message to Slack",
input_parameters={"type": "object", "properties": {}},
)
tools = [mock_tool, another_tool]
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
mock_sdk_tool.return_value = lambda fn: fn
wrapped = provider.wrap_tools(tools, mock_execute_tool)
assert len(wrapped) == 2
assert mock_sdk_tool.call_count == 2
def test_wrap_empty_tools_list(self, provider, mock_execute_tool):
"""Test wrapping an empty tools list."""
wrapped = provider.wrap_tools([], mock_execute_tool)
assert wrapped == []
class TestCreateMcpServer:
"""Tests for create_mcp_server method."""
def test_create_mcp_server_with_default_options(self, provider):
"""Test creating MCP server with default options."""
mock_tools = [MagicMock(), MagicMock()]
with patch(
"composio_claude_agent_sdk.provider.create_sdk_mcp_server"
) as mock_create:
mock_create.return_value = {"type": "sdk", "name": "composio"}
provider.create_mcp_server(mock_tools)
mock_create.assert_called_once_with(
name="composio",
version="1.0.0",
tools=mock_tools,
)
def test_create_mcp_server_with_custom_options(self, custom_provider):
"""Test creating MCP server with custom options."""
mock_tools = [MagicMock()]
with patch(
"composio_claude_agent_sdk.provider.create_sdk_mcp_server"
) as mock_create:
mock_create.return_value = {"type": "sdk", "name": "custom-server"}
custom_provider.create_mcp_server(mock_tools)
mock_create.assert_called_once_with(
name="custom-server",
version="2.0.0",
tools=mock_tools,
)
def test_create_mcp_server_with_empty_tools(self, provider):
"""Test creating MCP server with empty tools list."""
with patch(
"composio_claude_agent_sdk.provider.create_sdk_mcp_server"
) as mock_create:
mock_create.return_value = {"type": "sdk", "name": "composio"}
provider.create_mcp_server([])
mock_create.assert_called_once_with(
name="composio",
version="1.0.0",
tools=[],
)
class TestToolHandlerExecution:
"""Tests for tool handler execution."""
def test_tool_handler_success(self, provider, mock_tool):
"""Test successful tool execution."""
mock_execute = MagicMock(return_value={"data": "success", "successful": True})
# Create a real wrapped tool to test the handler
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
# Capture the decorated function
captured_handler = None
def capture_decorator(name, desc, schema):
def decorator(fn):
nonlocal captured_handler
captured_handler = fn
return fn
return decorator
mock_sdk_tool.side_effect = capture_decorator
provider.wrap_tool(mock_tool, mock_execute)
# Execute the captured handler
result = asyncio.run(captured_handler({"to": "test@example.com"}))
assert result["content"][0]["type"] == "text"
assert "success" in result["content"][0]["text"]
def test_tool_handler_error(self, provider, mock_tool):
"""Test tool execution with error."""
mock_execute = MagicMock(side_effect=Exception("Test error"))
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
captured_handler = None
def capture_decorator(name, desc, schema):
def decorator(fn):
nonlocal captured_handler
captured_handler = fn
return fn
return decorator
mock_sdk_tool.side_effect = capture_decorator
provider.wrap_tool(mock_tool, mock_execute)
result = asyncio.run(captured_handler({"to": "test@example.com"}))
assert result["content"][0]["type"] == "text"
response = json.loads(result["content"][0]["text"])
assert response["successful"] is False
assert "Test error" in response["error"]
def test_tool_handler_string_result(self, provider, mock_tool):
"""Test tool execution returning string result."""
mock_execute = MagicMock(return_value="Simple string result")
with patch("composio_claude_agent_sdk.provider.sdk_tool") as mock_sdk_tool:
captured_handler = None
def capture_decorator(name, desc, schema):
def decorator(fn):
nonlocal captured_handler
captured_handler = fn
return fn
return decorator
mock_sdk_tool.side_effect = capture_decorator
provider.wrap_tool(mock_tool, mock_execute)
result = asyncio.run(captured_handler({}))
assert result["content"][0]["type"] == "text"
assert result["content"][0]["text"] == "Simple string result"