2026-09-07 19:43:48 +03:00

325 lines
9.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Pytest конфигурация и фикстуры для тестирования LDAP модуля.
"""
import os
from unittest.mock import Mock, patch
import pytest
from django.contrib.auth.models import Group as DjangoGroup
from employees.ldap.domain.dtos import DirectoryDepartmentDTO, DirectoryUserDTO
from employees.models import Department, Employee, LdapSyncState, Position
# ==================== Mock LDAP Connection ====================
@pytest.fixture
def mock_ldap_connection():
"""Mock LDAP соединения для изоляции от реального AD."""
conn = Mock()
conn.result = {"description": "success"}
conn.entries = []
# Mock базовых операций
conn.add = Mock(return_value=True)
conn.delete = Mock(return_value=True)
conn.modify = Mock(return_value=True)
conn.modify_dn = Mock(return_value=True)
conn.search = Mock(return_value=True)
# Mock extend для паролей
conn.extend = Mock()
conn.extend.microsoft = Mock()
conn.extend.microsoft.modify_password = Mock(return_value=True)
return conn
@pytest.fixture
def mock_ldap_context(mock_ldap_connection):
"""Mock контекстного менеджера _ldap() во всех сервисах."""
targets = [
"employees.ldap.infrastructure.connections._ldap",
"employees.ldap.services.group_service._ldap",
"employees.ldap.services.department_service._ldap",
"employees.ldap.services.user_service._ldap",
"employees.ldap.services.position_service._ldap",
]
patches = []
for target in targets:
try:
p = patch(target)
mock = p.start()
mock.return_value.__enter__.return_value = mock_ldap_connection
mock.return_value.__exit__.return_value = None
patches.append(p)
except (AttributeError, ModuleNotFoundError):
pass
yield patches[0] if patches else None
for p in patches:
p.stop()
# ==================== Django Models Fixtures ====================
@pytest.fixture
def sample_employee(db):
"""Создает тестового сотрудника."""
return Employee.objects.create(
first_name="Иван",
last_name="Иванов",
email="ivanov@example.com",
is_active=True,
)
@pytest.fixture
def sample_department(db):
"""Создает тестовый отдел."""
return Department.objects.create(name="IT Отдел", description="IT Department")
@pytest.fixture
def sample_position(db):
"""Создает тестовую должность (без LDAP синхронизации)."""
from django.db.models.signals import post_save
from employees.signals.ldap.position import sync_position_to_ldap_on_save
# Отключаем LDAP синхронизацию при создании Position
post_save.disconnect(sync_position_to_ldap_on_save, sender=Position)
try:
pos = Position.objects.create(
name="Разработчик", description="Backend разработчик"
)
finally:
post_save.connect(sync_position_to_ldap_on_save, sender=Position)
return pos
@pytest.fixture
def sample_django_group(db):
"""Создает тестовую Django группу."""
return DjangoGroup.objects.create(name="Developers")
@pytest.fixture
def sample_ldap_sync_state(db, sample_employee):
"""Создает тестовую запись синхронизации."""
return LdapSyncState.objects.create(
model="employee",
object_pk=str(sample_employee.id),
ldap_dn="CN=Иван Иванов,OU=Users,DC=example,DC=com",
)
# ==================== DTO Fixtures ====================
@pytest.fixture
def sample_user_dto():
"""Создает тестовый DTO для пользователя."""
return DirectoryUserDTO(
first_name="Иван",
last_name="Иванов",
email="ivanov@example.com",
phone_e164="+79001234567",
department_dn="OU=IT,DC=example,DC=com",
group_cns=["Developers", "Users"],
initial_password="Test123456!",
avatar_bytes=None,
is_active=True,
)
@pytest.fixture
def sample_department_dto():
"""Создает тестовый DTO для отдела."""
return DirectoryDepartmentDTO(
name="IT Отдел", description="IT Department", head=None
)
# ==================== Mock Repositories ====================
@pytest.fixture
def mock_ldap_repository():
"""Mock LdapRepository."""
repo = Mock()
repo.read_attrs = Mock(return_value={"cn": ["Test User"]})
repo.is_taken = Mock(return_value=False)
repo.modify_attrs = Mock(return_value=True)
repo.ensure_container_exists = Mock(return_value=True)
repo.modify_or_ignore = Mock(return_value=True)
return repo
@pytest.fixture
def mock_employee_repo_functions():
"""Mock функций employee_repository."""
ns = Mock()
ns.load_users_index = Mock(return_value=({}, {}))
ns.find_user_for_dto = Mock(return_value=None)
ns.bind_user_department = Mock(return_value=None)
ns.get_stale_employee_ids = Mock(return_value=[])
return ns
@pytest.fixture
def mock_sync_state_repo_functions():
"""Mock функций sync_state_repository."""
ns = Mock()
ns.get_or_create = Mock()
ns.touch = Mock()
ns.get_employees_with_dn = Mock(return_value=Employee.objects.none())
ns.delete_for_employee = Mock()
return ns
# ==================== Mock Services ====================
@pytest.fixture
def mock_user_service():
"""Mock UserService для тестирования других сервисов."""
service = Mock()
service.create_user = Mock()
service.update_user = Mock()
service.delete_user = Mock()
service._get_employee_dn = Mock(
return_value="CN=Test User,OU=Users,DC=example,DC=com"
)
service._move_user_to_base = Mock(
return_value="CN=Test User,OU=NewBase,DC=example,DC=com"
)
return service
@pytest.fixture
def mock_group_service():
"""Mock GroupService для тестирования других сервисов."""
service = Mock()
dn = "CN=Test Group,OU=Groups,DC=example,DC=com"
service.create = Mock(return_value=dn)
service.delete = Mock()
service.rename = Mock()
service.set_description = Mock()
service.add_members = Mock()
service.remove_members = Mock()
service.replace_members = Mock()
service.list_members = Mock(return_value=[])
service.find_dn = Mock(return_value=dn)
service.groups_with_member = Mock(return_value=set())
return service
@pytest.fixture
def mock_department_service():
"""Mock DepartmentService для тестирования."""
service = Mock()
service.create_department = Mock()
service.update_department = Mock()
service.delete_department = Mock()
service.add_member = Mock()
service.remove_member = Mock()
service.set_head = Mock()
service._get_department_dn = Mock(
return_value="OU=Test Department,DC=example,DC=com"
)
return service
@pytest.fixture
def mock_position_service():
"""Mock PositionService для тестирования."""
service = Mock()
service.reconcile_position = Mock()
service.assign_position = Mock()
service.unassign_position = Mock()
service.delete_position_group = Mock()
return service
# ==================== LDAP Entry Mocks ====================
@pytest.fixture
def mock_ldap_entry():
"""Mock LDAP entry объекта."""
entry = Mock()
entry.entry_dn = "CN=Test User,OU=Users,DC=example,DC=com"
entry.cn = Mock()
entry.cn.value = "Test User"
entry.mail = Mock()
entry.mail.value = "test@example.com"
entry.objectGUID = Mock()
# Mock GUID (16 bytes)
guid_bytes = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10"
entry.objectGUID.value = guid_bytes
return entry
# ==================== Settings Overrides ====================
@pytest.fixture
def ldap_test_settings(settings):
"""Тестовые настройки LDAP."""
settings.LDAP_HOST = "ldaps://test.example.com:636"
settings.LDAP_BASE_DN = "DC=example,DC=com"
settings.LDAP_BIND_DN = "CN=TestUser,DC=example,DC=com"
settings.LDAP_BIND_PASSWORD = os.getenv(
"LDAP_BIND_PASSWORD",
"test-ldap-bind-password",
)
settings.LDAP_GROUPS_BASE = "OU=Groups,DC=example,DC=com"
settings.LDAP_POSITIONS_BASE = "OU=Positions,DC=example,DC=com"
settings.LDAP_UPN_SUFFIX = "@example.com"
return settings
# ==================== Helper Functions ====================
def create_mock_ldap_entry(dn, attributes=None):
"""
Создает mock LDAP entry с заданными атрибутами.
Args:
dn: Distinguished Name
attributes: Словарь атрибутов {attr_name: value}
Returns:
Mock объект LDAP entry
"""
entry = Mock()
entry.entry_dn = dn
if attributes:
for attr_name, attr_value in attributes.items():
attr_mock = Mock()
attr_mock.value = attr_value
setattr(entry, attr_name, attr_mock)
return entry
def assert_ldap_operation_called(mock_conn, operation, dn=None):
"""
Проверяет, что LDAP операция была вызвана.
Args:
mock_conn: Mock LDAP соединения
operation: Название операции ('add', 'delete', 'modify', etc.)
dn: Ожидаемый DN (опционально)
"""
op_mock = getattr(mock_conn, operation)
assert op_mock.called, f"LDAP операция '{operation}' не была вызвана"
if dn and op_mock.call_args:
called_dn = op_mock.call_args[0][0]
assert called_dn == dn, f"Ожидался DN '{dn}', получен '{called_dn}'"