Add comprehensive unit tests for all agent service components
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
202
tests/test_employees_tools.py
Normal file
202
tests/test_employees_tools.py
Normal file
@@ -0,0 +1,202 @@
|
||||
"""Unit tests for EmployeesTools."""
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from agent_service.tools.employees_tools import EmployeesTools
|
||||
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 EmployeesTools(odoo)
|
||||
|
||||
|
||||
# ── get_employees ────────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employees_default():
|
||||
t = _make_tools()
|
||||
result = await t.get_employees()
|
||||
t._o.search_read.assert_awaited_once()
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('active', '=', True) in domain
|
||||
assert isinstance(result, list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employees_with_department():
|
||||
t = _make_tools()
|
||||
await t.get_employees(department_id=5)
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('department_id', '=', 5) in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employees_inactive():
|
||||
t = _make_tools()
|
||||
await t.get_employees(active=False)
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('active', '=', False) in domain
|
||||
|
||||
|
||||
# ── get_employee_profile ──────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employee_profile_found():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(return_value=[
|
||||
{'id': 1, 'name': 'Alice', 'job_title': 'Dev', 'work_email': 'alice@co.com'}
|
||||
])
|
||||
result = await t.get_employee_profile(employee_id=1)
|
||||
assert result['name'] == 'Alice'
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('id', '=', 1) in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_employee_profile_not_found():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(return_value=[])
|
||||
result = await t.get_employee_profile(employee_id=999)
|
||||
assert result == {}
|
||||
|
||||
|
||||
# ── get_leaves ────────────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_leaves_default():
|
||||
t = _make_tools()
|
||||
result = await t.get_leaves()
|
||||
t._o.search_read.assert_awaited_once()
|
||||
assert isinstance(result, list)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_leaves_with_employee():
|
||||
t = _make_tools()
|
||||
await t.get_leaves(employee_id=3)
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('employee_id', '=', 3) in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_leaves_with_state():
|
||||
t = _make_tools()
|
||||
await t.get_leaves(state='validate')
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('state', '=', 'validate') in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_leaves_with_date_from():
|
||||
t = _make_tools()
|
||||
await t.get_leaves(date_from='2026-01-01')
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('date_from', '>=', '2026-01-01') in domain
|
||||
|
||||
|
||||
# ── get_contracts ─────────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_contracts_default_state_open():
|
||||
t = _make_tools()
|
||||
await t.get_contracts()
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('state', '=', 'open') in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_contracts_with_employee():
|
||||
t = _make_tools()
|
||||
await t.get_contracts(employee_id=7)
|
||||
domain = t._o.search_read.call_args[0][1]
|
||||
assert ('employee_id', '=', 7) in domain
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_contracts_returns_list():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(return_value=[
|
||||
{'id': 1, 'name': 'Contract A', 'wage': 50000.0}
|
||||
])
|
||||
result = await t.get_contracts()
|
||||
assert len(result) == 1
|
||||
|
||||
|
||||
# ── get_attendance_summary ────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_attendance_summary_totals():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(return_value=[
|
||||
{'worked_hours': 8.0, 'check_in': '2026-01-05 09:00:00'},
|
||||
{'worked_hours': 7.5, 'check_in': '2026-01-06 09:00:00'},
|
||||
])
|
||||
result = await t.get_attendance_summary(employee_id=1, date_from='2026-01-01', date_to='2026-01-31')
|
||||
assert result['total_hours'] == 15.5
|
||||
assert result['days_present'] == 2
|
||||
assert result['attendance_records'] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_attendance_summary_structure():
|
||||
t = _make_tools()
|
||||
result = await t.get_attendance_summary(employee_id=1, date_from='2026-01-01', date_to='2026-01-31')
|
||||
assert 'total_hours' in result
|
||||
assert 'days_present' in result
|
||||
assert result['employee_id'] == 1
|
||||
|
||||
|
||||
# ── get_department_summary ────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_department_summary_headcount():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(side_effect=[
|
||||
[{'id': 1, 'name': 'Alice'}, {'id': 2, 'name': 'Bob'}], # employees
|
||||
[{'id': 10, 'employee_id': [1, 'Alice'], 'wage': 60000.0},
|
||||
{'id': 11, 'employee_id': [2, 'Bob'], 'wage': 55000.0}], # contracts
|
||||
])
|
||||
result = await t.get_department_summary(department_id=3)
|
||||
assert result['headcount'] == 2
|
||||
assert result['department_id'] == 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_department_summary_avg_wage():
|
||||
t = _make_tools()
|
||||
t._o.search_read = AsyncMock(side_effect=[
|
||||
[{'id': 1, 'name': 'Alice'}],
|
||||
[{'id': 10, 'employee_id': [1, 'Alice'], 'wage': 60000.0}],
|
||||
])
|
||||
result = await t.get_department_summary(department_id=3)
|
||||
assert result['avg_wage'] == 60000.0
|
||||
|
||||
|
||||
# ── flag_for_review ───────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_flag_for_review():
|
||||
t = _make_tools()
|
||||
result = await t.flag_for_review('hr.employee', 1, 'Contract expired', severity='high')
|
||||
assert result is True
|
||||
call_args = t._o.call.call_args[0]
|
||||
assert call_args[1] == 'message_post'
|
||||
assert '[AI FLAG - HIGH]' in call_args[3]['body']
|
||||
|
||||
|
||||
# ── post_chatter_note ─────────────────────────────────────────────────────────
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_chatter_note():
|
||||
t = _make_tools()
|
||||
result = await t.post_chatter_note('hr.employee', 1, 'Note text')
|
||||
assert result is True
|
||||
t._o.call.assert_awaited_once()
|
||||
call_args = t._o.call.call_args[0]
|
||||
assert call_args[3]['body'] == 'Note text'
|
||||
Reference in New Issue
Block a user