Игорь Надеин 4d04aafdb9 refactor: Replace inline CSS with components (Phase 3)
- Replaced feed-* inline styles in base.html with feed-cards.css component (~60 lines)
- Replaced ios-search inline styles in 3 templates with ios-search.css component
  * employees/employees_list.html
  * search/results.html
  * employees/department_list.html
- Eliminated ~150 lines of duplicate CSS across templates
- All 17 tests passing
2025-10-28 08:54:38 +03:00

296 lines
8.8 KiB
Python
Raw 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 pytest
from unittest.mock import Mock, patch
from django.contrib.auth.models import Group as DjangoGroup
from employees.models import Employee, Department, Position, LdapSyncState
from employees.ldap.domain.dtos import DirectoryUserDTO, DirectoryDepartmentDTO
# ==================== 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()."""
with patch('employees.ldap.infrastructure.connections._ldap') as mock:
mock.return_value.__enter__.return_value = mock_ldap_connection
mock.return_value.__exit__.return_value = None
yield mock
# ==================== 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):
"""Создает тестовую должность."""
return Position.objects.create(
name="Разработчик",
description="Backend разработчик"
)
@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_repository():
"""Mock EmployeeRepository."""
repo = Mock()
repo.load_users_index = Mock(return_value=({}, {}))
repo.find_user_for_dto = Mock(return_value=None)
repo.bind_user_department = Mock(return_value=True)
repo.cleanup_absent_users = Mock(return_value=0)
return repo
@pytest.fixture
def mock_sync_state_repository():
"""Mock SyncStateRepository."""
repo = Mock()
repo.get_or_create = Mock()
repo.touch = Mock()
repo.get_employees_with_dn = Mock(return_value=Employee.objects.none())
repo.delete_for_employee = Mock()
return repo
# ==================== 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'
b'\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 = 'test_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}'"