test: align MCP transient reconnect coverage
maintainer edit: update the existing transient retry tests for reconnect-first behavior and keep structured retry-failure coverage in the focused MCP transient suite.
This commit is contained in:
@@ -16,6 +16,7 @@ from nanobot.agent.tools.mcp import (
|
||||
_is_session_terminated,
|
||||
_is_transient,
|
||||
)
|
||||
from nanobot.agent.tools.registry import is_tool_error_result
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _is_transient helper
|
||||
@@ -134,6 +135,7 @@ async def test_tool_fails_after_retry_exhausted():
|
||||
|
||||
assert "failed after retry" in output
|
||||
assert "ClosedResourceError" in output
|
||||
assert is_tool_error_result(wrapper.name, output)
|
||||
assert session.call_tool.call_count == 2
|
||||
|
||||
|
||||
@@ -237,12 +239,10 @@ async def test_tool_retry_on_end_of_stream():
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_reconnects_when_transient_retry_reveals_terminated_session():
|
||||
"""Tool should reconnect if a stale session reports termination after transient retry."""
|
||||
async def test_tool_reconnects_on_transient_failure():
|
||||
"""Tool should reconnect when a stale session reports a transient stream failure."""
|
||||
old_session = AsyncMock()
|
||||
old_session.call_tool = AsyncMock(
|
||||
side_effect=[_FakeClosedResourceError("closed"), _session_terminated_error()]
|
||||
)
|
||||
old_session.call_tool = AsyncMock(side_effect=_FakeClosedResourceError("closed"))
|
||||
new_session = AsyncMock()
|
||||
new_session.call_tool = AsyncMock(return_value=_make_tool_result("fresh"))
|
||||
|
||||
@@ -257,12 +257,13 @@ async def test_tool_reconnects_when_transient_retry_reveals_terminated_session()
|
||||
|
||||
wrapper.set_reconnect_handler(reconnect)
|
||||
|
||||
with patch("nanobot.agent.tools.mcp.asyncio.sleep", new_callable=AsyncMock):
|
||||
with patch("nanobot.agent.tools.mcp.asyncio.sleep", new_callable=AsyncMock) as mock_sleep:
|
||||
output = await wrapper.execute(foo="bar")
|
||||
|
||||
assert output == "fresh"
|
||||
assert old_session.call_tool.call_count == 2
|
||||
assert old_session.call_tool.call_count == 1
|
||||
assert new_session.call_tool.call_count == 1
|
||||
mock_sleep.assert_not_called()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -478,61 +478,6 @@ async def test_execute_handles_generic_exception() -> None:
|
||||
assert is_tool_error_result(wrapper.name, result)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_reconnects_on_transient_failure() -> None:
|
||||
class ClosedResourceError(Exception):
|
||||
pass
|
||||
|
||||
reconnects = 0
|
||||
|
||||
async def stale_call_tool(_name: str, arguments: dict) -> object:
|
||||
raise ClosedResourceError("stream closed")
|
||||
|
||||
async def fresh_call_tool(_name: str, arguments: dict) -> object:
|
||||
return SimpleNamespace(content=[_FakeTextContent("ok")])
|
||||
|
||||
wrapper = _make_wrapper(SimpleNamespace(call_tool=stale_call_tool))
|
||||
|
||||
async def reconnect(_server_name: str, _tool_name: str, _stale_tool: object) -> object:
|
||||
nonlocal reconnects
|
||||
reconnects += 1
|
||||
return SimpleNamespace(_session=SimpleNamespace(call_tool=fresh_call_tool))
|
||||
|
||||
wrapper.set_reconnect_handler(reconnect)
|
||||
|
||||
result = await wrapper.execute()
|
||||
|
||||
assert result == "ok"
|
||||
assert reconnects == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_execute_marks_transient_retry_failure_as_tool_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
class ClosedResourceError(Exception):
|
||||
pass
|
||||
|
||||
calls = 0
|
||||
|
||||
async def call_tool(_name: str, arguments: dict) -> object:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
raise ClosedResourceError("closed")
|
||||
|
||||
async def fast_sleep(_delay: float) -> None:
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(mcp_mod.asyncio, "sleep", fast_sleep)
|
||||
wrapper = _make_wrapper(SimpleNamespace(call_tool=call_tool))
|
||||
|
||||
result = await wrapper.execute()
|
||||
|
||||
assert calls == 2
|
||||
assert result == "(MCP tool call failed after retry: ClosedResourceError)"
|
||||
assert is_tool_error_result(wrapper.name, result)
|
||||
|
||||
|
||||
def _make_tool_def(name: str) -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
name=name,
|
||||
|
||||
Reference in New Issue
Block a user