"""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'