from typing import Optional import pytest from pydantic import BaseModel, Field from crewai.tools.structured_tool import CrewStructuredTool # Test fixtures @pytest.fixture def basic_function(): def test_func(param1: str, param2: int = 0) -> str: """Test function with basic params.""" return f"{param1} {param2}" return test_func @pytest.fixture def schema_class(): class TestSchema(BaseModel): param1: str param2: int = Field(default=0) return TestSchema class InternalCrewStructuredTool: def test_initialization(self, basic_function, schema_class): """Test basic initialization of CrewStructuredTool""" tool = CrewStructuredTool( name="test_tool", description="Test tool description", func=basic_function, args_schema=schema_class, ) assert tool.name == "test_tool" assert tool.description == "Test tool description" assert tool.func == basic_function assert tool.args_schema == schema_class def test_from_function(self, basic_function): """Test creating tool from function""" tool = CrewStructuredTool.from_function( func=basic_function, name="test_tool", description="Test description" ) assert tool.name == "test_tool" assert tool.description == "Test description" assert tool.func == basic_function assert isinstance(tool.args_schema, type(BaseModel)) def test_validate_function_signature(self, basic_function, schema_class): """Test function signature validation""" tool = CrewStructuredTool( name="test_tool", description="Test tool", func=basic_function, args_schema=schema_class, ) # Should not raise any exceptions tool._validate_function_signature() @pytest.mark.asyncio async def test_ainvoke(self, basic_function): """Test asynchronous invocation""" tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") result = await tool.ainvoke(input={"param1": "test"}) assert result == "test 0" def test_parse_args_dict(self, basic_function): """Test parsing dictionary arguments""" tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") parsed = tool._parse_args({"param1": "test", "param2": 42}) assert parsed["param1"] == "test" assert parsed["param2"] == 42 def test_parse_args_string(self, basic_function): """Test parsing string arguments""" tool = CrewStructuredTool.from_function(func=basic_function, name="test_tool") parsed = tool._parse_args('{"param1": "test", "param2": 42}') assert parsed["param1"] == "test" assert parsed["param2"] == 42 def test_complex_types(self): """Test handling of complex parameter types""" def complex_func(nested: dict, items: list) -> str: """Process complex types.""" return f"Processed {len(items)} items with {len(nested)} nested keys" tool = CrewStructuredTool.from_function( func=complex_func, name="test_tool", description="Test complex types" ) result = tool.invoke({"nested": {"key": "value"}, "items": [1, 2, 3]}) assert result == "Processed 3 items with 1 nested keys" def test_schema_inheritance(self): """Test tool creation with inherited schema""" def extended_func(base_param: str, extra_param: int) -> str: """Test function with inherited schema.""" return f"{base_param} {extra_param}" class BaseSchema(BaseModel): base_param: str class ExtendedSchema(BaseSchema): extra_param: int tool = CrewStructuredTool.from_function( func=extended_func, name="test_tool", args_schema=ExtendedSchema ) result = tool.invoke({"base_param": "test", "extra_param": 42}) assert result == "test 42" def test_default_values_in_schema(self): """Test handling of default values in schema""" def default_func( required_param: str, optional_param: str = "default", nullable_param: Optional[int] = None, ) -> str: """Test function with default values.""" return f"{required_param} {optional_param} {nullable_param}" tool = CrewStructuredTool.from_function( func=default_func, name="test_tool", description="Test defaults" ) # Test with minimal parameters result = tool.invoke({"required_param": "test"}) assert result == "test default None" # Test with all parameters result = tool.invoke( {"required_param": "test", "optional_param": "custom", "nullable_param": 42} ) assert result == "test custom 42"