from datetime import date 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 Employee, GroupSectionAccess from core.models import PortalAccess from core.portal_access import user_can_access, user_can_section_action from core.test_utils import login_2fa from core.user_groups_portal import apply_group_access_matrix, copy_group_access def make_employee(**overrides): n = Employee.objects.count() + 1 defaults = { 'username': f'ug.user{n}', 'email': f'ug.user{n}@company.local', 'first_name': 'Тест', 'last_name': 'Группы', 'hire_date': date(2026, 1, 1), 'user_location_type': 'office', 'mattermost_sync_enabled': False, 'ad_sync_enabled': False, 'is_staff': False, } defaults.update(overrides) return Employee.objects.create(**defaults) def portal_perm(codename: str) -> Permission: ct = ContentType.objects.get_for_model(PortalAccess, for_concrete_model=False) return Permission.objects.get(content_type=ct, codename=codename) def clear_perm_cache(user): for attr in ('_perm_cache', '_user_perm_cache', '_group_perm_cache'): if hasattr(user, attr): delattr(user, attr) class UserGroupsMatrixTests(TestCase): def test_staff_only_section(self): user = make_employee() self.assertFalse(user_can_access(user, 'user_groups')) staff = make_employee(is_staff=True) self.assertTrue(user_can_access(staff, 'user_groups')) def test_non_staff_forbidden_on_list(self): user = make_employee() login_2fa(self.client, user) resp = self.client.get('/user-groups/', HTTP_HX_REQUEST='true') self.assertEqual(resp.status_code, 302) self.assertEqual(resp['Location'], '/dashboard/') self.assertEqual(resp['HX-Redirect'], '/dashboard/') def test_staff_can_open_list(self): staff = make_employee(is_staff=True) login_2fa(self.client, staff) resp = self.client.get('/user-groups/', HTTP_HX_REQUEST='true') self.assertEqual(resp.status_code, 200) def test_apply_matrix_syncs_view_and_cud(self): group = Group.objects.create(name='Matrix Test Group') apply_group_access_matrix(group, { 'view_databases': '1', 'create_databases': '1', 'update_databases': '1', # delete off 'view_reports': '0', }) self.assertTrue(group.permissions.filter(codename='access_databases').exists()) self.assertFalse(group.permissions.filter(codename='access_reports').exists()) row = GroupSectionAccess.objects.get(group=group, section_key='databases') self.assertTrue(row.can_create) self.assertTrue(row.can_update) self.assertFalse(row.can_delete) apply_group_access_matrix(group, { 'view_databases': '1', 'view_all_reports': '1', }) self.assertTrue(group.permissions.filter(codename='view_all_departments').exists()) apply_group_access_matrix(group, {'view_databases': '1', 'view_all_reports': '0'}) self.assertFalse(group.permissions.filter(codename='view_all_departments').exists()) apply_group_access_matrix(group, {'view_databases': '0'}) self.assertFalse(group.permissions.filter(codename='access_databases').exists()) self.assertFalse( GroupSectionAccess.objects.filter(group=group, section_key='databases').exists() ) def test_user_can_section_action(self): group = Group.objects.create(name='CUD Group') group.permissions.add(portal_perm('access_databases')) GroupSectionAccess.objects.create( group=group, section_key='databases', can_create=True, can_update=False, can_delete=False, ) user = make_employee() user.groups.add(group) clear_perm_cache(user) self.assertTrue(user_can_access(user, 'databases')) self.assertTrue(user_can_section_action(user, 'databases', 'create')) self.assertFalse(user_can_section_action(user, 'databases', 'update')) self.assertFalse(user_can_section_action(user, 'databases', 'delete')) staff = make_employee(is_staff=True) staff.groups.add(group) clear_perm_cache(staff) # staff не обходит CUD self.assertFalse(user_can_section_action(staff, 'databases', 'update')) su = make_employee(is_superuser=True, is_staff=True) self.assertTrue(user_can_section_action(su, 'databases', 'update')) def test_copy_access(self): src = Group.objects.create(name='Copy Src') dst = Group.objects.create(name='Copy Dst') apply_group_access_matrix(src, { 'view_email': '1', 'create_email': '1', 'update_email': '0', 'delete_email': '0', 'view_all_reports': '1', }) copy_group_access(src, dst) self.assertTrue(dst.permissions.filter(codename='access_email').exists()) self.assertTrue(dst.permissions.filter(codename='view_all_departments').exists()) row = GroupSectionAccess.objects.get(group=dst, section_key='email') self.assertTrue(row.can_create) self.assertFalse(row.can_update) self.assertFalse(row.can_delete) def test_copy_endpoint(self): src = Group.objects.create(name='Copy Endpoint Src') dst = Group.objects.create(name='Copy Endpoint Dst') apply_group_access_matrix(src, { 'view_structure': '1', 'create_structure': '1', 'update_structure': '1', 'delete_structure': '1', }) staff = make_employee(is_staff=True) login_2fa(self.client, staff) resp = self.client.post( f'/user-groups/{src.pk}/access/copy/', {'target_group_id': str(dst.pk)}, ) self.assertEqual(resp.status_code, 302) self.assertTrue(dst.permissions.filter(codename='access_structure').exists()) def test_individual_view_all_reports_grant_and_revoke(self): from apps.reports.it_monthly.builder import user_can_view_all_department_reports staff = make_employee(is_staff=True, last_name='Админ', first_name='Портал', username='ug.staff') person = make_employee(last_name='Иванов', first_name='Пётр', username='ivanov.rep') self.assertFalse(user_can_view_all_department_reports(person)) login_2fa(self.client, staff) page = self.client.get('/user-groups/') self.assertContains(page, 'Отчёты всех отделов — отдельным сотрудникам') resp = self.client.post( '/user-groups/individual-reports/', {'employee_id': str(person.pk)}, ) self.assertEqual(resp.status_code, 302) person = Employee.objects.get(pk=person.pk) self.assertTrue(user_can_view_all_department_reports(person)) listed = self.client.get('/user-groups/') self.assertContains(listed, 'ivanov.rep') revoke = self.client.post(f'/user-groups/individual-reports/{person.pk}/revoke/') self.assertEqual(revoke.status_code, 302) person = Employee.objects.get(pk=person.pk) self.assertFalse(user_can_view_all_department_reports(person)) class GroupAccessMatrixAdminTests(TestCase): """Полная сетка доступа в админке (/admin/auth/group/access-matrix/).""" def setUp(self): from django.urls import reverse self.admin = make_employee(username='ug.admin', is_staff=True, is_superuser=True) login_2fa(self.client, self.admin) self.group = Group.objects.create(name='Тест-роль') # Префикс админки берём из reverse — не зависим от того, как назван путь. self.matrix_url = reverse('admin:index') + 'auth/group/access-matrix/' def test_matrix_renders(self): resp = self.client.get(self.matrix_url) self.assertEqual(resp.status_code, 200) self.assertContains(resp, 'Тест-роль') def test_matrix_save_grants_view(self): # включаем раздел 'reports' для группы через сетку self.client.post(self.matrix_url, { f'g{self.group.id}_reports': 'on', }) self.assertTrue(user_group_has_reports_access(self.group)) def user_group_has_reports_access(group): from core.user_groups_portal import _group_view_keys return 'reports' in _group_view_keys(group)