""" Tests for alfred.agent.registry — tool registration and JSON schema generation. """ from alfred.agent.registry import Tool, _create_tool_from_function, make_tools from alfred.settings import settings # --------------------------------------------------------------------------- # _create_tool_from_function # --------------------------------------------------------------------------- class TestCreateToolFromFunction: def test_name_from_function(self): def my_tool(x: str) -> dict: """Does something.""" return {} tool = _create_tool_from_function(my_tool) assert tool.name == "my_tool" def test_description_from_docstring_first_line(self): def my_tool(x: str) -> dict: """First line description. More details here. """ return {} tool = _create_tool_from_function(my_tool) assert tool.description == "First line description." def test_description_fallback_to_name(self): def no_doc(x: str) -> dict: return {} tool = _create_tool_from_function(no_doc) assert tool.description == "no_doc" def test_required_params_without_default(self): def tool(a: str, b: int) -> dict: """Tool.""" return {} t = _create_tool_from_function(tool) assert "a" in t.parameters["required"] assert "b" in t.parameters["required"] def test_optional_params_not_required(self): def tool(a: str, b: str = "default") -> dict: """Tool.""" return {} t = _create_tool_from_function(tool) assert "a" in t.parameters["required"] assert "b" not in t.parameters["required"] def test_none_default_not_required(self): def tool(a: str, b: str | None = None) -> dict: """Tool.""" return {} t = _create_tool_from_function(tool) assert "b" not in t.parameters["required"] def test_type_mapping_str(self): def tool(x: str) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "string" def test_type_mapping_int(self): def tool(x: int) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "integer" def test_type_mapping_float(self): def tool(x: float) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "number" def test_type_mapping_bool(self): def tool(x: bool) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "boolean" def test_unknown_type_defaults_to_string(self): def tool(x: list) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "string" def test_no_annotation_defaults_to_string(self): def tool(x) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["properties"]["x"]["type"] == "string" def test_self_param_excluded(self): class MyClass: def tool(self, x: str) -> dict: """T.""" return {} t = _create_tool_from_function(MyClass().tool) assert "self" not in t.parameters["properties"] def test_parameters_schema_structure(self): def tool(a: str, b: int = 0) -> dict: """T.""" return {} t = _create_tool_from_function(tool) assert t.parameters["type"] == "object" assert "properties" in t.parameters assert "required" in t.parameters def test_func_stored_on_tool(self): def tool(x: str) -> dict: """T.""" return {"x": x} t = _create_tool_from_function(tool) assert t.func("hello") == {"x": "hello"} # --------------------------------------------------------------------------- # make_tools # --------------------------------------------------------------------------- class TestMakeTools: def test_returns_dict(self): tools = make_tools(settings) assert isinstance(tools, dict) def test_all_expected_tools_present(self): tools = make_tools(settings) expected = { "set_path_for_folder", "list_folder", "resolve_destination", "move_media", "manage_subtitles", "create_seed_links", "learn", "find_media_imdb_id", "find_torrent", "add_torrent_by_index", "add_torrent_to_qbittorrent", "get_torrent_by_index", "set_language", } assert expected.issubset(tools.keys()) def test_each_tool_is_tool_instance(self): tools = make_tools(settings) for name, tool in tools.items(): assert isinstance(tool, Tool), f"{name} is not a Tool instance" def test_each_tool_has_callable_func(self): tools = make_tools(settings) for name, tool in tools.items(): assert callable(tool.func), f"{name}.func is not callable" def test_tool_name_matches_key(self): tools = make_tools(settings) for key, tool in tools.items(): assert tool.name == key def test_resolve_destination_schema(self): tools = make_tools(settings) t = tools["resolve_destination"] props = t.parameters["properties"] required = t.parameters["required"] # Required args assert "release_name" in required assert "source_file" in required assert "tmdb_title" in required assert "tmdb_year" in required # Optional args not required assert "tmdb_episode_title" not in required assert "confirmed_folder" not in required # tmdb_year is int assert props["tmdb_year"]["type"] == "integer" def test_move_media_schema(self): tools = make_tools(settings) t = tools["move_media"] required = t.parameters["required"] assert "source" in required assert "destination" in required def test_create_seed_links_schema(self): tools = make_tools(settings) t = tools["create_seed_links"] required = t.parameters["required"] assert "library_file" in required assert "original_download_folder" in required def test_no_duplicate_tools(self): tools = make_tools(settings) # dict keys are unique by definition, but verify no name conflicts names = [t.name for t in tools.values()] assert len(names) == len(set(names))