Corp/backend/tests/TESTING_PLAN_LDAP_OPTIONAL.md

46 KiB
Raw Blame History

План тестирования: Опциональная интеграция с LDAP

Цель тестирования

Проверить корректность работы системы в двух режимах:

  1. LDAP_ENABLED=True - полная интеграция с LDAP (существующая функциональность)
  2. LDAP_ENABLED=False - автономная работа без LDAP (новая функциональность)

Конфигурация окружения

Режим 1: С LDAP (LDAP_ENABLED=True)

export LDAP_ENABLED=true
export LDAP_SERVER_URI=ldap://your-ldap-server:389
export LDAP_BIND_DN=CN=admin,DC=example,DC=com
export LDAP_BIND_PASSWORD=your-password
export LDAP_BASE_DN=DC=example,DC=com
export LDAP_USERS_BASE=OU=Users,DC=example,DC=com
export LDAP_GROUPS_BASE=OU=Groups,DC=example,DC=com
export LDAP_UPN_SUFFIX=@example.com

Режим 2: Без LDAP (LDAP_ENABLED=False)

export LDAP_ENABLED=false
# Все остальные LDAP настройки можно не задавать

1. Тестирование вспомогательных функций

1.1 Функция _is_ldap_enabled()

ID Сценарий LDAP_ENABLED Ожидаемый результат
H1 Проверка с LDAP True Возвращает True
H2 Проверка без LDAP False Возвращает False
H3 LDAP_ENABLED не задан (не установлен) Возвращает False (по умолчанию)

1.2 Функция _ldap_try(fn)

ID Сценарий LDAP_ENABLED Действие Ожидаемый результат
H4 Успешное выполнение True fn() успешна Возвращает None
H5 Ошибка LDAP True fn() бросает DirectoryLdapError Возвращает Response(502)
H6 LDAP отключен False fn() не вызывается Возвращает None
H7 Ошибка DirectoryServiceError True fn() бросает DirectoryServiceError Возвращает Response(502)

2. Тестирование RegisterAPIView

