Введение
Последующее изменение может случайно отменить исправление. Регрессионный тест безопасности превращает важное требование в повторяемый запрос и ожидаемый результат.
Вы расширите небольшой шаблон Python для защищённой точки доступа DeskLite. Затем проверите сами тесты на двух намеренно неисправных локальных сервисах. Установка тестового фреймворка или опыт написания тестов не нужны.
Проверьте защищённую точку доступа
На этом шаге вы определите поведение, которое должны защищать тесты.
Подготовленный корректный сервис работает на локальном порту 8080. Его точка /tickets возвращает исходную заявку при действующем токене и 401 с объектом ошибки при отсутствующем или неверном токене. Аутентификация и бизнес-ответ — отдельные требования: один статус 200 не доказывает, что вернулась нужная заявка.
cd /home/labex/project/ticket-tests
Видимый в этой лабораторной работе course_test_fixture_token — публичное, намеренно вымышленное значение тестовых данных. Оно принимается только этими изолированными учебными сервисами и не даёт никаких внешних полномочий. Оно не заменяет приватные учётные данные приложения, изученные ранее.
Используйте тот же параметр заголовка запроса curl — -H, что и в лабораторной работе по токенам:
curl -i -H 'Authorization: Bearer course_test_fixture_token' http://127.0.0.1:8080/tickets
Ожидаются HTTP 200 и {"tickets": [{"id": 1, "title": "Printer offline"}]}. -i включает заголовки HTTP-ответа, в том числе его статус.
Теперь полностью пропустите заголовок:
curl -i http://127.0.0.1:8080/tickets
Ожидаются HTTP 401 и {"error": "unauthorized"}. Такой отказ — требуемое поведение, а не поломка упражнения.
Прочитайте небольшой шаблон теста
На этом шаге вы свяжете каждую строку теста с одной частью реального запроса.
cat test_security.py
Подготовленный файл содержит:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
from api import request загружает подготовленную вспомогательную функцию запроса. def test_valid_token(): определяет именованную тестовую функцию. Строки с отступами принадлежат этой функции, как в функции поиска из предыдущей лабораторной работы.
request(path, headers) отправляет реальный HTTP-запрос и возвращает словарь Python: соответствие имён и значений. Отображения заголовков тоже используют фигурные скобки. {"Authorization": "Bearer ..."} отправляет один заголовок, а {} — ни одного. response["status"] читает запись статуса; response["body"] читает разобранное тело JSON.
Утверждение, или assertion, задаёт ожидание. Python == сравнивает два значения; это отличается от одиночного =, используемого для присваивания. Ложный assert вызывает AssertionError, из-за чего тест не проходит. Последняя строка сравнивает полный ожидаемый бизнес-объект, включая заголовок заявки.
Вспомогательная функция выбирает подготовленный сервис по целевому аргументу средства запуска. Вы будете редактировать тесты, а не вспомогательную функцию или сервис.
Найдите пробел в проходящем тесте
На этом шаге вы увидите, почему одного проходящего позитивного теста недостаточно.
Запустите подготовленное средство запуска тестов. Его аргумент good выбирает корректный сервис:
python3 run_tests.py good
Ожидается test_valid_token с "result": "PASS". Средство запуска собирает функции, чьи имена начинаются с test_, и вызывает каждую. Оно возвращает код завершения 0, когда все проходят, 1 при невыполненном утверждении и 2 при ошибке теста или выполнения.
Два других сервиса намеренно неисправны: missing-auth принимает запросы без токена; wrong-auth принимает неверный токен. Их позитивный ответ по-прежнему работает. Попробуйте оба:
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Оба по-прежнему проходят единственный существующий тест. Этот результат показывает пробел в покрытии. Тест ни разу не отправил запрос, который должен быть отклонён, поэтому не мог заметить ни один обход.
Добавьте требование для отсутствующего токена
На этом шаге вы добавите один тест, ожидающий отказа для реального запроса без аутентификации.
Откройте файл тестов:
nano test_security.py
Сохраните позитивный тест и добавьте новую функцию под ним. Полный файл должен быть таким:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
Пустое отображение {} означает отсутствие заголовков запроса. Утверждение о статусе проверяет отказ, а утверждение о теле гарантирует отсутствие данных заявки в ответе с ошибкой. Сохраните отступ в четыре пробела. Сохраните файл с помощью Ctrl+O, Enter, затем выйдите с помощью Ctrl+X.
python3 run_tests.py good
Оба теста должны пройти. Теперь используйте намеренно неисправный сервис с регрессией отсутствующего заголовка:
python3 run_tests.py missing-auth
Ожидается, что test_missing_token покажет "result": "FAIL" и "error": "AssertionError". Этот намеренный код завершения 1 — полезное подтверждение: новый тест обнаруживает принятие сервисом запроса, который тот должен отклонить. Это отличается от ошибки импорта или соединения с сервером.
Добавьте требование для неверного токена
На этом шаге вы охватите присутствующие, но недействительные учётные данные.
Заголовок может присутствовать, не будучи действительным. Проверка только отсутствующего заголовка пропустила бы сервис, принимающий любой непустой токен. Откройте тот же файл и добавьте третью функцию, сохранив первые две:
nano test_security.py
Используйте этот полный файл:
from api import request
def test_valid_token():
response = request("/tickets", {"Authorization": "Bearer course_test_fixture_token"})
assert response["status"] == 200
assert response["body"] == {"tickets": [{"id": 1, "title": "Printer offline"}]}
def test_missing_token():
response = request("/tickets", {})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
def test_wrong_token():
response = request("/tickets", {"Authorization": "Bearer wrong_fixture_token"})
assert response["status"] == 401
assert response["body"] == {"error": "unauthorized"}
wrong_fixture_token намеренно не принимается корректным сервисом. Сохраните файл и выйдите с помощью Ctrl+O, Enter, Ctrl+X.
python3 run_tests.py good
Ожидается прохождение всех трёх тестов. Затем проверьте регрессию неверного заголовка:
python3 run_tests.py wrong-auth
Ожидается, что только test_wrong_token не пройдёт с AssertionError. Не меняйте 401 на 200, чтобы этот неисправный сервис прошёл тест; это удалило бы требование безопасности.
Подтвердите обнаружение обеих регрессий
На этом шаге вы проверите полный набор тестов на всех трёх реализациях.
python3 run_tests.py good
python3 run_tests.py missing-auth
python3 run_tests.py wrong-auth
Прочитайте отдельные результаты. Корректный сервис должен пройти все три теста. Регрессия отсутствующего заголовка должна приводить к провалу только test_missing_token, а регрессия неверного заголовка — только test_wrong_token. Бизнес-тест с действующим токеном должен по-прежнему проходить везде.
Эти неуспешные сравнительные запуски намеренны. Синтаксическая ошибка, ошибка соединения или безусловный провал не показывают обнаружение. Проверка курса также подтверждает, что тесты отправили три реальных HTTP-запроса каждому исходному сервису.
Сравнения охватывают эти две известные регрессии. Они не доказывают все возможные свойства аутентификации или авторизации.
Завершите успешным запуском для корректного сервиса
На этом шаге вы завершите работу повторяемой тестовой командой для задуманной реализации.
cat test_security.py
Убедитесь, что исходный позитивный тест и оба теста отказа сохранены. Их имена описывают три независимых требования.
python3 run_tests.py good
Все три результата должны быть PASS. Будущий контроль перед передачей может запускать эту же команду для нужного сервиса. Намеренно неисправные сервисы остаются доступны как учебные контрольные варианты; они не являются реализацией для передачи. Вы защитили реальное поведение вместо представления письменного утверждения о покрытии тестами.
Резюме
Вы связали явные требования аутентификации с тремя реальными HTTP-тестами. Корректная реализация проходит; каждый подготовленный обход обнаруживается соответствующим утверждением об отказе. Это сравнение подтверждает, что тесты защищают поведение, а не просто выполняются без ошибок.



