""" End-to-end integration test: simulates a full dispatch cycle from HTTP request through MasterAgent to FinanceAgent response. Uses in-process mocks for Odoo and LLM — no external services needed. """ import asyncio import pytest from unittest.mock import AsyncMock, MagicMock from agent_service.llm.llm_types import LLMResponse from agent_service.agents.peer_bus import PeerBus from agent_service.agents.finance_agent import FinanceAgent from agent_service.agents.master_agent import MasterAgent @pytest.fixture def llm_router(): router = MagicMock() # First call: classify intent # Second+ calls: agent execution router.get_backend = AsyncMock(return_value='ollama') classify_response = LLMResponse( content='{"intent": "overdue_invoices", "agents": ["finance_agent"], "confidence": 0.95, "context": {}}', tool_calls=[], backend_used='ollama', model_used='llama3', tokens_in=50, tokens_out=80, latency_ms=200, ) agent_response = LLMResponse( content='You have 2 overdue invoices totalling $125,000. Invoice #2 (BigCo, $120k) is 95 days overdue and has been flagged for review.', tool_calls=[], backend_used='ollama', model_used='llama3', tokens_in=200, tokens_out=150, latency_ms=800, ) router.complete = AsyncMock(side_effect=[classify_response, agent_response]) return router @pytest.fixture def mock_finance_tools(): ft = MagicMock() ft.get_overdue_invoices = AsyncMock(return_value=[ {'id': 1, 'partner_name': 'ACME', 'amount_residual': 5000.0, 'days_overdue': 45}, {'id': 2, 'partner_name': 'BigCo', 'amount_residual': 120000.0, 'days_overdue': 95}, ]) ft.get_financial_summary = AsyncMock(return_value={'total_invoiced': 200000.0, 'collection_rate': 75.0}) ft.get_invoices = AsyncMock(return_value=[]) ft.flag_for_review = AsyncMock(return_value=True) ft.send_payment_reminder = AsyncMock(return_value=True) ft.post_chatter_note = AsyncMock(return_value=True) ft.get_payment_history = AsyncMock(return_value=[]) return ft @pytest.fixture def mock_odoo(): odoo = MagicMock() odoo.search_read = AsyncMock(return_value=[]) odoo.write = AsyncMock() odoo.call = AsyncMock(return_value=True) odoo.ping = AsyncMock(return_value=True) return odoo @pytest.mark.asyncio async def test_e2e_overdue_invoice_query(mock_odoo, llm_router, mock_finance_tools): peer_bus = PeerBus() finance_agent = FinanceAgent(odoo=mock_odoo, llm=llm_router, peer_bus=peer_bus) finance_agent._ft = mock_finance_tools peer_bus.register('finance_agent', finance_agent) master = MasterAgent( odoo=mock_odoo, llm=llm_router, memory=None, peer_bus=peer_bus, registry=None, ) result = await asyncio.wait_for( master.handle_message( user_id='test_user_1', message='What are my overdue invoices?', context={}, ), timeout=30, ) assert result is not None assert result.reply assert len(result.reply) > 10 mock_finance_tools.get_overdue_invoices.assert_awaited() @pytest.mark.asyncio async def test_e2e_peer_bus_communication(mock_odoo, llm_router, mock_finance_tools): peer_bus = PeerBus() finance_agent = FinanceAgent(odoo=mock_odoo, llm=llm_router, peer_bus=peer_bus) finance_agent._ft = mock_finance_tools peer_bus.register('finance_agent', finance_agent) from agent_service.agents.peer_bus import PeerResponse resp = await peer_bus.call('finance_agent', {'type': 'overdue_summary'}) assert isinstance(resp, PeerResponse) assert resp.available is True assert 'overdue_count' in resp.data