feat(assistant): #91 steps humains en direct + outils web_search/fetch_url (labels backend, SSE step, garde SSRF)
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
"""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"
|
||||
Reference in New Issue
Block a user