214 lines
7.9 KiB
Python
214 lines
7.9 KiB
Python
"""Unit tests for FinanceTools."""
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
from agent_service.tools.finance_tools import FinanceTools
|
|
|
|
|
|
def _make_tools():
|
|
odoo = MagicMock()
|
|
odoo.search_read = AsyncMock(return_value=[])
|
|
odoo.read = AsyncMock(return_value=[])
|
|
odoo.call = AsyncMock(return_value=True)
|
|
odoo.post_chatter = AsyncMock(return_value=99)
|
|
return FinanceTools(odoo)
|
|
|
|
|
|
# ── get_invoices ──────────────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_invoices_default():
|
|
t = _make_tools()
|
|
result = await t.get_invoices()
|
|
t._odoo.search_read.assert_awaited_once()
|
|
assert isinstance(result, list)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_invoices_state_filter():
|
|
t = _make_tools()
|
|
await t.get_invoices(state='posted')
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['state', '=', 'posted'] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_invoices_move_type_filter():
|
|
t = _make_tools()
|
|
await t.get_invoices(move_type='out_invoice')
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['move_type', '=', 'out_invoice'] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_invoices_partner_filter():
|
|
t = _make_tools()
|
|
await t.get_invoices(partner_id=5)
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['partner_id', '=', 5] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_invoices_date_range():
|
|
t = _make_tools()
|
|
await t.get_invoices(date_from='2026-01-01', date_to='2026-01-31')
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['invoice_date', '>=', '2026-01-01'] in domain
|
|
assert ['invoice_date', '<=', '2026-01-31'] in domain
|
|
|
|
|
|
# ── get_overdue_invoices ──────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_overdue_invoices_default():
|
|
t = _make_tools()
|
|
result = await t.get_overdue_invoices()
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['move_type', 'in', ['out_invoice', 'out_refund']] in domain
|
|
assert ['state', '=', 'posted'] in domain
|
|
assert ['payment_state', 'in', ['not_paid', 'partial']] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_overdue_invoices_partner_filter():
|
|
t = _make_tools()
|
|
await t.get_overdue_invoices(partner_id=7)
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['partner_id', '=', 7] in domain
|
|
|
|
|
|
# ── get_unreconciled_statements ───────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_unreconciled_statements():
|
|
t = _make_tools()
|
|
await t.get_unreconciled_statements(journal_id=3)
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['journal_id', '=', 3] in domain
|
|
assert ['is_reconciled', '=', False] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_unreconciled_statements_date_range():
|
|
t = _make_tools()
|
|
await t.get_unreconciled_statements(journal_id=3, date_from='2026-01-01', date_to='2026-01-31')
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['date', '>=', '2026-01-01'] in domain
|
|
|
|
|
|
# ── send_payment_reminder ─────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_payment_reminder_success():
|
|
t = _make_tools()
|
|
t._odoo.read = AsyncMock(return_value=[{
|
|
'name': 'INV/001', 'partner_id': [1, 'ACME'],
|
|
'amount_residual': 500.0, 'invoice_date_due': '2026-01-01',
|
|
}])
|
|
result = await t.send_payment_reminder(invoice_id=1)
|
|
assert result['success'] is True
|
|
assert result['invoice'] == 'INV/001'
|
|
t._odoo.post_chatter.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_payment_reminder_invoice_not_found():
|
|
t = _make_tools()
|
|
t._odoo.read = AsyncMock(return_value=[])
|
|
result = await t.send_payment_reminder(invoice_id=999)
|
|
assert result['success'] is False
|
|
assert 'error' in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_send_payment_reminder_custom_message():
|
|
t = _make_tools()
|
|
t._odoo.read = AsyncMock(return_value=[{
|
|
'name': 'INV/001', 'partner_id': [1, 'ACME'],
|
|
'amount_residual': 500.0, 'invoice_date_due': '2026-01-01',
|
|
}])
|
|
await t.send_payment_reminder(invoice_id=1, custom_message='Please pay!')
|
|
call_args = t._odoo.post_chatter.call_args[0]
|
|
assert call_args[2] == 'Please pay!'
|
|
|
|
|
|
# ── get_financial_summary ─────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_financial_summary_current_period():
|
|
t = _make_tools()
|
|
t._odoo.search_read = AsyncMock(return_value=[
|
|
{'amount_total': 1000.0, 'amount_residual': 200.0, 'payment_state': 'partial'},
|
|
{'amount_total': 2000.0, 'amount_residual': 0.0, 'payment_state': 'paid'},
|
|
])
|
|
result = await t.get_financial_summary()
|
|
assert result['total_invoiced'] == 3000.0
|
|
assert result['invoice_count'] == 2
|
|
assert result['paid_count'] == 1
|
|
assert 'collection_rate' in result
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_financial_summary_explicit_period():
|
|
t = _make_tools()
|
|
await t.get_financial_summary(period='2026-01')
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['invoice_date', '>=', '2026-01-01'] in domain
|
|
assert ['invoice_date', '<=', '2026-01-31'] in domain
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_financial_summary_empty():
|
|
t = _make_tools()
|
|
result = await t.get_financial_summary()
|
|
assert result['total_invoiced'] == 0
|
|
assert result['collection_rate'] == 0
|
|
|
|
|
|
# ── get_payment_history ───────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_payment_history():
|
|
t = _make_tools()
|
|
t._odoo.search_read = AsyncMock(return_value=[
|
|
{'name': 'PAY/001', 'amount': 500.0, 'state': 'posted'}
|
|
])
|
|
result = await t.get_payment_history(partner_id=5)
|
|
domain = t._odoo.search_read.call_args[0][1]
|
|
assert ['partner_id', '=', 5] in domain
|
|
assert ['state', '=', 'posted'] in domain
|
|
assert len(result) == 1
|
|
|
|
|
|
# ── flag_for_review ───────────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_flag_for_review():
|
|
t = _make_tools()
|
|
result = await t.flag_for_review('account.move', 1, 'Overdue 90 days')
|
|
assert result['flagged'] is True
|
|
assert result['model'] == 'account.move'
|
|
assert result['record_id'] == 1
|
|
t._odoo.post_chatter.assert_awaited_once()
|
|
note = t._odoo.post_chatter.call_args[0][2]
|
|
assert 'MEDIUM' in note
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_flag_for_review_high_severity():
|
|
t = _make_tools()
|
|
result = await t.flag_for_review('account.move', 1, 'reason', severity='high')
|
|
assert result['severity'] == 'high'
|
|
note = t._odoo.post_chatter.call_args[0][2]
|
|
assert 'HIGH' in note
|
|
|
|
|
|
# ── post_chatter_note ─────────────────────────────────────────────────────────
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_post_chatter_note():
|
|
t = _make_tools()
|
|
result = await t.post_chatter_note('account.move', 1, 'Payment received')
|
|
assert result['success'] is True
|
|
assert result['message_id'] == 99
|
|
t._odoo.post_chatter.assert_awaited_once_with('account.move', 1, 'Payment received')
|