Files
ACORE/apps/visit_manager/tests_calendar.py
T
lokses b2f0c2a1e2 core/portal: сотрудник без группы видит только базовые разделы
Ранее разделы с perm=None (Новости, Задачи, Дислокация, Документы, Чат,
Календарь, HR-портал, Справка) были доступны любому вошедшему — сотрудник
без назначенной группы видел почти весь портал. Теперь такие разделы
требуют хотя бы одной группы; без групп доступны только базовые:
Главная, Профиль, Поддержка IT.

- user_can_access: BASELINE_OPEN_KEYS + gate «есть ли группа» для perm=None
- PortalSectionAccessMiddleware: барьер по префиксу URL (в т.ч. вложенные
  страницы вроде /tasks/5/), /settings/ не гейтим (редирект на свой профиль)
- index.html: пункты меню под can_section (сайдбар, дропдаун, bottom-nav),
  секция «Управление» скрывается целиком, если пусто
- тесты: политика без-группы + прямой заход по URL; тест-фабрики выдают
  группу (реальный сотрудник её имеет)

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

249 lines
9.5 KiB
Python

"""Тесты портального календаря."""
from datetime import date, time, timedelta
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from apps.employees.models import EmployeeHrRequest
from apps.visit_manager.calendar_service import build_calendar_context
from apps.visit_manager.models import (
CalendarEvent, Holiday, SchedulePattern, ScheduleTemplate, Shift, WorkSchedule,
)
from core.models.portal_access import PortalAccess
from core.test_utils import login_2fa
from core.tests_portal_access import make_employee
def _hr_perm():
ct = ContentType.objects.get_for_model(PortalAccess, for_concrete_model=False)
return Permission.objects.get(content_type=ct, codename='access_hr_portal')
def _events_on(ctx, day: date, kind: str | None = None):
for week in ctx['cal_weeks']:
for cell in week:
if cell and cell['date'] == day:
events = cell['events']
if kind is None:
return events
return [e for e in events if e['kind'] == kind]
return []
class CalendarServiceTests(TestCase):
def setUp(self):
self.user = make_employee(
username='cal.me',
first_name='Иван',
last_name='Петров',
birth_date=date(1990, 9, 15),
)
self.colleague = make_employee(
username='cal.other',
first_name='Оля',
last_name='Сидорова',
birth_date=date(1992, 9, 20),
)
self.hr = make_employee(
username='cal.hr',
first_name='HR',
last_name='Менеджер',
is_staff=True,
totp_enabled=True,
)
self.hr.user_permissions.add(_hr_perm())
def test_month_grid_builds(self):
ctx = build_calendar_context(self.user, month_str='2026-09', scope='mine')
self.assertEqual(ctx['cal_month'], 9)
self.assertEqual(ctx['cal_year'], 2026)
self.assertTrue(ctx['cal_weeks'])
days = [
cell['day']
for week in ctx['cal_weeks']
for cell in week
if cell
]
self.assertEqual(days, list(range(1, 31)))
def test_holiday_appears(self):
Holiday.objects.create(
name='Тест-свято', date=date(2026, 9, 10), is_working_day=False,
)
ctx = build_calendar_context(self.user, month_str='2026-09', scope='mine')
holidays = _events_on(ctx, date(2026, 9, 10), 'holiday')
self.assertTrue(any(e['label'] == 'Тест-свято' for e in holidays))
def test_own_leave_visible_in_mine(self):
EmployeeHrRequest.objects.create(
employee=self.user,
request_type='vacation',
date_from=date(2026, 9, 8),
date_to=date(2026, 9, 10),
status='approved',
)
EmployeeHrRequest.objects.create(
employee=self.colleague,
request_type='vacation',
date_from=date(2026, 9, 8),
date_to=date(2026, 9, 10),
status='approved',
)
ctx = build_calendar_context(self.user, month_str='2026-09', scope='mine')
leaves = _events_on(ctx, date(2026, 9, 9), 'leave')
self.assertEqual(len(leaves), 1)
self.assertIn('Отпуск', leaves[0]['label'])
def test_colleague_leave_visible_in_team_for_hr(self):
EmployeeHrRequest.objects.create(
employee=self.colleague,
request_type='vacation',
date_from=date(2026, 9, 12),
date_to=date(2026, 9, 12),
status='approved',
)
mine = build_calendar_context(self.user, month_str='2026-09', scope='mine')
self.assertEqual(_events_on(mine, date(2026, 9, 12), 'leave'), [])
team_hr = build_calendar_context(self.hr, month_str='2026-09', scope='team')
leaves = _events_on(team_hr, date(2026, 9, 12), 'leave')
self.assertTrue(leaves)
def test_birthday_in_month(self):
ctx = build_calendar_context(self.user, month_str='2026-09', scope='mine')
bdays = _events_on(ctx, date(2026, 9, 15), 'birthday')
self.assertEqual(len(bdays), 1)
team = build_calendar_context(self.hr, month_str='2026-09', scope='team')
team_bdays = _events_on(team, date(2026, 9, 20), 'birthday')
self.assertTrue(team_bdays)
def test_shift_chip_for_work_schedule(self):
from core.models import Directory, DirectoryType
from apps.locations.models import Pharmacy
org_type, _ = DirectoryType.objects.get_or_create(
code='organization', defaults={'name': 'Организация'},
)
region_type, _ = DirectoryType.objects.get_or_create(
code='region', defaults={'name': 'Регион'},
)
region, _ = Directory.objects.get_or_create(
directory_type=region_type, code='cal', defaults={'name': 'Кал'},
)
org, _ = Directory.objects.get_or_create(
directory_type=org_type, code='calorg', defaults={'name': 'КалОрг'},
)
pharmacy = Pharmacy.objects.create(
organization=org, location_region=region,
pharmacy_number=501, location_type='pharmacy',
street='Test', house_number='1',
)
self.user.user_pharmacy = pharmacy
self.user.user_location_type = 'pharmacy'
self.user.save(update_fields=['user_pharmacy', 'user_location_type'])
day_shift = Shift.objects.create(
name='Денна', shift_type='day',
start_time=time(9, 0), end_time=time(18, 0),
is_day_off=False,
)
template = ScheduleTemplate.objects.create(
name='5/2', template_type='fixed',
)
for weekday in range(5):
SchedulePattern.objects.create(
schedule_template=template, weekday=weekday, shift=day_shift,
)
WorkSchedule.objects.create(
employee=self.user,
schedule_template=template,
location=pharmacy,
start_date=date(2026, 9, 1),
is_active=True,
is_primary=True,
)
# 2026-09-07 is Monday
ctx = build_calendar_context(self.user, month_str='2026-09', scope='mine')
shifts = _events_on(ctx, date(2026, 9, 7), 'shift')
self.assertTrue(shifts)
self.assertIn('Денна', shifts[0]['label'])
class CalendarEventViewsTests(TestCase):
def setUp(self):
self.user = make_employee(username='cal.author', totp_enabled=False)
self.stranger = make_employee(username='cal.stranger', totp_enabled=False)
self.staff = make_employee(
username='cal.staff', is_staff=True, totp_enabled=True,
)
# Любая группа = доступ к открытым разделам (в т.ч. /calendar/).
grp = Group.objects.get_or_create(name='Сотрудники (тест)')[0]
for u in (self.user, self.stranger, self.staff):
u.groups.add(grp)
def test_create_event(self):
login_2fa(self.client, self.user)
resp = self.client.post(
'/calendar/events/',
{
'title': 'Співбесіда Аня',
'event_type': 'interview',
'visibility': 'company',
'date_from': '2026-09-14',
'date_to': '2026-09-14',
'time_from': '10:00',
'month': '2026-09',
'scope': 'mine',
},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertTrue(CalendarEvent.objects.filter(title='Співбесіда Аня').exists())
self.assertContains(resp, 'Событие создано')
self.assertContains(resp, 'Співбесіда Аня')
def test_delete_forbidden_for_stranger(self):
event = CalendarEvent.objects.create(
title='Private',
event_type='other',
date_from=date(2026, 9, 5),
date_to=date(2026, 9, 5),
visibility='company',
created_by=self.user,
)
login_2fa(self.client, self.stranger)
resp = self.client.post(
f'/calendar/events/{event.pk}/delete/',
{'month': '2026-09', 'scope': 'mine'},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 403)
self.assertTrue(CalendarEvent.objects.filter(pk=event.pk).exists())
def test_delete_by_author(self):
event = CalendarEvent.objects.create(
title='To delete',
event_type='meeting',
date_from=date(2026, 9, 5),
date_to=date(2026, 9, 5),
visibility='personal',
created_by=self.user,
)
login_2fa(self.client, self.user)
resp = self.client.post(
f'/calendar/events/{event.pk}/delete/',
{'month': '2026-09', 'scope': 'mine'},
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertFalse(CalendarEvent.objects.filter(pk=event.pk).exists())
def test_calendar_page_renders(self):
login_2fa(self.client, self.user)
resp = self.client.get('/calendar/?month=2026-09&scope=mine', HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'Вересень 2026')
self.assertContains(resp, 'Новое событие')