Files
odoo-ai/tests/test_sales_tools.py
2026-05-20 04:00:45 +00:00

201 lines
7.3 KiB
Python

"""Unit tests for SalesTools."""
import pytest
from unittest.mock import AsyncMock, MagicMock
from agent_service.tools.sales_tools import SalesTools
from agent_service.tools.odoo_client import WriteResult
def _make_tools():
odoo = MagicMock()
odoo.search_read = AsyncMock(return_value=[])
odoo.write = AsyncMock(return_value=WriteResult(
success=True, model='', record_id=None, action='write'))
odoo.create = AsyncMock(return_value=WriteResult(
success=True, model='', record_id=42, action='create'))
odoo.call = AsyncMock(return_value=True)
return SalesTools(odoo)
# ── get_sales_orders ──────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_sales_orders_default():
t = _make_tools()
result = await t.get_sales_orders()
domain = t._o.search_read.call_args[0][1]
assert ('state', '=', 'sale') in domain
assert isinstance(result, list)
@pytest.mark.asyncio
async def test_get_sales_orders_with_partner():
t = _make_tools()
await t.get_sales_orders(partner_id=5)
domain = t._o.search_read.call_args[0][1]
assert ('partner_id', '=', 5) in domain
@pytest.mark.asyncio
async def test_get_sales_orders_with_date_range():
t = _make_tools()
await t.get_sales_orders(date_from='2026-01-01', date_to='2026-01-31')
domain = t._o.search_read.call_args[0][1]
assert ('date_order', '>=', '2026-01-01') in domain
assert ('date_order', '<=', '2026-01-31') in domain
@pytest.mark.asyncio
async def test_get_sales_orders_custom_state():
t = _make_tools()
await t.get_sales_orders(state='draft')
domain = t._o.search_read.call_args[0][1]
assert ('state', '=', 'draft') in domain
# ── get_quotations ────────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_quotations_default():
t = _make_tools()
result = await t.get_quotations()
domain = t._o.search_read.call_args[0][1]
assert ('state', 'in', ['draft', 'sent']) in domain
assert isinstance(result, list)
@pytest.mark.asyncio
async def test_get_quotations_with_partner():
t = _make_tools()
await t.get_quotations(partner_id=7)
domain = t._o.search_read.call_args[0][1]
assert ('partner_id', '=', 7) in domain
# ── get_sales_summary ─────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_sales_summary_empty():
t = _make_tools()
result = await t.get_sales_summary()
assert result['order_count'] == 0
assert result['total_revenue'] == 0
@pytest.mark.asyncio
async def test_get_sales_summary_aggregates():
t = _make_tools()
t._o.search_read = AsyncMock(return_value=[
{'amount_total': 1000.0, 'user_id': [1, 'Alice']},
{'amount_total': 2000.0, 'user_id': [1, 'Alice']},
{'amount_total': 500.0, 'user_id': [2, 'Bob']},
])
result = await t.get_sales_summary()
assert result['order_count'] == 3
assert result['total_revenue'] == 3500.0
assert 'by_sales_rep' in result
@pytest.mark.asyncio
async def test_get_sales_summary_by_rep():
t = _make_tools()
t._o.search_read = AsyncMock(return_value=[
{'amount_total': 5000.0, 'user_id': [1, 'Alice']},
{'amount_total': 3000.0, 'user_id': [2, 'Bob']},
])
result = await t.get_sales_summary()
reps = result['by_sales_rep']
assert len(reps) == 2
assert reps[0]['total'] >= reps[1]['total'] # sorted descending
@pytest.mark.asyncio
async def test_get_sales_summary_with_dates():
t = _make_tools()
await t.get_sales_summary(date_from='2026-01-01', date_to='2026-01-31')
domain = t._o.search_read.call_args[0][1]
assert ('date_order', '>=', '2026-01-01') in domain
# ── get_customer_orders ───────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_get_customer_orders():
t = _make_tools()
t._o.search_read = AsyncMock(return_value=[
{'id': 1, 'name': 'SO/001', 'state': 'sale'}
])
result = await t.get_customer_orders(partner_id=5)
domain = t._o.search_read.call_args[0][1]
assert ('partner_id', '=', 5) in domain
assert len(result) == 1
# ── confirm_quotation ─────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_confirm_quotation_success():
t = _make_tools()
result = await t.confirm_quotation(order_id=3)
assert result is True
t._o.call.assert_awaited_once_with('sale.order', 'action_confirm', [[3]])
@pytest.mark.asyncio
async def test_confirm_quotation_handles_exception():
t = _make_tools()
t._o.call = AsyncMock(side_effect=Exception('access denied'))
result = await t.confirm_quotation(order_id=3)
assert result is False
# ── update_order_note ─────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_update_order_note_success():
t = _make_tools()
result = await t.update_order_note(order_id=1, note='Special instructions')
assert result is True
t._o.write.assert_awaited_once_with('sale.order', [1], {'note': 'Special instructions'})
@pytest.mark.asyncio
async def test_update_order_note_failure():
t = _make_tools()
t._o.write = AsyncMock(return_value=WriteResult(
success=False, model='', record_id=None, action='write'))
result = await t.update_order_note(order_id=1, note='note')
assert result is False
# ── flag_for_review ───────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_flag_for_review():
t = _make_tools()
result = await t.flag_for_review('sale.order', 1, 'Large discount applied')
assert result is True
call_args = t._o.call.call_args[0]
assert 'message_post' in str(call_args)
body = call_args[3]['body']
assert '[AI FLAG - MEDIUM]' in body
assert 'Large discount applied' in body
@pytest.mark.asyncio
async def test_flag_for_review_high_severity():
t = _make_tools()
await t.flag_for_review('sale.order', 1, 'reason', severity='high')
body = t._o.call.call_args[0][3]['body']
assert 'HIGH' in body
# ── post_chatter_note ─────────────────────────────────────────────────────────
@pytest.mark.asyncio
async def test_post_chatter_note():
t = _make_tools()
result = await t.post_chatter_note('sale.order', 1, 'Order confirmed by AI')
assert result is True
call_args = t._o.call.call_args[0]
assert call_args[3]['body'] == 'Order confirmed by AI'