2.1 Регистрация пользователя (POST /api/v1/register/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Тест-данные Ожидаемый результат
R1 Успешная регистрация Валидные данные (email, пароль, имя, телефон) • Создан пользователь в LDAP (disabled)
• Пароль установлен в LDAP
• Создана запись в БД (unusable_password)
• is_active=False
• Отправлено письмо верификации
• HTTP 201
R2 Регистрация с аватаром + файл avatar • Аватар загружен в LDAP
• Сохранён в БД
• HTTP 201
R3 Дублирование email Email уже существует HTTP 400 "email_exists"
R4 Дублирование телефона Телефон уже существует HTTP 400 "phone_exists"
R5 LDAP недоступен LDAP сервер не отвечает HTTP 502 "ldap_error"
R6 Ошибка создания в БД БД недоступна после LDAP • LDAP пользователь удалён (rollback)
• HTTP 500

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Тест-данные Ожидаемый результат
R7 Успешная регистрация Валидные данные • Создан пользователь только в БД
• Пароль установлен в БД (set_password)
• is_active=False
• Письмо верификации отправлено
• HTTP 201
R8 Регистрация с аватаром + файл avatar • Аватар сохранён в media/
• HTTP 201
R9 Дублирование данных Email/телефон существует HTTP 400

Проверки после регистрации:

# С LDAP
assert Employee.objects.filter(email=test_email).exists()
assert not user.has_usable_password()  # Пароль в LDAP
assert LdapSyncState.objects.filter(model='employee', object_pk=user.id).exists()

# Без LDAP
assert Employee.objects.filter(email=test_email).exists()
assert user.has_usable_password()  # Пароль в БД
assert user.check_password(test_password)

3. Тестирование VerifyEmailAPIView

3.1 Верификация email (POST /api/v1/verify-email/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Предусловия Ожидаемый результат
V1 Успешная верификация Пользователь существует, не верифицирован • is_active=True в БД
• Активирован в LDAP (enabled)
• HTTP 200
V2 Повторная верификация Уже верифицирован HTTP 400 "already_verified"
V3 Неверный токен Токен не существует/истёк HTTP 404 "user_not_found"
V4 LDAP ошибка активации LDAP недоступен HTTP 502 с деталями ошибки

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Предусловия Ожидаемый результат
V5 Успешная верификация Пользователь не верифицирован • is_active=True в БД
• HTTP 200
V6 Повторная верификация Уже верифицирован HTTP 400

Проверки после верификации:

# С LDAP
assert user.is_active
assert ldap_user_is_enabled(user.ldap_dn)

# Без LDAP
assert user.is_active
assert user.check_password(original_password)  # Пароль сохранён

4. Тестирование DepartmentViewSet

4.1 Создание отдела (POST /api/v1/departments/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Тест-данные Ожидаемый результат
D1 Создание отдела name, description • OU создан в LDAP
• Запись в БД
• HTTP 201
D2 Создание с главой + head_id • OU создан
• head установлен через DirectoryService
• HTTP 201
D3 LDAP ошибка LDAP недоступен HTTP 502
D4 Ошибка БД после LDAP БД недоступна • OU удалён (rollback)
• HTTP 500

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Тест-данные Ожидаемый результат
D5 Создание отдела name, description • Только запись в БД
• HTTP 201
D6 Создание с главой + head_id • Запись в БД с head
• HTTP 201

4.2 Обновление отдела (PATCH /api/v1/departments/{id}/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Изменения Ожидаемый результат
D7 Изменение имени name="New Name" • OU переименован в LDAP
• Обновлено в БД
• HTTP 200
D8 Смена главы head_id=new_head • set_head() в LDAP
• head_appointed_at установлен
• HTTP 200
D9 Удаление главы head_id=null • set_head(None) в LDAP
• head=null в БД
• HTTP 200

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Изменения Ожидаемый результат
D10 Изменение имени name="New Name" • Обновлено только в БД
• HTTP 200
D11 Смена главы head_id=new_head • head обновлён в БД
• head_appointed_at установлен
• HTTP 200

4.3 Управление участниками отдела

add_member (POST /api/v1/departments/{id}/add-member/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
D12 Добавление участника employee_id • Пользователь MOVE в OU отдела в LDAP
• DepartmentMember создан/активирован
• HTTP 200
D13 Повторное добавление Уже участник • Активация существующей связи
• HTTP 200
D14 LDAP ошибка LDAP недоступен HTTP 502
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
D15 Добавление участника employee_id • DepartmentMember создан/активирован в БД
• HTTP 200

remove_member (POST /api/v1/departments/{id}/remove-member/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
D16 Удаление участника employee_id • Пользователь MOVE обратно в Users OU
• is_active=False в DepartmentMember
• HTTP 200
D17 Удаление несуществующего Нет такого участника HTTP 404
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
D18 Удаление участника employee_id • is_active=False в БД
• HTTP 200

4.4 set_member_role (POST /api/v1/departments/{id}/set-member-role/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Данные Ожидаемый результат
D19 Назначение роли employee_id, role_id • Роль назначена в LDAP (группы)
• role обновлена в БД
• HTTP 200
D20 Снятие роли employee_id, role_id=null • Роли удалены в LDAP
• role=null в БД
• HTTP 200

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Данные Ожидаемый результат
D21 Назначение роли employee_id, role_id • role обновлена только в БД
• HTTP 200

4.5 Удаление отдела (DELETE /api/v1/departments/{id}/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Условия Ожидаемый результат
D22 Удаление пустого отдела Нет участников • OU удалён в LDAP
• Удалено из БД
• HTTP 204
D23 Удаление с участниками Есть активные участники • OU удалён
• Участники перемещены в Users OU
• HTTP 204
D24 LDAP ошибка без force_db LDAP недоступен, force_db=false HTTP 502
D25 LDAP ошибка с force_db LDAP недоступен, force_db=true • БД удалена несмотря на LDAP
• HTTP 204

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Условия Ожидаемый результат
D26 Удаление отдела Любые условия • Удалено из БД
• HTTP 204

5. Тестирование EmployeeViewSet

5.1 Создание сотрудника (POST /api/v1/employees/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Данные Ожидаемый результат
E1 Создание базовое email, имя, пароль • Пользователь в LDAP
• Запись в БД
• HTTP 201
E2 Создание с позицией + position_id • Создан в LDAP
• assign_position() вызван
• Группы позиции назначены
• HTTP 201
E3 Создание с аватаром + avatar file • Аватар в LDAP
• HTTP 201
E4 LDAP ошибка LDAP недоступен HTTP 502

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Данные Ожидаемый результат
E5 Создание базовое email, имя, пароль • Только в БД
• Пароль через set_password()
• HTTP 201
E6 Создание с позицией + position_id • Создан в БД
• position установлена
• Группы через M2M
• HTTP 201

5.2 Обновление сотрудника (PATCH /api/v1/employees/{id}/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Изменения Ожидаемый результат
E7 Обновление имени first_name, last_name • update_user() в LDAP
• Обновлено в БД
• HTTP 200
E8 Смена email email="new@mail.com" • Email обновлён в LDAP
• Обновлён в БД
• HTTP 200
E9 Смена позиции position_id=new_position • Позиция обновлена в LDAP
• Группы пересчитаны
• HTTP 200
E10 Удаление позиции position=null • Позиция удалена в LDAP
• position=null в БД
• HTTP 200
E11 Обновление аватара + новый avatar • Аватар обновлён в LDAP
• Сохранён в БД
• HTTP 200
E12 Перемещение в отдел department_dn=dept_dn • MOVE в LDAP
• HTTP 200
E13 Назначение групп group_cns=['CN=Group1',...] • Группы назначены в LDAP
• HTTP 200

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Изменения Ожидаемый результат
E14 Обновление всех полей first_name, last_name, email, phone, position, avatar • Все обновлено в БД
• Аватар сохранён в media/
• HTTP 200
E15 Смена позиции position_id=new_position • Позиция обновлена в БД
• Группы через M2M
• HTTP 200

Проверки:

# С LDAP
user = Employee.objects.get(id=test_id)
ldap_user = DirectoryService().get_user_by_dn(user.ldap_dn)
assert ldap_user['givenName'] == user.first_name
assert ldap_user['sn'] == user.last_name

# Без LDAP
user = Employee.objects.get(id=test_id)
assert user.first_name == expected_first_name
assert user.position_id == expected_position_id

6. Тестирование PositionViewSet

6.1 CRUD позиций

С LDAP (LDAP_ENABLED=True)

ID Сценарий Действие Ожидаемый результат
P1 Создание позиции POST /api/v1/positions/ • Позиция в БД
• reconcile_position() вызван
• Группа создана в LDAP
• HTTP 201
P2 Обновление позиции PATCH /api/v1/positions/{id}/ • Обновлено в БД
• reconcile_position() вызван
• HTTP 200
P3 Удаление позиции DELETE /api/v1/positions/{id}/ • delete_position_group() вызван
• Удалено из БД
• HTTP 204
P4 Назначение групп POST /positions/{id}/set-groups • groups.set() в БД
• reconcile_position() синхронизирует LDAP
• HTTP 200
P5 Добавление групп POST /positions/{id}/add-groups • groups.add() в БД
• reconcile_position()
• HTTP 200
P6 Удаление групп POST /positions/{id}/remove-groups • groups.remove() в БД
• reconcile_position()
• HTTP 200

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Действие Ожидаемый результат
P7 Создание позиции POST /api/v1/positions/ • Только в БД
• LDAP не вызывается
• HTTP 201
P8 Обновление позиции PATCH /api/v1/positions/{id}/ • Обновлено в БД
• HTTP 200
P9 Удаление позиции DELETE /api/v1/positions/{id}/ • Удалено из БД
• HTTP 204
P10 Управление группами set/add/remove-groups • Только M2M в БД
• HTTP 200

Проверки _ldap_try():

# _ldap_try() должен возвращать None в обоих случаях:
# - LDAP_ENABLED=True и операция успешна → None
# - LDAP_ENABLED=False → None (операция пропущена)

# Это не должно блокировать дальнейшее выполнение

7. Тестирование GroupViewSet

7.1 Создание группы (POST /api/v1/groups/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Данные Ожидаемый результат
G1 Создание группы name, description • Группа создана в LDAP
• Запись в БД
• HTTP 201
G2 С permissions + permissions=[1,2,3] • Группа в LDAP
• Permissions в БД
• HTTP 201
G3 LDAP ошибка LDAP недоступен HTTP 502
G4 Ошибка БД БД сбой после LDAP • Группа удалена из LDAP (rollback)
• HTTP 500

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Данные Ожидаемый результат
G5 Создание группы name, description, permissions • Только в БД
• Permissions через M2M
• HTTP 201

7.2 Обновление группы (PATCH /api/v1/groups/{id}/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Изменения Ожидаемый результат
G6 Переименование name="New Name" • group_rename() в LDAP
• Обновлено в БД
• HTTP 200
G7 Изменение описания ldap_description="..." • group_set_description() в LDAP
• HTTP 200
G8 Группа не найдена в LDAP _resolve_group_dn() → None HTTP 404

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Изменения Ожидаемый результат
G9 Переименование name="New Name" • Обновлено в БД
• HTTP 200

7.3 Action методы группы

rename (POST /api/v1/groups/{id}/rename/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
G10 Переименование new_name="NewName" • group_rename() в LDAP
• name обновлён в БД
• HTTP 200
G11 LDAP ошибка LDAP недоступен HTTP 502
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
G12 Переименование new_name="NewName" • Обновлено в БД
• HTTP 200

set_description (POST /api/v1/groups/{id}/set-description/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
G13 Установка описания description="..." • group_set_description() в LDAP
• HTTP 200
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
G14 Установка описания description="..." • Ничего не происходит (нет поля в БД)
• HTTP 200

members (GET /api/v1/groups/{id}/members/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Условия Ожидаемый результат
G15 Получение участников Группа существует • group_list_members() из LDAP
• Сопоставление с БД через LdapSyncState
• HTTP 200 {"dns": [...], "employees": [...]}
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Условия Ожидаемый результат
G16 Получение участников Группа существует • Участники из БД (grp.user_set.all())
• HTTP 200 {"dns": [], "employees": [...]}

add_members (POST /api/v1/groups/{id}/add-members/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
G17 Добавление через DN member_dns=['CN=User1,...'] • group_add_members() в LDAP
• user_set.add() в БД
• HTTP 200
G18 Добавление через ID member_ids=[1,2,3] • ID→DN преобразование
• Добавлены в LDAP и БД
• HTTP 200
G19 Ошибка БД БД сбой после LDAP • group_remove_members() (rollback)
• HTTP 500
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
G20 Добавление участников member_ids=[1,2,3] • user_set.add() в БД
• HTTP 200

remove_members (POST /api/v1/groups/{id}/remove-members/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
G21 Удаление участников member_dns/member_ids • group_remove_members() в LDAP
• user_set.remove() в БД
• HTTP 200
G22 Ошибка БД БД сбой после LDAP • group_add_members() (rollback)
• HTTP 500
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
G23 Удаление участников member_ids=[1,2,3] • user_set.remove() в БД
• HTTP 200

replace_members (POST /api/v1/groups/{id}/replace-members/)

С LDAP (LDAP_ENABLED=True)
ID Сценарий Данные Ожидаемый результат
G24 Замена состава member_dns/member_ids • Сохранён старый состав
• group_replace_members() в LDAP
• user_set.set() в БД
• HTTP 200
G25 Ошибка БД БД сбой после LDAP • group_replace_members(prev_dns) (rollback)
• HTTP 500
Без LDAP (LDAP_ENABLED=False)
ID Сценарий Данные Ожидаемый результат
G26 Замена состава member_ids=[...] • user_set.set() в БД
• HTTP 200

7.4 Удаление группы (DELETE /api/v1/groups/{id}/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Параметры Ожидаемый результат
G27 Удаление группы force_db=false • group_delete() в LDAP
• Удалено из БД
• HTTP 204
G28 LDAP ошибка без force force_db=false, LDAP недоступен HTTP 502
G29 LDAP ошибка с force force_db=true, LDAP недоступен • Игнорируем LDAP
• Удалено из БД
• HTTP 204

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Параметры Ожидаемый результат
G30 Удаление группы Любые • Удалено из БД
• HTTP 204

7.5 Список групп (GET /api/v1/groups/)

С LDAP (LDAP_ENABLED=True)

ID Сценарий Условия Ожидаемый результат
G31 Получение списка Любые • sync_groups_catalog() вызван (throttled)
• Список из БД
• HTTP 200

Без LDAP (LDAP_ENABLED=False)

ID Сценарий Условия Ожидаемый результат
G32 Получение списка Любые • Список из БД
• HTTP 200

8. Интеграционные тесты

8.1 Сквозной сценарий: Регистрация → Верификация → Назначение позиции

С LDAP

Шаг Действие Проверка
1 POST /api/v1/register/ Пользователь в LDAP (disabled) и БД (inactive)
2 POST /api/v1/verify-email/ Активирован в LDAP и БД
3 PATCH /api/v1/employees/{id}/ (position_id) Группы позиции назначены в LDAP
4 GET /api/v1/employees/{id}/ position корректна, is_active=true

Без LDAP

Шаг Действие Проверка
1 POST /api/v1/register/ Пользователь только в БД, пароль установлен
2 POST /api/v1/verify-email/ is_active=true в БД
3 PATCH /api/v1/employees/{id}/ (position_id) Позиция обновлена, группы через M2M
4 Аутентификация EmailOrPhoneBackend работает, пароль проверяется из БД

8.2 Сквозной сценарий: Создание отдела → Добавление участника → Назначение роли

С LDAP

Шаг Действие Проверка
1 POST /api/v1/departments/ OU создан в LDAP
2 POST /departments/{id}/add-member/ Пользователь перемещён в OU
3 POST /departments/{id}/set-member-role/ Роли назначены через LDAP группы

Без LDAP

Шаг Действие Проверка
1 POST /api/v1/departments/ Запись в БД
2 POST /departments/{id}/add-member/ DepartmentMember создан в БД
3 POST /departments/{id}/set-member-role/ role обновлена в БД

8.3 Сценарий: Создание группы → Добавление участников → Проверка permissions

С LDAP

Шаг Действие Проверка
1 POST /api/v1/groups/ (+ permissions) Группа в LDAP, permissions в БД
2 POST /groups/{id}/add-members/ Участники добавлены в LDAP группу и БД
3 Проверка прав пользователя user.has_perm() работает корректно

Без LDAP

Шаг Действие Проверка
1 POST /api/v1/groups/ (+ permissions) Группа и permissions в БД
2 POST /groups/{id}/add-members/ user_set.add() в БД
3 Проверка прав user.has_perm() работает через Django permissions

9. Тесты граничных условий и ошибок

9.1 Переключение режима во время работы

ID Сценарий Действия Ожидаемый результат
B1 LDAP→No LDAP 1. Создать пользователя с LDAP
2. Отключить LDAP
3. Обновить пользователя
• Обновление работает только с БД
• Данные не теряются
B2 No LDAP→LDAP 1. Создать пользователя без LDAP
2. Включить LDAP
3. Обновить пользователя
• Обновление пытается синхронизировать с LDAP
• Может вернуть 502 если пользователя нет в LDAP

9.2 Частичные сбои LDAP

ID Сценарий Условия Ожидаемый результат
B3 LDAP timeout LDAP_ENABLED=True, LDAP не отвечает HTTP 502 с деталями ошибки
B4 LDAP частичный сбой Создание в LDAP OK, обновление FAIL Зависит от операции - либо rollback, либо 502
B5 БД сбой после LDAP LDAP OK, БД FAIL Rollback LDAP операции, HTTP 500

9.3 Некорректные данные

ID Сценарий Данные Ожидаемый результат
B6 LDAP_ENABLED не boolean "yes"/"no"/"1"/"0" Корректно обрабатывается через .lower() == "true"
B7 Отсутствующие LDAP настройки LDAP_ENABLED=True, но нет SERVER_URI Ошибка при инициализации DirectoryService
B8 Некорректный DN Попытка работы с несуществующим DN HTTP 404 или 502 в зависимости от метода

10. Тесты производительности

10.1 Массовые операции

ID Сценарий Параметры Проверка
P1 Массовая регистрация 100 пользователей • С LDAP: время создания < 30s
• Без LDAP: время < 10s
P2 Добавление 50 участников в группу 50 member_ids • Операция выполняется атомарно
• При ошибке - полный rollback
P3 Создание 20 отделов 20 departments • С LDAP: все OU созданы
• Без LDAP: только БД

10.2 Нагрузочное тестирование

ID Сценарий Условия Цель
P4 Concurrent создание пользователей 10 параллельных запросов Нет race conditions
P5 Обновление одного пользователя 5 параллельных PATCH Последовательность транзакций корректна

11. Автоматизация тестирования

11.1 Структура тестов

tests/
├── test_ldap_optional/
│   ├── __init__.py
│   ├── conftest.py              # Фикстуры для двух режимов
│   ├── test_helpers.py          # Тесты вспомогательных функций
│   ├── test_register.py         # RegisterAPIView
│   ├── test_verify_email.py     # VerifyEmailAPIView
│   ├── test_departments.py      # DepartmentViewSet
│   ├── test_employees.py        # EmployeeViewSet
│   ├── test_positions.py        # PositionViewSet
│   ├── test_groups.py           # GroupViewSet
│   ├── test_integration.py      # Сквозные сценарии
│   └── test_edge_cases.py       # Граничные условия

11.2 Фикстуры pytest

# conftest.py
import pytest
from django.conf import settings

@pytest.fixture(params=[True, False], ids=["with_ldap", "without_ldap"])
def ldap_mode(request, settings):
    """Параметризованная фикстура для тестирования обоих режимов"""
    original_value = getattr(settings, 'LDAP_ENABLED', False)
    settings.LDAP_ENABLED = request.param
    yield request.param
    settings.LDAP_ENABLED = original_value

@pytest.fixture
def with_ldap(settings):
    """Фикстура для тестов только с LDAP"""
    settings.LDAP_ENABLED = True
    yield
    settings.LDAP_ENABLED = False

@pytest.fixture
def without_ldap(settings):
    """Фикстура для тестов только без LDAP"""
    settings.LDAP_ENABLED = False
    yield

@pytest.fixture
def test_user_data():
    """Базовые данные для создания тестового пользователя"""
    return {
        "email": "test@example.com",
        "first_name": "Test",
        "last_name": "User",
        "phone_number": "+79001234567",
        "password": "SecurePass123!",
        "whatsapp": "+79001234567"
    }

11.3 Примеры тестов

# test_register.py
import pytest
from rest_framework.test import APIClient
from employees.models import Employee

@pytest.mark.django_db
class TestRegisterAPIView:
    
    def test_register_user_both_modes(self, ldap_mode, test_user_data):
        """R1, R7: Регистрация в обоих режимах"""
        client = APIClient()
        response = client.post('/api/v1/register/', test_user_data)
        
        assert response.status_code == 201
        user = Employee.objects.get(email=test_user_data['email'])
        assert user.is_active is False
        
        if ldap_mode:  # LDAP_ENABLED=True
            assert not user.has_usable_password()  # Пароль в LDAP
            # Проверка создания в LDAP
            from employees.ldap.services import DirectoryService
            svc = DirectoryService()
            ldap_user = svc.get_user_by_email(test_user_data['email'])
            assert ldap_user is not None
        else:  # LDAP_ENABLED=False
            assert user.has_usable_password()  # Пароль в БД
            assert user.check_password(test_user_data['password'])
    
    def test_register_with_avatar_with_ldap(self, with_ldap, test_user_data):
        """R2: Регистрация с аватаром (LDAP)"""
        client = APIClient()
        with open('test_files/avatar.jpg', 'rb') as avatar:
            test_user_data['avatar'] = avatar
            response = client.post('/api/v1/register/', test_user_data, format='multipart')
        
        assert response.status_code == 201
        user = Employee.objects.get(email=test_user_data['email'])
        assert user.avatar  # Аватар сохранён
    
    def test_register_duplicate_email(self, ldap_mode, test_user_data):
        """R3, R9: Дублирование email"""
        client = APIClient()
        Employee.objects.create_user(**test_user_data)
        
        response = client.post('/api/v1/register/', test_user_data)
        assert response.status_code == 400
# test_groups.py
import pytest
from rest_framework.test import APIClient
from django.contrib.auth.models import Group

@pytest.mark.django_db
class TestGroupViewSet:
    
    def test_create_group_both_modes(self, ldap_mode, admin_user):
        """G1, G5: Создание группы"""
        client = APIClient()
        client.force_authenticate(user=admin_user)
        
        data = {"name": "TestGroup", "ldap_description": "Test description"}
        response = client.post('/api/v1/groups/', data)
        
        assert response.status_code == 201
        group = Group.objects.get(name="TestGroup")
        assert group is not None
        
        if ldap_mode:
            # Проверка создания в LDAP
            from employees.ldap.services import DirectoryService
            svc = DirectoryService()
            dn = svc.group_find_dn("TestGroup")
            assert dn is not None
    
    def test_add_members_both_modes(self, ldap_mode, admin_user, test_group, test_users):
        """G17, G20: Добавление участников"""
        client = APIClient()
        client.force_authenticate(user=admin_user)
        
        member_ids = [u.id for u in test_users[:3]]
        response = client.post(
            f'/api/v1/groups/{test_group.id}/add-members/',
            {"member_ids": member_ids}
        )
        
        assert response.status_code == 200
        assert test_group.user_set.count() == 3
        
        if ldap_mode:
            # Проверка в LDAP
            from employees.ldap.services import DirectoryService
            svc = DirectoryService()
            dn = svc.group_find_dn(test_group.name)
            members = svc.group_list_members(dn)
            assert len(members) == 3

11.4 Команды для запуска тестов

# Все тесты в обоих режимах
pytest tests/test_ldap_optional/ -v

# Только тесты с LDAP
pytest tests/test_ldap_optional/ -v -k "with_ldap"

# Только тесты без LDAP
pytest tests/test_ldap_optional/ -v -k "without_ldap"

# Конкретный тестовый файл
pytest tests/test_ldap_optional/test_register.py -v

# С покрытием кода
pytest tests/test_ldap_optional/ --cov=api.v1.employees.views --cov-report=html

# Параллельный запуск (быстрее)
pytest tests/test_ldap_optional/ -v -n auto

12. Ручное тестирование

12.1 Чек-лист для ручной проверки

Подготовка

  • Создать .env файл с LDAP_ENABLED=true
  • Создать .env файл с LDAP_ENABLED=false
  • Поднять LDAP сервер (для режима с LDAP)
  • Запустить миграции БД

Регистрация и верификация

  • Зарегистрировать пользователя с LDAP
  • Зарегистрировать пользователя без LDAP
  • Верифицировать email в обоих режимах
  • Попытаться войти после верификации

Отделы

  • Создать отдел с LDAP
  • Создать отдел без LDAP
  • Добавить участника в отдел (оба режима)
  • Удалить участника из отдела (оба режима)
  • Назначить роль участнику (оба режима)
  • Удалить отдел (оба режима)

Сотрудники

  • Создать сотрудника через админ-панель (оба режима)
  • Обновить имя/email/телефон (оба режима)
  • Назначить позицию (оба режима)
  • Загрузить аватар (оба режима)

Позиции

  • Создать позицию (оба режима)
  • Назначить группы позиции (оба режима)
  • Удалить позицию (оба режима)

Группы

  • Создать группу (оба режима)
  • Переименовать группу (оба режима)
  • Добавить участников в группу (оба режима)
  • Удалить участников из группы (оба режима)
  • Удалить группу (оба режима)

12.2 Инструменты для ручного тестирования

Postman Collection:

  • Импортировать коллекцию API endpoints
  • Создать окружения для LDAP/No-LDAP
  • Выполнить последовательность запросов

LDAP Browser (для режима с LDAP):

  • Apache Directory Studio
  • JXplorer
  • Проверка структуры OU, пользователей, групп

Django Admin:

  • Проверка записей в БД
  • Сравнение с LDAP (в режиме с LDAP)

13. Критерии успешности тестирования

13.1 Обязательные критерии (PASS/FAIL)

✅ PASS критерии:

  1. Все автоматические тесты проходят в обоих режимах (≥95% успешности)
  2. Нет критических ошибок (500) при корректных запросах
  3. Данные в БД консистентны после всех операций
  4. В режиме с LDAP: данные синхронизированы между LDAP и БД
  5. В режиме без LDAP: все операции работают без вызовов DirectoryService
  6. Rollback механизмы работают корректно при ошибках
  7. Аутентификация работает в обоих режимах

13.2 Метрики качества

Метрика Целевое значение
Покрытие кода тестами ≥ 90%
Успешность автотестов 100%
Время выполнения всех тестов < 5 минут
Количество критических багов 0
Количество major багов ≤ 2
Производительность (регистрация с LDAP) < 3s на пользователя
Производительность (регистрация без LDAP) < 1s на пользователя

13.3 Тест-репорт

После завершения тестирования создать отчёт:

# Отчёт о тестировании LDAP Optional Integration

## Окружение
- Django версия: X.X.X
- Python версия: X.X.X
- LDAP сервер: OpenLDAP X.X.X / AD

## Результаты автотестов
- Всего тестов: XXX
- Успешно: XXX (XX%)
- Провалено: XX (XX%)
- Пропущено: XX

## Покрытие кода
- Общее: XX%
- views.py: XX%

## Найденные баги
1. [CRITICAL] Описание бага...
2. [MAJOR] Описание бага...

## Рекомендации
- Улучшение 1...
- Улучшение 2...

## Заключение
✅ Система готова к развёртыванию / ❌ Требуется доработка

14. Continuous Integration (CI)

14.1 GitHub Actions / GitLab CI

# .github/workflows/test_ldap_optional.yml
name: Test LDAP Optional Integration

on: [push, pull_request]

jobs:
  test-with-ldap:
    runs-on: ubuntu-latest
    services:
      ldap:
        image: osixia/openldap:latest
        ports:
          - 389:389
        env:
          LDAP_ORGANISATION: "Test Org"
          LDAP_DOMAIN: "example.com"
          LDAP_ADMIN_PASSWORD: "admin"
    
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.10'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      - name: Run tests with LDAP
        env:
          LDAP_ENABLED: true
          LDAP_SERVER_URI: ldap://localhost:389
        run: |
          pytest tests/test_ldap_optional/ -v -k "with_ldap"
  
  test-without-ldap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: '3.10'
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      - name: Run tests without LDAP
        env:
          LDAP_ENABLED: false
        run: |
          pytest tests/test_ldap_optional/ -v -k "without_ldap"

15. Заключение

Этот план тестирования обеспечивает:

  1. Полноту покрытия - все 50+ методов во всех ViewSets
  2. Двойное тестирование - каждая функция в обоих режимах
  3. Автоматизацию - pytest тесты для CI/CD
  4. Документированность - четкие test case ID и ожидаемые результаты
  5. Воспроизводимость - фикстуры и примеры кода
  6. Масштабируемость - легко добавить новые тест-кейсы

Следующие шаги:

  1. Реализовать базовые автотесты (conftest.py, test_helpers.py)
  2. Постепенно покрыть каждый ViewSet тестами
  3. Настроить CI для автоматического прогона
  4. Провести ручное тестирование по чек-листу
  5. Исправить найденные баги
  6. Создать итоговый отчёт