mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-07 02:07:45 +02:00
[eric] tools_lib: leading-_ -> p_/public across tools_lib/mcp_config/mcp_discovery/oauth_tokens/tool_taxonomy; promote cross-file public (load_all_tools/sanitize_server_name/discover_mcp_tools_*), fix re-export + attr test refs
This commit is contained in:
@@ -25,8 +25,8 @@ def tools_tmp(tmp_path, monkeypatch):
|
||||
d = tmp_path / "tools"
|
||||
d.mkdir()
|
||||
monkeypatch.setattr(tools_lib, "DATA_DIR", str(d))
|
||||
monkeypatch.setattr(tools_lib, "_tools_cache", None)
|
||||
monkeypatch.setattr(tools_lib, "_tools_cache_sig", None)
|
||||
monkeypatch.setattr(tools_lib, "p_tools_cache", None)
|
||||
monkeypatch.setattr(tools_lib, "p_tools_cache_sig", None)
|
||||
return d
|
||||
|
||||
|
||||
@@ -71,40 +71,40 @@ def test_settings_file_deleted_falls_back_to_defaults(settings_tmp):
|
||||
|
||||
|
||||
def test_tools_write_then_list_is_fresh(tools_tmp):
|
||||
assert tools_lib._load_all() == []
|
||||
assert tools_lib.load_all_tools() == []
|
||||
t = ToolDefinition(name="Alpha", description="a")
|
||||
tools_lib._save(t)
|
||||
tools_lib.save(t)
|
||||
_bump_mtime(tools_tmp / f"{t.id}.json")
|
||||
names = [x.name for x in tools_lib._load_all()]
|
||||
names = [x.name for x in tools_lib.load_all_tools()]
|
||||
assert names == ["Alpha"]
|
||||
|
||||
t2 = ToolDefinition(name="Beta", description="b")
|
||||
tools_lib._save(t2)
|
||||
assert sorted(x.name for x in tools_lib._load_all()) == ["Alpha", "Beta"]
|
||||
tools_lib.save(t2)
|
||||
assert sorted(x.name for x in tools_lib.load_all_tools()) == ["Alpha", "Beta"]
|
||||
|
||||
|
||||
def test_tools_delete_detected(tools_tmp):
|
||||
t = ToolDefinition(name="Gone", description="g")
|
||||
tools_lib._save(t)
|
||||
assert [x.name for x in tools_lib._load_all()] == ["Gone"]
|
||||
tools_lib.save(t)
|
||||
assert [x.name for x in tools_lib.load_all_tools()] == ["Gone"]
|
||||
os.remove(tools_tmp / f"{t.id}.json")
|
||||
assert tools_lib._load_all() == []
|
||||
assert tools_lib.load_all_tools() == []
|
||||
|
||||
|
||||
def test_tools_in_place_rewrite_detected(tools_tmp):
|
||||
t = ToolDefinition(name="Old", description="x")
|
||||
tools_lib._save(t)
|
||||
assert [x.name for x in tools_lib._load_all()] == ["Old"]
|
||||
tools_lib.save(t)
|
||||
assert [x.name for x in tools_lib.load_all_tools()] == ["Old"]
|
||||
t.name = "New"
|
||||
tools_lib._save(t)
|
||||
tools_lib.save(t)
|
||||
_bump_mtime(tools_tmp / f"{t.id}.json")
|
||||
assert [x.name for x in tools_lib._load_all()] == ["New"]
|
||||
assert [x.name for x in tools_lib.load_all_tools()] == ["New"]
|
||||
|
||||
|
||||
def test_tools_cached_hit_skips_reparse(tools_tmp, monkeypatch):
|
||||
tools_lib._save(ToolDefinition(name="Once", description="o"))
|
||||
tools_lib._load_all()
|
||||
tools_lib.save(ToolDefinition(name="Once", description="o"))
|
||||
tools_lib.load_all_tools()
|
||||
def boom(*a, **k):
|
||||
raise AssertionError("disk re-parse on unchanged dir")
|
||||
monkeypatch.setattr(json, "load", boom)
|
||||
assert [x.name for x in tools_lib._load_all()] == ["Once"]
|
||||
assert [x.name for x in tools_lib.load_all_tools()] == ["Once"]
|
||||
|
||||
@@ -11,7 +11,7 @@ tool-name shape, including the round-trip that the old code failed.
|
||||
"""
|
||||
|
||||
from backend.apps.tools_lib.tools_lib import resolve_policy_slot, PolicySlot
|
||||
from backend.apps.tools_lib.mcp_config import _sanitize_server_name
|
||||
from backend.apps.tools_lib.mcp_config import sanitize_server_name
|
||||
from backend.apps.tools_lib.models import ToolDefinition
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ def test_slot_for_our_browser_and_invoke_agents_uses_inner_name():
|
||||
|
||||
def test_slot_for_community_mcp_points_at_the_owning_tool():
|
||||
tool = _mcp_tool("My Notion Server")
|
||||
slug = _sanitize_server_name(tool.name)
|
||||
slug = sanitize_server_name(tool.name)
|
||||
assert resolve_policy_slot(f"mcp__{slug}__notion-fetch", [tool]) == \
|
||||
PolicySlot("mcp", tool.id, "notion-fetch")
|
||||
|
||||
@@ -73,7 +73,7 @@ def test_always_approve_round_trips_for_every_tool_shape():
|
||||
"""The invariant the old code violated: after WRITE(always_allow), the very
|
||||
next READ returns always_allow, for builtin, our agents, and community MCP."""
|
||||
notion = _mcp_tool("Notion")
|
||||
slug = _sanitize_server_name("Notion")
|
||||
slug = sanitize_server_name("Notion")
|
||||
tools = [notion]
|
||||
builtin_perms: dict[str, str] = {}
|
||||
|
||||
@@ -94,7 +94,7 @@ def test_always_approve_round_trips_for_every_tool_shape():
|
||||
def test_two_actions_on_the_same_mcp_server_are_independent():
|
||||
"""Approving one action must not silently approve a sibling action."""
|
||||
tool = _mcp_tool("Notion")
|
||||
slug = _sanitize_server_name("Notion")
|
||||
slug = sanitize_server_name("Notion")
|
||||
tools = [tool]
|
||||
bp: dict[str, str] = {}
|
||||
_write(f"mcp__{slug}__notion-fetch", "always_allow", bp, tools)
|
||||
@@ -121,21 +121,21 @@ def test_builtin_policy_survives_a_real_file_reload(tmp_path, monkeypatch):
|
||||
|
||||
def test_mcp_policy_survives_a_real_tool_file_reload(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(tl, "DATA_DIR", str(tmp_path))
|
||||
monkeypatch.setattr(tl, "_tools_cache", None)
|
||||
monkeypatch.setattr(tl, "_tools_cache_sig", None)
|
||||
tl._save(_mcp_tool("Notion"))
|
||||
slug = _sanitize_server_name("Notion")
|
||||
monkeypatch.setattr(tl, "p_tools_cache", None)
|
||||
monkeypatch.setattr(tl, "p_tools_cache_sig", None)
|
||||
tl.save(_mcp_tool("Notion"))
|
||||
slug = sanitize_server_name("Notion")
|
||||
name = f"mcp__{slug}__notion-fetch"
|
||||
|
||||
# WRITE via the resolver against the freshly loaded tool, then persist.
|
||||
tools = tl._load_all()
|
||||
tools = tl.load_all_tools()
|
||||
slot = tl.resolve_policy_slot(name, tools)
|
||||
target = next(t for t in tools if t.id == slot.key)
|
||||
target.tool_permissions[slot.action] = "always_allow"
|
||||
tl._save(target)
|
||||
tl.save(target)
|
||||
|
||||
# RELOAD from disk and read via the resolver: the policy is there.
|
||||
tools2 = tl._load_all()
|
||||
tools2 = tl.load_all_tools()
|
||||
rslot = tl.resolve_policy_slot(name, tools2)
|
||||
got = next(t for t in tools2 if t.id == rslot.key)
|
||||
assert got.tool_permissions.get(rslot.action) == "always_allow"
|
||||
|
||||
@@ -641,7 +641,7 @@ async def test_mcp_gate_only_forwards_activated_servers():
|
||||
auth_status="configured", auth_type="apikey") for n in names]
|
||||
|
||||
# allowed_tools == get_all_tool_names() bypasses the (separate) permission
|
||||
# gate so we isolate the ACTIVATION gate. _sanitize_server_name -> identity.
|
||||
# gate so we isolate the ACTIVATION gate. sanitize_server_name -> identity.
|
||||
with patch("backend.apps.agents.manager.RunSupportMixin.load_all_tools", side_effect=installed), \
|
||||
patch("backend.apps.agents.manager.RunSupportMixin.get_all_tool_names", return_value=["__ALL__"]), \
|
||||
patch("backend.apps.agents.manager.RunSupportMixin.sanitize_server_name", side_effect=lambda n: n), \
|
||||
@@ -810,9 +810,9 @@ def test_mcp_brand_covers_curated_servers():
|
||||
"google-workspace", "microsoft-365", "slack", "discord",
|
||||
"notion", "airtable", "hubspot", "reddit", "youtube",
|
||||
}
|
||||
from backend.apps.tools_lib.tools_lib import _sanitize_server_name
|
||||
from backend.apps.tools_lib.tools_lib import sanitize_server_name
|
||||
for slug in curated:
|
||||
assert _sanitize_server_name(slug) == slug, (
|
||||
assert sanitize_server_name(slug) == slug, (
|
||||
f"curated slug {slug!r} is not in sanitized form"
|
||||
)
|
||||
|
||||
@@ -828,30 +828,30 @@ def test_curated_server_aliases_in_main():
|
||||
|
||||
|
||||
def test_sanitize_server_name_idempotent():
|
||||
"""_sanitize_server_name must be idempotent (sanitize twice = sanitize once)."""
|
||||
from backend.apps.tools_lib.tools_lib import _sanitize_server_name
|
||||
"""sanitize_server_name must be idempotent (sanitize twice = sanitize once)."""
|
||||
from backend.apps.tools_lib.tools_lib import sanitize_server_name
|
||||
test_inputs = [
|
||||
"Google Workspace", "Microsoft 365", "Slack", "Discord",
|
||||
"Notion", "Airtable", "HubSpot", "Reddit", "YouTube",
|
||||
"GitHub", "GitLab", "Jira",
|
||||
]
|
||||
for raw in test_inputs:
|
||||
once = _sanitize_server_name(raw)
|
||||
twice = _sanitize_server_name(once)
|
||||
once = sanitize_server_name(raw)
|
||||
twice = sanitize_server_name(once)
|
||||
assert once == twice, f"{raw}: sanitize not idempotent ({once} != {twice})"
|
||||
|
||||
|
||||
def test_sanitize_server_name_lowercase():
|
||||
from backend.apps.tools_lib.tools_lib import _sanitize_server_name
|
||||
assert _sanitize_server_name("Gmail") == "gmail"
|
||||
assert _sanitize_server_name("UPPERCASE") == "uppercase"
|
||||
from backend.apps.tools_lib.tools_lib import sanitize_server_name
|
||||
assert sanitize_server_name("Gmail") == "gmail"
|
||||
assert sanitize_server_name("UPPERCASE") == "uppercase"
|
||||
|
||||
|
||||
def test_sanitize_server_name_strips_special_chars():
|
||||
from backend.apps.tools_lib.tools_lib import _sanitize_server_name
|
||||
assert _sanitize_server_name("Foo Bar!") == "foo-bar"
|
||||
assert _sanitize_server_name("@x/y") == "x-y"
|
||||
assert _sanitize_server_name("a__b") == "a-b"
|
||||
from backend.apps.tools_lib.tools_lib import sanitize_server_name
|
||||
assert sanitize_server_name("Foo Bar!") == "foo-bar"
|
||||
assert sanitize_server_name("@x/y") == "x-y"
|
||||
assert sanitize_server_name("a__b") == "a-b"
|
||||
|
||||
|
||||
# ===========================================================================
|
||||
@@ -864,10 +864,10 @@ def test_mcp_activate_handler_unknown_server():
|
||||
# We test the response shape independently of the FastAPI plumbing.
|
||||
# The handler is a closure inside main.py:mcp_meta_handler, so we
|
||||
# instead exercise the contract: invalid name surfaces alternatives.
|
||||
from backend.apps.tools_lib.tools_lib import _sanitize_server_name
|
||||
from backend.apps.tools_lib.tools_lib import sanitize_server_name
|
||||
valid = {"gmail", "slack", "google-workspace"}
|
||||
requested = "Gmail" # raw, needs sanitize
|
||||
sanitized = _sanitize_server_name(requested)
|
||||
sanitized = sanitize_server_name(requested)
|
||||
if sanitized in valid:
|
||||
status = "would_activate"
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user