Files
odoo-ai/tests/test_odoo_client.py
ActiveBlue Build 7487fc73f9 feat(infra): add sweep coordinator, structured logging, test suite, and README
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>
2026-04-12 18:08:11 -04:00

56 lines
2.0 KiB
Python

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from agent_service.tools.odoo_client import OdooClient, WriteResult
@pytest.fixture
def odoo():
client = OdooClient(url='http://localhost:8069', db='test', api_key='testkey')
return client
@pytest.mark.asyncio
async def test_search_read_returns_list(odoo):
with patch.object(odoo, '_call', new=AsyncMock(return_value=[
{'id': 1, 'name': 'Invoice 1'},
])):
result = await odoo.search_read('account.move', [('state', '=', 'posted')], ['name'])
assert isinstance(result, list)
assert result[0]['id'] == 1
@pytest.mark.asyncio
async def test_write_returns_write_result(odoo):
with patch.object(odoo, '_call', new=AsyncMock(return_value=True)), \
patch.object(odoo, 'search_read', new=AsyncMock(return_value=[{'id': 1, 'name': 'test'}])):
result = await odoo.write('account.move', [1], {'state': 'posted'})
assert isinstance(result, WriteResult)
assert result.success is True
@pytest.mark.asyncio
async def test_write_result_has_before_after(odoo):
before = [{'id': 1, 'amount_residual': 1000.0}]
after = [{'id': 1, 'amount_residual': 0.0}]
call_count = 0
async def mock_search_read(*args, **kwargs):
nonlocal call_count
call_count += 1
return before if call_count == 1 else after
with patch.object(odoo, '_call', new=AsyncMock(return_value=True)), \
patch.object(odoo, 'search_read', new=mock_search_read):
result = await odoo.write('account.move', [1], {'amount_residual': 0})
assert result.before == before[0]
assert result.after == after[0]
@pytest.mark.asyncio
async def test_unlink_logs_warning(odoo, caplog):
import logging
with patch.object(odoo, '_call', new=AsyncMock(return_value=True)):
with caplog.at_level(logging.WARNING):
await odoo.unlink('account.move', [99])
assert any('unlink' in r.message.lower() or '99' in r.message for r in caplog.records)