Files
openswarm/backend/tests/test_tool_result_hook.py
T

68 lines
2.8 KiB
Python

"""Unit coverage for the extracted PostToolUse hook (tool_result_hook). The streaming harness
mocks claude_agent_sdk.query, so it never fires the SDK's PostToolUse hooks; this pins the
behavior directly: a tool result becomes a tool_result message, and an Agent tool spawns a
sub-session into the manager's LIVE registry (the InstanceOf[dict] sharing, the subtle bit)."""
import pytest
from unittest.mock import patch, AsyncMock
from backend.apps.agents.core.models import AgentSession
from backend.apps.agents.manager.streaming.hook_context import HookContext
from backend.apps.agents.manager.streaming import tool_result_hook
def _ctx(registry: dict) -> HookContext:
session = AgentSession(name="t", model="sonnet", dashboard_id="d")
registry[session.id] = session
return HookContext(
session=session,
session_id=session.id,
prompt="hi",
builtin_perms={},
policy_defaults={},
sessions=registry,
)
@pytest.mark.asyncio
async def test_normal_tool_result_appends_message_and_continues():
registry: dict = {}
ctx = _ctx(registry)
before = len(ctx.session.messages)
with patch.object(tool_result_hook.ws_manager, "send_to_session", new=AsyncMock()) as send:
out = await tool_result_hook.post_tool_hook(
ctx, {"tool_name": "Read", "tool_response": "file body", "tool_input": {"file_path": "/x"}}, "tu1", None
)
assert out == {"continue_": True}
assert len(ctx.session.messages) == before + 1
msg = ctx.session.messages[-1]
assert msg.role == "tool_result"
assert "file body" in str(msg.content)
send.assert_awaited() # the tool_result is broadcast to the UI
@pytest.mark.asyncio
async def test_agent_tool_spawns_subsession_into_live_registry():
registry: dict = {}
ctx = _ctx(registry)
parent_id = ctx.session_id
raw = {
"content": [{"type": "text", "text": "sub-agent did the work"}],
"usage": {"input_tokens": 7, "output_tokens": 3},
"total_cost_usd": 0.01,
"model": "sonnet",
}
with patch.object(tool_result_hook.ws_manager, "send_to_session", new=AsyncMock()), \
patch.object(tool_result_hook.ws_manager, "broadcast_global", new=AsyncMock()):
out = await tool_result_hook.post_tool_hook(
ctx, {"tool_name": "Agent", "tool_response": raw, "tool_input": {"prompt": "do x"}}, "tu1", None
)
assert out == {"continue_": True}
# exactly one NEW session registered (besides the parent), parented correctly
children = [s for sid, s in registry.items() if sid != parent_id]
assert len(children) == 1
child = children[0]
assert child.parent_session_id == parent_id
assert child.active_mcps == [] # context-isolation invariant: no inherited activations
assert "sub-agent did the work" in str(child.messages[-1].content)