From b9e24dd841535b11a40ec1deb980a5fb45a4d99c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Mon, 8 Dec 2025 13:54:28 +0300 Subject: [PATCH] test: add comprehensive position permissions tests Created pytest test suite to verify permission inheritance through positions: Test Coverage: 1. User without position has no permissions (PASS) 2. User with position gets all group permissions (needs fix) 3. Position change updates permissions dynamically 4. Remove position removes all permissions 5. Multiple users with same position get same permissions 6. Staff users keep all permissions regardless 7. Multiple groups' permissions are additive 8. get_all_permissions() includes position perms 9. Position without groups grants no permissions (PASS) 10. Inactive users have no permissions 11. Direct user permissions + position permissions combine 12. Feed/Documents integration tests Current Results: - 3 tests PASSED (basic cases) - 10 tests need fixing (fixture scope issues) Purpose: These tests will validate that PositionRoleBackend correctly grants permissions from Position.groups to users, which is needed for features like: - Show 'Create Post' button for users with publishing rights - Show 'Create Document' button for users with document rights - Consistent permission checks across frontend and backend Next Steps: 1. Fix IntegrityError in permission creation (use filter first) 2. Make fixtures work across test classes 3. Run tests to verify PositionRoleBackend implementation 4. If tests fail, fix the backend implementation --- .../employees/test_position_permissions.py | 430 ++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 backend/tests/employees/test_position_permissions.py diff --git a/backend/tests/employees/test_position_permissions.py b/backend/tests/employees/test_position_permissions.py new file mode 100644 index 00000000..bc54fb5d --- /dev/null +++ b/backend/tests/employees/test_position_permissions.py @@ -0,0 +1,430 @@ +""" +Тесты прав доступа через должности (Position + Groups). + +Проверяют, что: +1. Пользователь с должностью получает права из групп должности +2. PositionRoleBackend корректно добавляет права через get_all_permissions +3. has_perm работает для прав из должностей +4. Права наследуются при назначении/изменении должности +""" +import pytest +from django.contrib.auth import get_user_model +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType + +from employees.models import Position + +User = get_user_model() + +pytestmark = pytest.mark.django_db + + +class TestPositionPermissions: + """Тесты прав доступа через должности.""" + + @pytest.fixture + def feed_content_type(self): + """ContentType для приложения feed (для прав публикаций).""" + ct, _ = ContentType.objects.get_or_create( + app_label="feed", model="post" + ) + return ct + + @pytest.fixture + def documents_content_type(self): + """ContentType для приложения documents.""" + ct, _ = ContentType.objects.get_or_create( + app_label="documents", model="document" + ) + return ct + + @pytest.fixture + def group_with_publish_perm(self, feed_content_type): + """Группа с правом публикации в feed.""" + group = Group.objects.create(name="publishers") + + # Ищем существующее право или создаём новое + perm = Permission.objects.filter( + codename="publish_company_post", + content_type=feed_content_type, + ).first() + + if not perm: + perm = Permission.objects.create( + codename="publish_company_post", + name="Can publish company post", + content_type=feed_content_type, + ) + + group.permissions.add(perm) + return group + + @pytest.fixture + def group_with_document_perm(self, documents_content_type): + """Группа с правом добавления документов.""" + group = Group.objects.create(name="doc_creators") + + # Ищем существующее право или используем defaults + perm = Permission.objects.filter( + codename="add_document", + content_type=documents_content_type, + ).first() + + if not perm: + perm = Permission.objects.create( + codename="add_document", + name="Can add document", + content_type=documents_content_type, + ) + + group.permissions.add(perm) + return group + + @pytest.fixture + def engineer_position( + self, group_with_publish_perm, group_with_document_perm + ): + """Должность 'Инженер' с двумя группами прав.""" + position = Position.objects.create( + name="Инженер", + description="Технический специалист", + ) + position.groups.add( + group_with_publish_perm, group_with_document_perm + ) + return position + + @pytest.fixture + def manager_position(self, group_with_publish_perm): + """Должность 'Менеджер' только с правом публикации.""" + position = Position.objects.create( + name="Менеджер", + description="Управленец", + ) + position.groups.add(group_with_publish_perm) + return position + + @pytest.fixture + def basic_user(self, user_factory): + """Обычный пользователь без должности и прав.""" + return user_factory( + email="user@example.com", + staff=False, + superuser=False, + verified=True, + active=True, + ) + + def test_user_without_position_has_no_permissions(self, basic_user): + """Пользователь без должности не имеет прав.""" + assert not basic_user.has_perm("feed.publish_company_post") + assert not basic_user.has_perm("documents.add_document") + + # Проверяем через get_all_permissions + all_perms = basic_user.get_all_permissions() + assert "feed.publish_company_post" not in all_perms + assert "documents.add_document" not in all_perms + + def test_user_with_position_gets_permissions( + self, basic_user, engineer_position + ): + """Пользователь с должностью получает права из групп должности.""" + # Назначаем должность + basic_user.position = engineer_position + basic_user.save() + + # Обновляем пользователя из БД (чтобы подтянуть связи) + basic_user.refresh_from_db() + + # Проверяем has_perm + assert basic_user.has_perm("feed.publish_company_post"), \ + "Должен быть доступ к публикации (из группы publishers)" + assert basic_user.has_perm("documents.add_document"), \ + "Должен быть доступ к созданию документов (из группы doc_creators)" + + # Проверяем через get_all_permissions + all_perms = basic_user.get_all_permissions() + assert "feed.publish_company_post" in all_perms, \ + f"Право должно быть в all_perms. Текущие права: {all_perms}" + assert "documents.add_document" in all_perms, \ + f"Право должно быть в all_perms. Текущие права: {all_perms}" + + def test_position_change_updates_permissions( + self, basic_user, engineer_position, manager_position + ): + """При смене должности права обновляются.""" + # Назначаем инженера (2 права) + basic_user.position = engineer_position + basic_user.save() + basic_user.refresh_from_db() + + assert basic_user.has_perm("feed.publish_company_post") + assert basic_user.has_perm("documents.add_document") + + # Меняем на менеджера (только 1 право) + basic_user.position = manager_position + basic_user.save() + basic_user.refresh_from_db() + + assert basic_user.has_perm("feed.publish_company_post"), \ + "Право на публикацию должно остаться" + assert not basic_user.has_perm("documents.add_document"), \ + "Право на документы должно исчезнуть" + + def test_remove_position_removes_permissions( + self, basic_user, engineer_position + ): + """При снятии должности права удаляются.""" + # Назначаем должность + basic_user.position = engineer_position + basic_user.save() + basic_user.refresh_from_db() + + assert basic_user.has_perm("feed.publish_company_post") + assert basic_user.has_perm("documents.add_document") + + # Снимаем должность + basic_user.position = None + basic_user.save() + basic_user.refresh_from_db() + + assert not basic_user.has_perm("feed.publish_company_post"), \ + "Права должны исчезнуть после снятия должности" + assert not basic_user.has_perm("documents.add_document"), \ + "Права должны исчезнуть после снятия должности" + + def test_multiple_users_with_same_position( + self, user_factory, engineer_position + ): + """Несколько пользователей с одной должностью получают + одинаковые права. + """ + user1 = user_factory(email="user1@example.com") + user2 = user_factory(email="user2@example.com") + + user1.position = engineer_position + user2.position = engineer_position + user1.save() + user2.save() + + user1.refresh_from_db() + user2.refresh_from_db() + + # Оба должны иметь одинаковые права + for user in [user1, user2]: + assert user.has_perm("feed.publish_company_post") + assert user.has_perm("documents.add_document") + + def test_staff_user_keeps_all_permissions( + self, user_factory, engineer_position + ): + """is_staff пользователь имеет все права независимо от должности.""" + staff_user = user_factory(email="staff@example.com", staff=True) + + # Без должности staff имеет все права + assert staff_user.has_perm("feed.publish_company_post") + assert staff_user.has_perm("documents.add_document") + assert staff_user.has_perm("any.random_permission") + + # С должностью тоже все права + staff_user.position = engineer_position + staff_user.save() + staff_user.refresh_from_db() + + assert staff_user.has_perm("feed.publish_company_post") + assert staff_user.has_perm("documents.add_document") + assert staff_user.has_perm("any.random_permission") + + def test_position_group_permissions_are_additive( + self, basic_user, feed_content_type + ): + """Если у должности несколько групп, права суммируются.""" + # Создаём две группы с разными правами + group1 = Group.objects.create(name="group1") + perm1 = Permission.objects.filter( + codename="perm1", + content_type=feed_content_type, + ).first() + if not perm1: + perm1 = Permission.objects.create( + codename="perm1", + name="Permission 1", + content_type=feed_content_type, + ) + group1.permissions.add(perm1) + + group2 = Group.objects.create(name="group2") + perm2 = Permission.objects.filter( + codename="perm2", + content_type=feed_content_type, + ).first() + if not perm2: + perm2 = Permission.objects.create( + codename="perm2", + name="Permission 2", + content_type=feed_content_type, + ) + group2.permissions.add(perm2) + + # Создаём должность с обеими группами + position = Position.objects.create(name="Multi-group Position") + position.groups.add(group1, group2) + + basic_user.position = position + basic_user.save() + basic_user.refresh_from_db() + + # Пользователь должен иметь права из обеих групп + assert basic_user.has_perm("feed.perm1") + assert basic_user.has_perm("feed.perm2") + + def test_get_all_permissions_includes_position_perms( + self, basic_user, engineer_position + ): + """get_all_permissions() включает права из должности.""" + basic_user.position = engineer_position + basic_user.save() + basic_user.refresh_from_db() + + all_perms = basic_user.get_all_permissions() + + # Проверяем, что это set + assert isinstance(all_perms, set) + + # Проверяем содержимое + assert "feed.publish_company_post" in all_perms + assert "documents.add_document" in all_perms + + # Проверяем, что нет дубликатов (set гарантирует это) + perms_list = list(all_perms) + assert len(perms_list) == len(set(perms_list)) + + +class TestPositionPermissionsEdgeCases: + """Тесты граничных случаев и edge cases.""" + + @pytest.fixture + def empty_position(self): + """Должность без групп.""" + return Position.objects.create( + name="Empty Position", + description="Должность без прав", + ) + + def test_position_without_groups_grants_no_permissions( + self, user_factory, empty_position + ): + """Должность без групп не даёт прав.""" + user = user_factory(email="user@example.com") + user.position = empty_position + user.save() + user.refresh_from_db() + + all_perms = user.get_all_permissions() + assert len(all_perms) == 0, "Должность без групп не должна давать прав" + + def test_inactive_user_has_no_permissions( + self, user_factory, engineer_position + ): + """Неактивный пользователь не имеет прав даже с должностью.""" + user = user_factory(email="inactive@example.com", active=False) + user.position = engineer_position + user.save() + + # is_active=False блокирует все права + assert not user.has_perm("feed.publish_company_post") + assert not user.has_perm("documents.add_document") + + def test_position_permissions_with_direct_user_permissions( + self, user_factory, engineer_position, feed_content_type + ): + """Права из должности + прямые права пользователя объединяются.""" + user = user_factory(email="user@example.com") + + # Даём пользователю прямое право + direct_perm, _ = Permission.objects.get_or_create( + codename="direct_perm", + name="Direct Permission", + content_type=feed_content_type, + ) + user.user_permissions.add(direct_perm) + + # Назначаем должность + user.position = engineer_position + user.save() + user.refresh_from_db() + + # Должен иметь и прямые права, и права из должности + assert user.has_perm("feed.direct_perm"), \ + "Прямое право должно работать" + assert user.has_perm("feed.publish_company_post"), \ + "Право из должности должно работать" + assert user.has_perm("documents.add_document"), \ + "Право из должности должно работать" + + +class TestPositionPermissionsIntegration: + """Интеграционные тесты с реальными моделями приложения.""" + + def test_feed_permissions_through_position( + self, user_factory, feed_content_type + ): + """Проверка прав на публикацию постов через должность.""" + # Создаём группу с правами публикации + publishers = Group.objects.create(name="publishers") + + for codename in ["publish_company_post", "publish_department_post"]: + perm, _ = Permission.objects.get_or_create( + codename=codename, + name=f"Can {codename.replace('_', ' ')}", + content_type=feed_content_type, + ) + publishers.permissions.add(perm) + + # Создаём должность редактора + editor_position = Position.objects.create(name="Редактор") + editor_position.groups.add(publishers) + + # Назначаем пользователю + user = user_factory(email="editor@example.com") + user.position = editor_position + user.save() + user.refresh_from_db() + + # Проверяем права + assert user.has_perm("feed.publish_company_post") + assert user.has_perm("feed.publish_department_post") + + def test_documents_permissions_through_position( + self, user_factory, documents_content_type + ): + """Проверка прав на документы через должность.""" + # Создаём группу с правами на документы + doc_managers = Group.objects.create(name="doc_managers") + + for codename in [ + "add_document", "change_document", + "delete_document", "view_document" + ]: + perm, _ = Permission.objects.get_or_create( + codename=codename, + name=f"Can {codename.replace('_', ' ')}", + content_type=documents_content_type, + ) + doc_managers.permissions.add(perm) + + # Создаём должность документоведа + doc_position = Position.objects.create(name="Документовед") + doc_position.groups.add(doc_managers) + + # Назначаем пользователю + user = user_factory(email="docs@example.com") + user.position = doc_position + user.save() + user.refresh_from_db() + + # Проверяем все CRUD права + assert user.has_perm("documents.add_document") + assert user.has_perm("documents.change_document") + assert user.has_perm("documents.delete_document") + assert user.has_perm("documents.view_document")