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

1208 lines
41 KiB
Python

"""Test provider functionality.
This test module verifies provider functionality including:
- Provider initialization
- execute_tool function setup and usage
- Provider helper methods
- Both agentic and non-agentic provider behavior
"""
from unittest.mock import Mock
import pytest
from composio.client.types import Tool, tool_list_response
from composio.core.models.base import allow_tracking
from composio.core.models.tools import Tools
from composio.core.provider import AgenticProvider, NonAgenticProvider
from tests.conftest import mock_http_client
@pytest.fixture(autouse=True)
def disable_telemetry():
"""Disable telemetry for all tests to prevent thread issues."""
token = allow_tracking.set(False)
yield
allow_tracking.reset(token)
def create_mock_tool(
slug: str, toolkit_slug: str, version: str = "12012025_00"
) -> Tool:
"""Create a mock tool for testing."""
return Tool(
name=f"Test {slug}",
slug=slug,
description="Test tool for provider testing",
input_parameters={"type": "object", "properties": {}},
output_parameters={},
available_versions=[version],
version=version,
scopes=[],
toolkit=tool_list_response.ItemToolkit(
name=toolkit_slug.title(), slug=toolkit_slug, logo=""
),
deprecated=tool_list_response.ItemDeprecated(
available_versions=[version],
displayName=f"Test {slug}",
version=version,
toolkit=tool_list_response.ItemDeprecatedToolkit(logo=""),
is_deprecated=False,
),
is_deprecated=False,
no_auth=False,
tags=[],
)
class TestProviderInitialization:
"""Test cases for provider initialization."""
def test_non_agentic_provider_initialization(self):
"""Test that non-agentic providers initialize correctly."""
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
assert provider is not None
assert provider.name == "openai"
assert isinstance(provider, NonAgenticProvider)
def test_agentic_provider_initialization(self):
"""Test that agentic providers initialize correctly."""
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {"slug": tool.slug, "execute": execute_tool}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
assert provider is not None
assert provider.name == "test_agentic"
assert isinstance(provider, AgenticProvider)
def test_provider_has_name_attribute(self):
"""Test that all providers have a name attribute."""
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
assert hasattr(provider, "name")
assert isinstance(provider.name, str)
assert len(provider.name) > 0
class TestProviderExecuteToolSetup:
"""Test cases for execute_tool setup during initialization."""
def test_execute_tool_set_during_tools_initialization_non_agentic(self):
"""Test that execute_tool is set during Tools initialization for non-agentic providers."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
# Create Tools instance
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# After initialization, provider should have execute_tool
assert hasattr(provider, "execute_tool")
assert provider.execute_tool is not None
assert callable(provider.execute_tool)
def test_execute_tool_set_during_tools_initialization_agentic(self):
"""Test that execute_tool is set during Tools initialization for agentic providers."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {"slug": tool.slug, "execute": execute_tool}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
# Create Tools instance
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# After initialization, provider should have execute_tool
assert hasattr(provider, "execute_tool")
assert provider.execute_tool is not None
assert callable(provider.execute_tool)
def test_execute_tool_available_immediately_after_initialization(self):
"""Test that execute_tool is available immediately after initialization, before get() is called."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
# Create Tools instance (but don't call get())
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# execute_tool should be available immediately
assert hasattr(provider, "execute_tool")
assert callable(provider.execute_tool)
def test_execute_tool_signature_matches_protocol(self):
"""Test that execute_tool has the correct signature matching ExecuteToolFn protocol."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# Verify it's callable
assert callable(provider.execute_tool)
# Try calling it with the protocol signature
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
# Should accept slug, arguments, and keyword-only modifiers/user_id
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
modifiers=None,
user_id="test-user",
)
assert result["successful"] is True
class TestProviderExecuteToolFunctionality:
"""Test cases for execute_tool functionality."""
def test_execute_tool_executes_composio_tool(self):
"""Test that provider.execute_tool executes Composio tools correctly."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True, "repo": "composio/composio"},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
assert result["data"]["starred"] is True
assert result["data"]["repo"] == "composio/composio"
mock_client.tools.execute.assert_called_once()
def test_execute_tool_uses_dangerously_skip_version_check(self):
"""Test that execute_tool automatically sets dangerously_skip_version_check=True."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
# Create Tools without explicit toolkit versions (defaults to 'latest')
Tools(
client=mock_client,
provider=provider,
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "latest")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
# Should NOT raise ToolVersionRequiredError even with 'latest'
# because dangerously_skip_version_check=True is set automatically
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
def test_execute_tool_passes_user_id(self):
"""Test that execute_tool correctly passes user_id parameter."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
user_id="user-123",
)
assert result["successful"] is True
call_args = mock_client.tools.execute.call_args
assert call_args.kwargs["user_id"] == "user-123"
def test_execute_tool_passes_modifiers(self):
"""Test that execute_tool correctly passes modifiers."""
from composio.core.models._modifiers import before_execute
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
def modify_arguments(tool, toolkit, params):
params["arguments"]["modified"] = True
return params
modifier = before_execute(modify_arguments)
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
modifiers=[modifier],
)
assert result["successful"] is True
call_args = mock_client.tools.execute.call_args
assert call_args.kwargs["arguments"]["modified"] is True
def test_execute_tool_with_toolkit_versions(self):
"""Test that execute_tool uses configured toolkit versions."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00", "slack": "12012025_01"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
call_args = mock_client.tools.execute.call_args
assert call_args.kwargs["version"] == "12012025_00"
class TestNonAgenticProviderHelperMethods:
"""Test cases for non-agentic provider helper methods."""
def test_openai_provider_execute_tool_call(self):
"""Test that OpenAIProvider.execute_tool_call uses provider.execute_tool."""
from openai.types.chat.chat_completion_message_tool_call import (
ChatCompletionMessageToolCall,
Function,
)
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
tool_call = ChatCompletionMessageToolCall(
id="call_123",
function=Function(
name="GITHUB_STAR_REPO",
arguments='{"repo": "composio/composio"}',
),
type="function",
)
result = provider.execute_tool_call(
user_id="test-user",
tool_call=tool_call,
)
assert result["successful"] is True
assert result["data"]["starred"] is True
def test_openai_provider_handle_tool_calls(self):
"""Test that OpenAIProvider.handle_tool_calls processes multiple tool calls."""
from openai.types.chat import ChatCompletion
from openai.types.chat.chat_completion import Choice
from openai.types.chat.chat_completion_message import ChatCompletionMessage
from openai.types.chat.chat_completion_message_tool_call import (
ChatCompletionMessageToolCall,
Function,
)
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
tool_call = ChatCompletionMessageToolCall(
id="call_123",
function=Function(
name="GITHUB_STAR_REPO",
arguments='{"repo": "composio/composio"}',
),
type="function",
)
message = ChatCompletionMessage(
role="assistant",
content=None,
tool_calls=[tool_call],
)
choice = Choice(
finish_reason="tool_calls",
index=0,
message=message,
)
completion = ChatCompletion(
id="chatcmpl-123",
choices=[choice],
created=1234567890,
model="gpt-4",
object="chat.completion",
)
results = provider.handle_tool_calls(
user_id="test-user",
response=completion,
)
assert len(results) == 1
assert results[0]["successful"] is True
assert results[0]["data"]["starred"] is True
def test_openai_provider_handle_tool_calls_only_first_choice(self):
"""Only the first choice runs; n > 1 alternatives would orphan tool_call_ids."""
from openai.types.chat import ChatCompletion
from openai.types.chat.chat_completion import Choice
from openai.types.chat.chat_completion_message import ChatCompletionMessage
from openai.types.chat.chat_completion_message_tool_call import (
ChatCompletionMessageToolCall,
Function,
)
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
def make_choice(index: int, call_id: str) -> Choice:
return Choice(
finish_reason="tool_calls",
index=index,
message=ChatCompletionMessage(
role="assistant",
content=None,
tool_calls=[
ChatCompletionMessageToolCall(
id=call_id,
function=Function(
name="GITHUB_STAR_REPO",
arguments='{"repo": "composio/composio"}',
),
type="function",
)
],
),
)
completion = ChatCompletion(
id="chatcmpl-123",
choices=[make_choice(0, "call_first"), make_choice(1, "call_second")],
created=1234567890,
model="gpt-4",
object="chat.completion",
)
results = provider.handle_tool_calls(
user_id="test-user",
response=completion,
)
assert len(results) == 1
assert mock_client.tools.execute.call_count == 1
def test_openai_provider_wrap_tools(self):
"""Test that OpenAIProvider.wrap_tools creates proper tool definitions."""
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
tools_list = [
create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00"),
create_mock_tool("SLACK_SEND_MESSAGE", "slack", "12012025_01"),
]
wrapped = provider.wrap_tools(tools_list)
assert len(wrapped) == 2
assert all(tool["type"] == "function" for tool in wrapped)
assert wrapped[0]["function"]["name"] == "GITHUB_STAR_REPO"
assert wrapped[1]["function"]["name"] == "SLACK_SEND_MESSAGE"
class TestAgenticProviderFunctionality:
"""Test cases for agentic providers."""
def test_agentic_provider_has_execute_tool_after_initialization(self):
"""Test that agentic providers have execute_tool after Tools initialization."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {"slug": tool.slug, "execute": execute_tool}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
assert hasattr(provider, "execute_tool")
assert callable(provider.execute_tool)
def test_agentic_provider_execute_tool_works(self):
"""Test that agentic provider's execute_tool executes tools correctly."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {"slug": tool.slug, "execute": execute_tool}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
assert result["data"]["starred"] is True
def test_agentic_provider_wrapped_tools_receive_execute_function(self):
"""Test that wrapped tools from agentic providers receive the execute function."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {
"name": tool.slug,
"description": tool.description,
"executor": execute_tool,
}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
tools = Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_list_response = Mock()
mock_list_response.items = [github_tool]
mock_client.tools.list.return_value = mock_list_response
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
wrapped_tools = tools.get(
user_id="test-user",
slug="GITHUB_STAR_REPO",
)
assert len(wrapped_tools) == 1
wrapped_tool = wrapped_tools[0]
assert "executor" in wrapped_tool
assert callable(wrapped_tool["executor"])
# Call the executor
result = wrapped_tool["executor"](
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
assert result["data"]["starred"] is True
def test_agentic_provider_wrap_tools_receives_execute_function(self):
"""Test that wrap_tools method receives the execute_tool function."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {"slug": tool.slug, "has_executor": callable(execute_tool)}
def wrap_tools(self, tools, execute_tool):
# Verify execute_tool is callable
assert callable(execute_tool)
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
tools = Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_list_response = Mock()
mock_list_response.items = [github_tool]
mock_client.tools.list.return_value = mock_list_response
wrapped = tools.get(user_id="test-user", slug="GITHUB_STAR_REPO")
assert len(wrapped) == 1
assert wrapped[0]["has_executor"] is True
class TestLangchainReservedKeywords:
"""Regression tests for PLEN-1671: ValueError when tool schema has Python reserved keywords as parameter names.
Tools from some toolkits (e.g. Intercom) have parameters named 'from',
which is a Python reserved keyword. The LangchainProvider must substitute
these names before building function signatures and reinstate them when
executing the tool.
"""
def _make_tool_with_params(self, properties: dict, required: list | None = None):
"""Helper: create a mock Tool whose input_parameters use the given properties."""
return Tool(
name="Test Tool",
slug="TEST_TOOL",
description="A tool for testing reserved keyword handling",
input_parameters={
"type": "object",
"title": "TestToolRequest",
"properties": properties,
"required": required or [],
},
output_parameters={},
available_versions=["12012025_00"],
version="12012025_00",
scopes=[],
toolkit=tool_list_response.ItemToolkit(name="Test", slug="test", logo=""),
deprecated=tool_list_response.ItemDeprecated(
available_versions=["12012025_00"],
displayName="Test Tool",
version="12012025_00",
toolkit=tool_list_response.ItemDeprecatedToolkit(logo=""),
is_deprecated=False,
),
is_deprecated=False,
no_auth=False,
tags=[],
)
def test_wrap_tool_with_from_parameter(self):
"""PLEN-1671: 'from' parameter must not raise ValueError."""
from composio_langchain import LangchainProvider
provider = LangchainProvider()
tool = self._make_tool_with_params(
properties={
"from": {
"type": "string",
"description": "Starting point for listing",
},
"limit": {
"type": "integer",
"description": "Max results",
"default": 20,
},
},
)
captured = {}
def mock_execute(slug, arguments):
captured.update(arguments)
return {"data": {}, "error": None, "successful": True}
# Must not raise ValueError: 'from' is not a valid parameter name
wrapped = provider.wrap_tool(tool, mock_execute)
assert wrapped.name == "TEST_TOOL"
# Invoke the tool and verify 'from' is reinstated in the arguments
wrapped.run({"from_rs": "2024-01-01", "limit": 10})
assert captured == {"from": "2024-01-01", "limit": 10}
def test_wrap_tool_with_multiple_reserved_keywords(self):
"""All Python reserved keywords used as parameter names must be handled."""
from composio_langchain import LangchainProvider
provider = LangchainProvider()
tool = self._make_tool_with_params(
properties={
"from": {
"type": "string",
"description": "Start date",
},
"for": {
"type": "string",
"description": "Recipient",
},
"import": {
"type": "string",
"description": "Import source",
},
"class": {
"type": "string",
"description": "CSS class",
},
"async": {
"type": "boolean",
"description": "Async flag",
"default": False,
},
"normal_param": {
"type": "string",
"description": "A normal parameter",
},
},
)
captured = {}
def mock_execute(slug, arguments):
captured.update(arguments)
return {"data": {}, "error": None, "successful": True}
wrapped = provider.wrap_tool(tool, mock_execute)
assert wrapped.name == "TEST_TOOL"
# Invoke with renamed parameters and verify originals are reinstated
wrapped.run(
{
"from_rs": "2024-01-01",
"for_rs": "user@example.com",
"import_rs": "csv",
"class_rs": "primary",
"async_rs": True,
"normal_param": "hello",
}
)
assert captured == {
"from": "2024-01-01",
"for": "user@example.com",
"import": "csv",
"class": "primary",
"async": True,
"normal_param": "hello",
}
def test_wrap_tool_with_required_reserved_keyword_param(self):
"""Reserved keywords that are also required params must work."""
from composio_langchain import LangchainProvider
provider = LangchainProvider()
tool = self._make_tool_with_params(
properties={
"from": {
"type": "string",
"description": "Required start date",
},
},
required=["from"],
)
captured = {}
def mock_execute(slug, arguments):
captured.update(arguments)
return {"data": {}, "error": None, "successful": True}
wrapped = provider.wrap_tool(tool, mock_execute)
wrapped.run({"from_rs": "2024-01-01"})
assert captured == {"from": "2024-01-01"}
class TestProviderEdgeCases:
"""Test edge cases and error handling for providers."""
def test_execute_tool_with_none_parameters(self):
"""Test that execute_tool works when optional parameters are None."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
modifiers=None,
user_id=None,
)
assert result["successful"] is True
def test_execute_tool_with_multiple_toolkit_versions(self):
"""Test execute_tool with multiple configured toolkit versions."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
toolkit_versions = {
"github": "12012025_00",
"slack": "12012025_01",
"notion": "12012025_02",
}
Tools(
client=mock_client,
provider=provider,
toolkit_versions=toolkit_versions,
)
# Test each toolkit uses its configured version
for toolkit, version in toolkit_versions.items():
tool = create_mock_tool(f"{toolkit.upper()}_TEST", toolkit, version)
mock_client.tools.retrieve.return_value = tool
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"success": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
result = provider.execute_tool(
slug=f"{toolkit.upper()}_TEST",
arguments={"test": "data"},
)
assert result["successful"] is True
call_args = mock_client.tools.execute.call_args
assert call_args.kwargs["version"] == version
class TestProviderIntegration:
"""Integration tests for provider functionality."""
def test_provider_workflow_non_agentic(self):
"""Test complete workflow with non-agentic provider."""
mock_client = mock_http_client()
from composio.core.provider._openai import OpenAIProvider
provider = OpenAIProvider()
# Initialize Tools
tools = Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# Verify provider has execute_tool
assert hasattr(provider, "execute_tool")
# Mock tool and response
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_list_response = Mock()
mock_list_response.items = [github_tool]
mock_client.tools.list.return_value = mock_list_response
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
# Get wrapped tools
wrapped_tools = tools.get(user_id="test-user", slug="GITHUB_STAR_REPO")
# Verify wrapped tools are in OpenAI format
assert len(wrapped_tools) == 1
wrapped_tool = wrapped_tools[0]
assert wrapped_tool["type"] == "function"
assert wrapped_tool["function"]["name"] == "GITHUB_STAR_REPO"
# Execute tool directly via provider
result = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
user_id="test-user",
)
assert result["successful"] is True
assert result["data"]["starred"] is True
def test_provider_workflow_agentic(self):
"""Test complete workflow with agentic provider."""
mock_client = mock_http_client()
class TestAgenticProvider(AgenticProvider, name="test_agentic"):
def wrap_tool(self, tool, execute_tool):
return {
"name": tool.slug,
"description": tool.description,
"parameters": tool.input_parameters,
"executor": execute_tool,
}
def wrap_tools(self, tools, execute_tool):
return [self.wrap_tool(tool, execute_tool) for tool in tools]
provider = TestAgenticProvider()
# Initialize Tools
tools = Tools(
client=mock_client,
provider=provider,
toolkit_versions={"github": "12012025_00"},
)
# Verify provider has execute_tool
assert hasattr(provider, "execute_tool")
# Mock tool and response
github_tool = create_mock_tool("GITHUB_STAR_REPO", "github", "12012025_00")
mock_client.tools.retrieve.return_value = github_tool
mock_list_response = Mock()
mock_list_response.items = [github_tool]
mock_client.tools.list.return_value = mock_list_response
mock_execute_response = Mock()
mock_execute_response.model_dump.return_value = {
"data": {"starred": True},
"error": None,
"successful": True,
}
mock_client.tools.execute.return_value = mock_execute_response
# Get wrapped tools
wrapped_tools = tools.get(user_id="test-user", slug="GITHUB_STAR_REPO")
# Verify wrapped tools have executor
assert len(wrapped_tools) == 1
wrapped_tool = wrapped_tools[0]
assert "executor" in wrapped_tool
assert callable(wrapped_tool["executor"])
# Execute via wrapped tool executor
result = wrapped_tool["executor"](
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
)
assert result["successful"] is True
assert result["data"]["starred"] is True
# Also test direct execution via provider
result2 = provider.execute_tool(
slug="GITHUB_STAR_REPO",
arguments={"repo": "composio/composio"},
user_id="test-user",
)
assert result2["successful"] is True
assert result2["data"]["starred"] is True
class TestAgenticSkipDefaultsParity:
"""Regression: agentic providers must honor skip_defaults in __signature__.
The langchain and langgraph providers strip schema defaults from
args_schema when schema_config={"skip_defaults": True}, but kept the
defaults in the function __signature__, so the signature and args_schema
disagreed about which optional params were required. They now pass the
same skip_default to both, matching the sibling llamaindex provider.
autogen only builds __signature__ (no args_schema), so it never had the
mismatch, but its signature likewise ignored skip_defaults; it is aligned
too and checked on the signature alone.
Provider packages are optional (the unit-test job installs langchain and
autogen), so each case skips via importorskip when its provider is absent.
"""
def _make_tool_with_default(self):
return Tool(
name="Test Tool",
slug="TEST_TOOL",
description="A tool with a defaulted optional param",
input_parameters={
"type": "object",
"title": "TestToolRequest",
"properties": {
"limit": {
"type": "integer",
"description": "Max results",
"default": 5,
},
},
"required": [],
},
output_parameters={},
available_versions=["12012025_00"],
version="12012025_00",
scopes=[],
toolkit=tool_list_response.ItemToolkit(name="Test", slug="test", logo=""),
deprecated=tool_list_response.ItemDeprecated(
available_versions=["12012025_00"],
displayName="Test Tool",
version="12012025_00",
toolkit=tool_list_response.ItemDeprecatedToolkit(logo=""),
is_deprecated=False,
),
is_deprecated=False,
no_auth=False,
tags=[],
)
def _provider(self, name, schema_config=None):
module = pytest.importorskip(f"composio_{name}")
provider_cls = getattr(module, f"{name.capitalize()}Provider")
return (
provider_cls(schema_config=schema_config)
if schema_config
else provider_cls()
)
@pytest.mark.parametrize("name", ["langchain", "langgraph"])
def test_skip_defaults_signature_matches_args_schema(self, name):
from inspect import Parameter
provider = self._provider(name, {"skip_defaults": True})
wrapped = provider.wrap_tool(self._make_tool_with_default(), Mock())
limit = wrapped.func.__signature__.parameters["limit"]
assert limit.default is Parameter.empty
assert wrapped.args_schema.model_fields["limit"].is_required()
@pytest.mark.parametrize("name", ["langchain", "langgraph"])
def test_default_preserved_without_skip_defaults(self, name):
provider = self._provider(name)
wrapped = provider.wrap_tool(self._make_tool_with_default(), Mock())
limit = wrapped.func.__signature__.parameters["limit"]
assert limit.default == 5
assert not wrapped.args_schema.model_fields["limit"].is_required()
def test_autogen_signature_honors_skip_defaults(self):
from inspect import Parameter
provider = self._provider("autogen", {"skip_defaults": True})
wrapped = provider.wrap_tool(self._make_tool_with_default(), Mock())
# autogen exposes the wrapped function as `_func` and has no args_schema.
limit = wrapped._func.__signature__.parameters["limit"]
assert limit.default is Parameter.empty
def test_autogen_signature_preserves_default(self):
provider = self._provider("autogen")
wrapped = provider.wrap_tool(self._make_tool_with_default(), Mock())
limit = wrapped._func.__signature__.parameters["limit"]
assert limit.default == 5