Add comprehensive unit tests for all agent service components

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 04:00:45 +00:00
parent 6c22a9a128
commit 20a69313d7
19 changed files with 3734 additions and 66 deletions

View File

@@ -1,69 +1,171 @@
"""Unit tests for PeerBus — request routing, circular detection, depth limit, timeout."""
import asyncio
import pytest
from unittest.mock import AsyncMock, MagicMock
from agent_service.agents.peer_bus import PeerBus, PeerResponse
from agent_service.agents.peer_bus import PeerBus, PeerResponse, PeerCircularRequestError
class MockAgent:
name = 'mock_agent'
def _make_registry(agents=None, active=None):
registry = MagicMock()
agents = agents or {}
active_set = set(active) if active is not None else set(agents.keys())
async def handle_peer_request(self, request: dict) -> dict:
return {'answer': 42, 'echo': request.get('data')}
async def is_active(key):
return key in active_set
registry.is_active = is_active
registry.get_agent_instance = lambda key: agents.get(key)
return registry
class SlowAgent:
name = 'slow_agent'
def _make_agent(response=None):
agent = MagicMock()
agent.handle_peer_request = AsyncMock(
return_value=response if response is not None else {'answer': 42, 'success': True}
)
return agent
async def handle_peer_request(self, request: dict) -> dict:
await asyncio.sleep(35)
return {}
# ── basic request routing ────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_peer_bus_register_and_call():
bus = PeerBus()
bus.register('mock_agent', MockAgent())
resp = await bus.call('mock_agent', {'data': 'hello'})
async def test_request_routes_to_agent():
agent = _make_agent({'answer': 42, 'success': True})
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
resp = await bus.request('from_agent', 'mock_agent', 'some_request', {}, 'reason')
assert isinstance(resp, PeerResponse)
assert resp.available is True
assert resp.success is True
assert resp.data.get('answer') == 42
@pytest.mark.asyncio
async def test_peer_bus_unknown_agent():
bus = PeerBus()
resp = await bus.call('nonexistent_agent', {})
async def test_request_passes_correct_args_to_handler():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
await bus.request('from_agent', 'mock_agent', 'query_type', {'key': 'val'}, 'reason')
agent.handle_peer_request.assert_awaited_once_with('query_type', {'key': 'val'}, 'd1')
@pytest.mark.asyncio
async def test_request_records_call_log():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
await bus.request('from_agent', 'mock_agent', 'some_type', {}, 'reason')
assert len(bus.call_log) == 1
assert bus.call_log[0]['from'] == 'from_agent'
assert bus.call_log[0]['to'] == 'mock_agent'
assert bus.call_log[0]['type'] == 'some_type'
# ── inactive / missing agent ─────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_request_inactive_agent_returns_unavailable():
reg = _make_registry(agents={}, active=set())
bus = PeerBus(reg, directive_id='d1')
resp = await bus.request('from_agent', 'inactive_agent', 'some_type', {}, 'reason')
assert resp.available is False
@pytest.mark.asyncio
async def test_peer_bus_max_depth():
bus = PeerBus()
bus.register('mock_agent', MockAgent())
call_chain = ['a', 'b', 'c']
resp = await bus.call('mock_agent', {}, _call_chain=call_chain)
async def test_request_no_instance_returns_unavailable():
reg = _make_registry(agents={}, active={'ghost_agent'})
bus = PeerBus(reg, directive_id='d1')
resp = await bus.request('from_agent', 'ghost_agent', 'some_type', {}, 'reason')
assert resp.available is False
# ── circular detection ───────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_circular_request_raises():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
bus._call_chain = ['mock_agent']
with pytest.raises(PeerCircularRequestError):
await bus.request('some_agent', 'mock_agent', 'some_type', {}, 'reason')
# ── max depth limit ──────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_max_depth_returns_unavailable():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
bus._call_chain = ['a', 'b', 'c'] # already at MAX_DEPTH=3
resp = await bus.request('some_agent', 'mock_agent', 'some_type', {}, 'reason')
assert resp.available is False
assert 'depth' in str(resp.error).lower() or 'max' in str(resp.error).lower()
# ── timeout ──────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_timeout_returns_success_false():
agent = MagicMock()
async def slow_handler(request_type, params, directive_id):
await asyncio.sleep(35)
return {}
agent.handle_peer_request = slow_handler
reg = _make_registry(agents={'slow_agent': agent})
bus = PeerBus(reg, directive_id='d1')
import unittest.mock as um
with um.patch('asyncio.wait_for', side_effect=asyncio.TimeoutError):
resp = await bus.request('from_agent', 'slow_agent', 'some_type', {}, 'reason')
assert resp.available is True
assert resp.success is False
assert 'timeout' in str(resp.error).lower()
# ── exception from agent ─────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_agent_exception_returns_success_false():
agent = MagicMock()
agent.handle_peer_request = AsyncMock(side_effect=RuntimeError('boom'))
reg = _make_registry(agents={'bad_agent': agent})
bus = PeerBus(reg, directive_id='d1')
resp = await bus.request('from_agent', 'bad_agent', 'some_type', {}, 'reason')
assert resp.available is True
assert resp.success is False
assert 'boom' in str(resp.error)
# ── call_log ─────────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_call_log_grows_with_requests():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
await bus.request('a', 'mock_agent', 't1', {}, 'r1')
await bus.request('b', 'mock_agent', 't2', {}, 'r2')
assert len(bus.call_log) == 2
@pytest.mark.asyncio
async def test_peer_bus_timeout():
bus = PeerBus()
bus.register('slow_agent', SlowAgent())
resp = await bus.call('slow_agent', {})
assert resp.available is False
async def test_call_log_empty_initially():
reg = _make_registry()
bus = PeerBus(reg, directive_id='d1')
assert bus.call_log == []
# ── PeerResponse fields ──────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_peer_bus_circular_detection():
bus = PeerBus()
bus.register('mock_agent', MockAgent())
resp = await bus.call('mock_agent', {}, _call_chain=['mock_agent'])
assert resp.available is False
def test_peer_bus_get_agent():
bus = PeerBus()
agent = MockAgent()
bus.register('mock_agent', agent)
assert bus.get_agent('mock_agent') is agent
assert bus.get_agent('missing') is None
async def test_response_has_agent_and_request_type():
agent = _make_agent()
reg = _make_registry(agents={'mock_agent': agent})
bus = PeerBus(reg, directive_id='d1')
resp = await bus.request('from_agent', 'mock_agent', 'my_type', {}, 'reason')
assert resp.agent == 'mock_agent'
assert resp.request_type == 'my_type'