oclaw/tests/test_mcp_timeout_and_cache.py
oliver d3f621b58b Cache inventory MCP lists and accept YES as WhatsApp confirm.
Extend short TTL reuse to listManagedNe/queryUmeNeInventory to cut list self-loops, and treat short YES/continue as confirmation when a token is pending.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 22:56:50 +08:00

133 lines
5.5 KiB
Python

from __future__ import annotations
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from svc.persistence.sqlite_store import SqliteStore
from runtime.tools.mcp.adapter import (
clear_list_cli_targets_cache,
materialize_mcp_tools,
mcp_timeout_for_tool,
)
from runtime.tools.tool_validation import format_invalid_arguments_error, validate_tool_arguments
class McpTimeoutAndCacheTests(unittest.TestCase):
def test_exec_managed_ne_timeout_override(self) -> None:
self.assertEqual(mcp_timeout_for_tool("execManagedNe", 30.0), 320.0)
self.assertEqual(mcp_timeout_for_tool("ping", 30.0), 30.0)
self.assertGreaterEqual(mcp_timeout_for_tool("sqlQueryUme", 30.0), 90.0)
def test_materialize_applies_exec_timeout(self) -> None:
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td:
store = SqliteStore(str(Path(td) / "ops.sqlite"))
store.upsert_mcp_server(
server_id="netx",
source_type="github",
source_ref="local",
entry_command="python",
entry_args=["-m", "netx_mcp"],
enabled=True,
timeout_s=30.0,
)
store.replace_mcp_server_tools(
server_id="netx",
tools=[
{
"tool_name": "execManagedNe",
"description": "exec",
"parameters": {"type": "object", "properties": {}},
},
{
"tool_name": "listCliTargets",
"description": "list",
"parameters": {"type": "object", "properties": {}},
},
],
)
specs = {s.name: s for s in materialize_mcp_tools(store)}
self.assertEqual(specs["mcp__netx__execManagedNe"].timeout_s, 320.0)
self.assertEqual(specs["mcp__netx__listCliTargets"].timeout_s, 30.0)
def test_list_cli_targets_ttl_cache(self) -> None:
clear_list_cli_targets_cache()
with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as td:
store = SqliteStore(str(Path(td) / "ops.sqlite"))
store.upsert_mcp_server(
server_id="netx",
source_type="github",
source_ref="local",
entry_command="python",
entry_args=["-m", "netx_mcp"],
enabled=True,
)
store.replace_mcp_server_tools(
server_id="netx",
tools=[
{
"tool_name": "listCliTargets",
"description": "list",
"parameters": {"type": "object", "properties": {}},
},
{
"tool_name": "queryUmeNeInventory",
"description": "inventory",
"parameters": {"type": "object", "properties": {}},
},
{
"tool_name": "listManagedNe",
"description": "managed",
"parameters": {"type": "object", "properties": {}},
},
],
)
specs = {s.name: s for s in materialize_mcp_tools(store)}
calls = {"n": 0}
def fake_call_tool(self, tool_name, arguments=None): # type: ignore[no-untyped-def]
calls["n"] += 1
return {"ok": True, "data": {"items": [{"ne_id": "1"}], "tool": tool_name}}
with patch("runtime.tools.mcp.adapter.McpProcessRuntime.call_tool", fake_call_tool):
cli = specs["mcp__netx__listCliTargets"]
inv = specs["mcp__netx__queryUmeNeInventory"]
managed = specs["mcp__netx__listManagedNe"]
first = cli.handler({"keyword": "PE", "source": "ume"})
second = cli.handler({"keyword": "PE", "source": "ume"})
inv1 = inv.handler({"keyword": "core"})
inv2 = inv.handler({"keyword": "core"})
m1 = managed.handler({"keyword": "x", "vendor": "huawei", "connect_status": "online"})
m2 = managed.handler({"keyword": "x", "vendor": "huawei", "connect_status": "online"})
self.assertEqual(calls["n"], 3)
self.assertFalse(first.get("cache_hit"))
self.assertTrue(second.get("cache_hit"))
self.assertTrue(inv2.get("cache_hit"))
self.assertTrue(m2.get("cache_hit"))
self.assertEqual(second.get("data", {}).get("items", [])[0]["ne_id"], "1")
clear_list_cli_targets_cache()
class InvalidArgFormatTests(unittest.TestCase):
def test_format_includes_example(self) -> None:
schema = {
"type": "object",
"properties": {
"command": {"type": "string"},
"timeout": {"type": "integer", "default": 30},
},
"required": ["command"],
"additionalProperties": False,
}
ok, err = validate_tool_arguments(schema, {})
self.assertFalse(ok)
payload = format_invalid_arguments_error(schema, str(err), lang="en")
self.assertEqual(payload["error_code"], "tool_invalid_arguments")
self.assertIn("example", payload)
self.assertIn("command", payload["example"])
self.assertIn("required", payload)
if __name__ == "__main__":
unittest.main()