46 KiB
46 KiB
План тестирования: Опциональная интеграция с LDAP
Цель тестирования
Проверить корректность работы системы в двух режимах:
- LDAP_ENABLED=True - полная интеграция с LDAP (существующая функциональность)
- 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 критерии:
- Все автоматические тесты проходят в обоих режимах (≥95% успешности)
- Нет критических ошибок (500) при корректных запросах
- Данные в БД консистентны после всех операций
- В режиме с LDAP: данные синхронизированы между LDAP и БД
- В режиме без LDAP: все операции работают без вызовов DirectoryService
- Rollback механизмы работают корректно при ошибках
- Аутентификация работает в обоих режимах
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. Заключение
Этот план тестирования обеспечивает:
- Полноту покрытия - все 50+ методов во всех ViewSets
- Двойное тестирование - каждая функция в обоих режимах
- Автоматизацию - pytest тесты для CI/CD
- Документированность - четкие test case ID и ожидаемые результаты
- Воспроизводимость - фикстуры и примеры кода
- Масштабируемость - легко добавить новые тест-кейсы
Следующие шаги:
- Реализовать базовые автотесты (conftest.py, test_helpers.py)
- Постепенно покрыть каждый ViewSet тестами
- Настроить CI для автоматического прогона
- Провести ручное тестирование по чек-листу
- Исправить найденные баги
- Создать итоговый отчёт