"""Edge case tests for tool registry.""" import pytest from alfred.agent.registry import Tool, make_tools from alfred.settings import settings class TestToolEdgeCases: """Edge case tests for Tool dataclass.""" def test_tool_creation(self): """Should create tool with all fields.""" tool = Tool( name="test_tool", description="Test description", func=lambda: {"status": "ok"}, parameters={"type": "object", "properties": {}}, ) assert tool.name == "test_tool" assert tool.description == "Test description" assert callable(tool.func) def test_tool_with_unicode_name(self): """Should handle unicode in tool name.""" tool = Tool( name="tool_日本語", description="Japanese tool", func=lambda: {}, parameters={}, ) assert "日本語" in tool.name def test_tool_with_unicode_description(self): """Should handle unicode in description.""" tool = Tool( name="test", description="日本語の説明 🔧", func=lambda: {}, parameters={}, ) assert "日本語" in tool.description def test_tool_with_complex_parameters(self): """Should handle complex parameter schemas.""" tool = Tool( name="complex", description="Complex tool", func=lambda: {}, parameters={ "type": "object", "properties": { "nested": { "type": "object", "properties": { "deep": { "type": "array", "items": {"type": "string"}, }, }, }, "enum_field": { "type": "string", "enum": ["a", "b", "c"], }, }, "required": ["nested"], }, ) assert "nested" in tool.parameters["properties"] def test_tool_with_empty_parameters(self): """Should handle empty parameters.""" tool = Tool( name="no_params", description="Tool with no parameters", func=lambda: {}, parameters={}, ) assert tool.parameters == {} def test_tool_with_none_func(self): """Should handle None func (though invalid).""" tool = Tool( name="invalid", description="Invalid tool", func=None, parameters={}, ) assert tool.func is None def test_tool_func_execution(self): """Should execute tool function.""" result_value = {"status": "ok", "data": "test"} tool = Tool( name="test", description="Test", func=lambda: result_value, parameters={}, ) result = tool.func() assert result == result_value def test_tool_func_with_args(self): """Should execute tool function with arguments.""" tool = Tool( name="test", description="Test", func=lambda x, y: {"sum": x + y}, parameters={}, ) result = tool.func(1, 2) assert result["sum"] == 3 def test_tool_func_with_kwargs(self): """Should execute tool function with keyword arguments.""" tool = Tool( name="test", description="Test", func=lambda **kwargs: {"received": kwargs}, parameters={}, ) result = tool.func(a=1, b=2) assert result["received"]["a"] == 1 class TestMakeToolsEdgeCases: """Edge case tests for make_tools function.""" def test_make_tools_returns_dict(self, memory): """Should return dictionary of tools.""" tools = make_tools(settings) assert isinstance(tools, dict) def test_make_tools_all_tools_have_required_fields(self, memory): """Should have all required fields for each tool.""" tools = make_tools(settings) for name, tool in tools.items(): assert tool.name == name assert isinstance(tool.description, str) assert len(tool.description) > 0 assert callable(tool.func) assert isinstance(tool.parameters, dict) def test_make_tools_unique_names(self, memory): """Should have unique tool names.""" tools = make_tools(settings) names = list(tools.keys()) assert len(names) == len(set(names)) def test_make_tools_valid_parameter_schemas(self, memory): """Should have valid JSON Schema for parameters.""" tools = make_tools(settings) for tool in tools.values(): params = tool.parameters if params: assert "type" in params assert params["type"] == "object" if "properties" in params: assert isinstance(params["properties"], dict) def test_make_tools_required_params_in_properties(self, memory): """Should have required params defined in properties.""" tools = make_tools(settings) for tool in tools.values(): params = tool.parameters if "required" in params and "properties" in params: for req in params["required"]: assert req in params["properties"], ( f"Required param {req} not in properties for {tool.name}" ) def test_make_tools_descriptions_not_empty(self, memory): """Should have non-empty descriptions.""" tools = make_tools(settings) for tool in tools.values(): assert tool.description.strip() != "" def test_make_tools_funcs_callable(self, memory): """Should have callable functions.""" tools = make_tools(settings) for tool in tools.values(): assert callable(tool.func) def test_make_tools_expected_tools_present(self, memory): """Should have expected tools.""" tools = make_tools(settings) expected = [ "set_path_for_folder", "list_folder", "find_media_imdb_id", "find_torrent", # Changed from find_torrents "add_torrent_by_index", "add_torrent_to_qbittorrent", "get_torrent_by_index", "set_language", ] for name in expected: assert name in tools, f"Expected tool {name} not found" def test_make_tools_idempotent(self, memory): """Should return same tools on multiple calls.""" tools1 = make_tools(settings) tools2 = make_tools(settings) assert set(tools1.keys()) == set(tools2.keys()) def test_make_tools_parameter_types(self, memory): """Should have valid parameter types.""" tools = make_tools(settings) valid_types = ["string", "integer", "number", "boolean", "array", "object"] for tool in tools.values(): if "properties" in tool.parameters: for prop_name, prop_schema in tool.parameters["properties"].items(): if "type" in prop_schema: assert prop_schema["type"] in valid_types, ( f"Invalid type for {tool.name}.{prop_name}" ) def test_make_tools_enum_values(self, memory): """Should have valid enum values.""" tools = make_tools(settings) for tool in tools.values(): if "properties" in tool.parameters: for _prop_name, prop_schema in tool.parameters["properties"].items(): if "enum" in prop_schema: assert isinstance(prop_schema["enum"], list) assert len(prop_schema["enum"]) > 0 class TestToolExecution: """Tests for tool execution edge cases.""" def test_tool_returns_dict(self, memory, real_folder): """Should return dict from tool execution.""" tools = make_tools(settings) memory.ltm.set_config("download_folder", str(real_folder["downloads"])) result = tools["list_folder"].func(folder_type="download") assert isinstance(result, dict) def test_tool_returns_status(self, memory, real_folder): """Should return status in result.""" tools = make_tools(settings) memory.ltm.set_config("download_folder", str(real_folder["downloads"])) result = tools["list_folder"].func(folder_type="download") assert "status" in result or "error" in result def test_tool_handles_missing_args(self, memory): """Should handle missing required arguments.""" tools = make_tools(settings) with pytest.raises(TypeError): tools["set_path_for_folder"].func() # Missing required args def test_tool_handles_wrong_type_args(self, memory): """Should handle wrong type arguments.""" tools = make_tools(settings) # Pass wrong type - should either work or raise try: result = tools["get_torrent_by_index"].func(index="not an int") # If it doesn't raise, should return error assert "error" in result or "status" in result except (TypeError, ValueError): pass # Also acceptable def test_tool_handles_extra_args(self, memory, real_folder): """Should handle extra arguments.""" tools = make_tools(settings) memory.ltm.set_config("download_folder", str(real_folder["downloads"])) # Extra args should raise TypeError with pytest.raises(TypeError): tools["list_folder"].func( folder_type="download", extra_arg="should fail", )