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>
61 lines
2.2 KiB
Python
61 lines
2.2 KiB
Python
import pytest
|
|
from fastapi.testclient import TestClient
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
|
|
@pytest.fixture
|
|
def app_with_mock_master():
|
|
mock_master = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.directive_id = 'test-directive-1'
|
|
mock_response.reply = 'You have 3 overdue invoices totalling $15,000.'
|
|
mock_response.agent_reports = []
|
|
mock_response.escalations = []
|
|
mock_response.actions_taken = []
|
|
mock_master.handle_message = AsyncMock(return_value=mock_response)
|
|
|
|
with patch('agent_service.app_state.get_master_agent', return_value=mock_master):
|
|
from agent_service.main import create_app
|
|
app = create_app()
|
|
yield app
|
|
|
|
|
|
def test_dispatch_returns_reply(app_with_mock_master):
|
|
client = TestClient(app_with_mock_master, raise_server_exceptions=False)
|
|
resp = client.post('/dispatch', json={
|
|
'user_id': '42',
|
|
'message': 'What are my overdue invoices?',
|
|
'context': {},
|
|
})
|
|
assert resp.status_code in (200, 503)
|
|
|
|
|
|
def test_dispatch_rate_limit():
|
|
mock_master = MagicMock()
|
|
mock_response = MagicMock()
|
|
mock_response.directive_id = 'x'
|
|
mock_response.reply = 'ok'
|
|
mock_response.agent_reports = []
|
|
mock_response.escalations = []
|
|
mock_response.actions_taken = []
|
|
mock_master.handle_message = AsyncMock(return_value=mock_response)
|
|
|
|
with patch('agent_service.app_state.get_master_agent', return_value=mock_master), \
|
|
patch('agent_service.routers.dispatch._rate_limit_store', {}):
|
|
from agent_service.main import create_app
|
|
app = create_app()
|
|
client = TestClient(app, raise_server_exceptions=False)
|
|
for _ in range(31):
|
|
client.post('/dispatch', json={'user_id': 'ratelimit_user', 'message': 'test', 'context': {}})
|
|
|
|
|
|
def test_health_endpoint():
|
|
with patch('agent_service.app_state.get_master_agent', return_value=MagicMock()):
|
|
from agent_service.main import create_app
|
|
app = create_app()
|
|
client = TestClient(app, raise_server_exceptions=False)
|
|
resp = client.get('/health')
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert 'status' in data
|