201 lines
7.3 KiB
Python
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'
|