"""Unit tests for the Notion-style step labels (backend.tools.labels).""" from backend.agent.loop import ToolCallRecord from backend.tools.labels import GENERIC_KEY, _STEP_LABELS, tool_step_label from backend.tools.registry import list_tools from backend.tools.context import ToolScope class TestToolStepLabel: def test_every_in_app_tool_has_a_label(self): # A registered tool without a mapping still degrades gracefully to the # generic key, but the curated coverage must stay complete for the UI. names = {spec.name for spec in list_tools(scope=ToolScope.IN_APP)} assert names.issubset(set(_STEP_LABELS)), names - set(_STEP_LABELS) def test_label_argument_names_match_input_models(self): # A mapped primary argument that the tool's input model does not # actually declare would silently render "{value}" placeholders — # verify every (name, arg) pair against the real schemas. from backend.tools.registry import get_tool for name, (_key, arg) in _STEP_LABELS.items(): if arg is None: continue spec = get_tool(name) assert spec is not None, name fields = spec.input_model.model_fields assert arg in fields, f"{name}: input model has no field '{arg}'" def test_named_tool_returns_key_and_value(self): label = tool_step_label("read_file", {"vault": "V", "path": "notes/a.md"}) assert label["key"] == "file_read" assert label["params"]["value"] == "notes/a.md" def test_search_tool_surfaces_the_query(self): label = tool_step_label("search_fulltext", {"q": "pizza", "vault": "V"}) assert label["key"] == "search" assert label["params"]["value"] == "pizza" def test_no_argument_tools_have_empty_params(self): label = tool_step_label("list_vaults", {}) assert label["key"] == "vaults" assert label["params"] == {} def test_missing_argument_degrades_to_key_only(self): label = tool_step_label("read_file", {}) assert label["key"] == "file_read" assert "value" not in label["params"] def test_unknown_tool_falls_back_to_generic(self): label = tool_step_label("future_tool_name", {"x": 1}) assert label["key"] == GENERIC_KEY assert label["params"]["tool"] == "future tool name" def test_arguments_none_is_accepted(self): assert tool_step_label("list_tags", None)["key"] == "tags" def test_tool_call_record_carries_step(self): rec = ToolCallRecord( name="read_file", arguments={"path": "a.md"}, ok=True, result={}, step=tool_step_label("read_file", {"path": "a.md"}), ) assert rec.step["key"] == "file_read" assert rec.step["params"]["value"] == "a.md"