Sweep coordinator (Step 16): - SweepCoordinator runs all 8 agents in parallel with 60s per-agent / 300s total timeout - Aggregates findings, actions, errors into SweepCoordinatorResult - Registered in FastAPI lifespan; triggered via POST /sweep Structured logging (Step 18): - logging_utils/structured.py: JSONFormatter emitting ts/level/logger/msg + custom fields - log_directive_event() for structured directive lifecycle logging - push_to_loki() async Loki push (graceful no-op if LOKI_URL unset) - configure_logging() replaces root handler at startup Tests (Steps 17+19): - conftest.py: mock_odoo, mock_pool, mock_llm fixtures - test_tool_validator.py: 9 tests covering validation, coercion, hallucination stripping - test_llm_router.py: 6 tests covering local/cloud/hybrid modes and HIPAA enforcement - test_peer_bus.py: 6 tests covering registration, timeout, depth, circular detection - test_finance_agent.py: 10 tests covering all 6 steps + sweep + peer request - test_memory_manager.py: 3 tests covering context build + hard cap enforcement - test_dispatch_router.py: 3 tests covering dispatch, rate limit, health endpoint - test_odoo_client.py: 4 tests covering search_read, write result, unlink warning - test_e2e_dispatch.py: 2 E2E tests - full dispatch cycle + peer bus communication README (Step 20): architecture diagram, privacy modes, quick start, env vars, structure Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
70 lines
1.8 KiB
Python
70 lines
1.8 KiB
Python
import asyncio
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
from agent_service.agents.peer_bus import PeerBus, PeerResponse
|
|
|
|
|
|
class MockAgent:
|
|
name = 'mock_agent'
|
|
|
|
async def handle_peer_request(self, request: dict) -> dict:
|
|
return {'answer': 42, 'echo': request.get('data')}
|
|
|
|
|
|
class SlowAgent:
|
|
name = 'slow_agent'
|
|
|
|
async def handle_peer_request(self, request: dict) -> dict:
|
|
await asyncio.sleep(35)
|
|
return {}
|
|
|
|
|
|
@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'})
|
|
assert isinstance(resp, PeerResponse)
|
|
assert resp.available 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', {})
|
|
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)
|
|
assert resp.available is False
|
|
|
|
|
|
@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
|
|
|
|
|
|
@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
|