Files
ACORE/apps/employees/tests_hr_requests.py
T
lokses 3359a8db13 employees: HR-заявки, приглашения на пароль, профиль и 2FA
Модель/сервисы HR-заявок с заменами, приглашения на установку пароля
(инвайты), обновлённая страница профиля сотрудника и правки 2FA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 18:03:32 +03:00

293 lines
10 KiB
Python

import datetime
from django.contrib.auth.models import Group
from django.core.exceptions import ValidationError
from django.test import Client, TestCase
from django.urls import reverse
from django.utils import timezone
from apps.employees.models import Employee, EmployeeHrRequest
from apps.employees.services.hr_requests import (
build_leave_balance,
cancel_hr_request,
check_leave_balance,
create_hr_request,
decide_hr_request,
next_pending_vacation,
pending_hr_requests_count,
)
from core.test_utils import login_2fa
def make_employee(username, **kw):
kw.setdefault('first_name', 'Иван')
kw.setdefault('last_name', 'Иванов')
kw.setdefault('hire_date', datetime.date(2020, 1, 1))
kw.setdefault('mattermost_sync_enabled', False)
kw.setdefault('ad_sync_enabled', False)
return Employee.objects.create_user(username=username, **kw)
class HrRequestServiceTests(TestCase):
def test_create_vacation_requires_dates(self):
employee = make_employee('emp1')
with self.assertRaises(ValidationError):
create_hr_request(
employee=employee,
request_type='vacation',
date_from=None,
date_to=None,
)
def test_create_certificate_without_dates(self):
employee = make_employee('emp2')
req = create_hr_request(
employee=employee,
request_type='certificate',
date_from=None,
date_to=None,
note='2-НДФЛ',
)
self.assertEqual(req.status, 'pending')
self.assertEqual(req.day_count, 0)
def test_cancel_own_pending(self):
employee = make_employee('emp3')
req = create_hr_request(
employee=employee,
request_type='day_off',
date_from=datetime.date(2026, 6, 1),
date_to=datetime.date(2026, 6, 1),
)
cancelled = cancel_hr_request(req, actor=employee)
self.assertEqual(cancelled.status, 'cancelled')
def test_cannot_cancel_other_user_request(self):
employee = make_employee('emp4')
other = make_employee('emp5')
req = create_hr_request(
employee=employee,
request_type='day_off',
date_from=datetime.date(2026, 6, 2),
date_to=datetime.date(2026, 6, 2),
)
with self.assertRaises(ValidationError):
cancel_hr_request(req, actor=other)
def test_decide_approve_and_reject(self):
employee = make_employee('emp6')
hr = make_employee('hr1')
hr.groups.add(Group.objects.get(name='HR'))
req = create_hr_request(
employee=employee,
request_type='vacation',
date_from=datetime.date(2026, 7, 1),
date_to=datetime.date(2026, 7, 5),
)
approved = decide_hr_request(req, decided_by=hr, approve=True)
self.assertEqual(approved.status, 'approved')
self.assertEqual(approved.decided_by_id, hr.pk)
req2 = create_hr_request(
employee=employee,
request_type='sick_leave',
date_from=datetime.date(2026, 8, 1),
date_to=datetime.date(2026, 8, 3),
)
rejected = decide_hr_request(req2, decided_by=hr, approve=False, decision_note='Нет')
self.assertEqual(rejected.status, 'rejected')
self.assertEqual(rejected.decision_note, 'Нет')
def test_leave_balance_counts_approved_days(self):
employee = make_employee('emp7')
year = timezone.localdate().year
create_hr_request(
employee=employee,
request_type='vacation',
date_from=datetime.date(year, 3, 1),
date_to=datetime.date(year, 3, 5),
)
hr = make_employee('hr2')
hr.groups.add(Group.objects.get(name='HR'))
pending = EmployeeHrRequest.objects.filter(employee=employee, status='pending').first()
decide_hr_request(pending, decided_by=hr, approve=True)
balance = build_leave_balance(employee, year=year)
annual = next(row for row in balance if row.key == 'annual')
self.assertEqual(annual.used, 5)
self.assertEqual(annual.remaining, 19)
def test_next_pending_vacation_future_only(self):
employee = make_employee('emp8')
today = timezone.localdate()
create_hr_request(
employee=employee,
request_type='vacation',
date_from=today - datetime.timedelta(days=5),
date_to=today - datetime.timedelta(days=1),
)
future = create_hr_request(
employee=employee,
request_type='vacation',
date_from=today + datetime.timedelta(days=10),
date_to=today + datetime.timedelta(days=15),
)
self.assertEqual(next_pending_vacation(employee).pk, future.pk)
def test_pending_count(self):
employee = make_employee('emp9')
self.assertEqual(pending_hr_requests_count(), 0)
create_hr_request(
employee=employee,
request_type='certificate',
date_from=None,
date_to=None,
)
self.assertEqual(pending_hr_requests_count(), 1)
def test_create_rejects_exceeding_annual_balance(self):
employee = make_employee('emp10')
year = timezone.localdate().year
create_hr_request(
employee=employee,
request_type='vacation',
date_from=datetime.date(year, 1, 1),
date_to=datetime.date(year, 1, 24),
)
with self.assertRaises(ValidationError):
create_hr_request(
employee=employee,
request_type='vacation',
date_from=datetime.date(year, 2, 1),
date_to=datetime.date(year, 2, 5),
)
def test_check_balance_allows_within_limit(self):
employee = make_employee('emp11')
year = timezone.localdate().year
check_leave_balance(
employee, 'vacation',
datetime.date(year, 6, 1), datetime.date(year, 6, 5),
)
def test_approve_rejects_when_balance_exhausted(self):
employee = make_employee('emp12')
hr = make_employee('hr3')
hr.groups.add(Group.objects.get(name='HR'))
year = timezone.localdate().year
from apps.employees.services.hr_requests import get_entitlement
ent = get_entitlement(employee, year)
ent.annual_days = 5
ent.save(update_fields=['annual_days'])
req = create_hr_request(
employee=employee,
request_type='vacation',
date_from=datetime.date(year, 1, 1),
date_to=datetime.date(year, 1, 5),
)
EmployeeHrRequest.objects.create(
employee=employee,
request_type='vacation',
date_from=datetime.date(year, 6, 1),
date_to=datetime.date(year, 6, 3),
status='approved',
)
with self.assertRaises(ValidationError):
decide_hr_request(req, decided_by=hr, approve=True)
class HrRequestViewTests(TestCase):
def setUp(self):
self.client = Client()
self.employee = make_employee('view_emp')
self.hr = make_employee('view_hr')
self.hr.groups.add(Group.objects.get(name='HR'))
def test_create_request_via_post(self):
login_2fa(self.client, self.employee)
year = timezone.localdate().year
resp = self.client.post(
reverse('hr_request_create'),
{
'request_type': 'vacation',
'date_from': f'{year}-09-01',
'date_to': f'{year}-09-05',
'note': 'Отпуск',
},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertTrue(
EmployeeHrRequest.objects.filter(
employee=self.employee, request_type='vacation', status='pending',
).exists()
)
def test_cancel_own_request(self):
req = create_hr_request(
employee=self.employee,
request_type='day_off',
date_from=datetime.date(2026, 10, 1),
date_to=datetime.date(2026, 10, 1),
)
login_2fa(self.client, self.employee)
resp = self.client.post(reverse('hr_request_cancel', args=[req.pk]), HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
req.refresh_from_db()
self.assertEqual(req.status, 'cancelled')
def test_hr_approve_pending(self):
req = create_hr_request(
employee=self.employee,
request_type='vacation',
date_from=datetime.date(2026, 11, 1),
date_to=datetime.date(2026, 11, 3),
)
login_2fa(self.client, self.hr)
resp = self.client.post(reverse('hr_request_approve', args=[req.pk]), HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
req.refresh_from_db()
self.assertEqual(req.status, 'approved')
def test_create_certificate_via_post_on_profile(self):
login_2fa(self.client, self.employee)
resp = self.client.post(
reverse('hr_request_create'),
{
'request_type': 'certificate',
'note': '2-НДФЛ',
'return_to': 'profile',
},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'Заявку подано')
self.assertContains(resp, 'emp-profile-hr-requests')
self.assertContains(resp, 'data-profile-active-tab="overview"')
def test_validation_error_shown_on_page(self):
login_2fa(self.client, self.employee)
resp = self.client.post(
reverse('hr_request_create'),
{'request_type': 'vacation'},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'Не удалось подать заявку')
self.assertContains(resp, 'data-hr-request-form-open="1"')
def test_non_hr_cannot_approve(self):
req = create_hr_request(
employee=self.employee,
request_type='vacation',
date_from=datetime.date(2026, 12, 1),
date_to=datetime.date(2026, 12, 2),
)
login_2fa(self.client, self.employee)
resp = self.client.post(reverse('hr_request_approve', args=[req.pk]), HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 403)
req.refresh_from_db()
self.assertEqual(req.status, 'pending')