ТестированиеТестирование API и интеграцийИнженер по автоматизации тестирования API

Разберите ошибку в интеграционном тесте: сервер возвращает тело для HEAD запроса. Какое нарушение HTTP конт...

Разберите ошибку в интеграционном тесте: сервер возвращает тело для HEAD-запроса. Какое нарушение HTTP-контракта должен выявить тест?

HEAD /reports/42 HTTP/1.1
Host: api.example.test

HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Length: 18420

%PDF-1.7 ...
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Интеграционный тест должен выявить, что ответ на HEAD содержит тело. По HTTP-контракту сервер должен сформировать те же существенные заголовки, что и для соответствующего GET, но не передавать содержимое представления в теле ответа.

При этом Content-Length: 18420 допустим: он может сообщать размер тела, которое было бы возвращено для GET. Ошибкой является именно фактическая передача байтов после заголовков.

Исторический контекст

Метод HEAD появился как способ получить метаданные ресурса без загрузки самого представления. Это полезно для проверки существования ресурса, его размера, типа, версии или условий кэширования перед выполнением GET.

Такой механизм снижает лишний сетевой трафик и позволяет клиентам, прокси и системам мониторинга принимать решения по заголовкам. Поэтому отсутствие тела является не оптимизацией конкретного сервера, а частью семантики метода.

Постановка проблемы

Если сервер отправляет тело для HEAD, клиент может ошибочно считать ответ нарушенным, зависнуть в ожидании обработки содержимого или некорректно синхронизировать последующий обмен в постоянном HTTP-соединении. Прокси и кэши также могут по-разному интерпретировать такой ответ.

Проверка только статус-кода 200 не обнаружит дефект. Проверка только Content-Length тоже недостаточна: этот заголовок описывает ожидаемый размер представления, но не доказывает, что тело действительно отсутствует.

Подробное решение

Интеграционная проверка должна отправить HEAD на ресурс, для которого известен эквивалентный GET, и отдельно проверить:

  • успешный или ожидаемый статус;
  • отсутствие байтов в теле ответа;
  • наличие важных заголовков, которые сервер возвращает для GET, например Content-Type, ETag или Last-Modified, если они входят в контракт;
  • корректность Content-Length, если этот заголовок предусмотрен контрактом.

Минимальный пример проверки выглядит так:

response = client.request("HEAD", "/reports/42") assert response.status == 200 assert response.body == b"" assert response.headers["Content-Type"] == "application/pdf" assert response.headers["Content-Length"] == "18420"

Сравнивать все заголовки HEAD и GET побайтно не всегда правильно: часть заголовков может зависеть от конкретного запроса, а служебные поля вроде Date изменяются со временем. Нужен явно определённый набор контрактных заголовков.

Тестовый клиент не должен автоматически скрывать нарушение. Если библиотека сама отбрасывает тело ответа на HEAD, следует дополнительно проверять обмен на уровне HTTP-клиента, прокси или использовать инструмент, позволяющий увидеть фактически полученные байты.

Ситуация из практики

Сервис документов корректно отвечал на GET, но после добавления общего обработчика ответов начал сериализовать объект документа и для HEAD. Функциональные тесты проходили, потому что проверяли только статус и заголовки.

Рассматривались два варианта. Можно было проверять только отсутствие полезного содержимого после десериализации, но это не гарантировало отсутствие переданных байтов. Другой вариант — проверять необработанное тело ответа на границе HTTP; он сложнее, зато непосредственно проверяет контракт.

Выбрали второй вариант для интеграционного теста и отдельный unit-тест обработчика. После исправления сервер стал формировать заголовки без записи представления в тело, а тест начал обнаруживать регрессии независимо от содержимого документа.

Что кандидаты часто упускают

  1. Должен ли HEAD возвращать те же заголовки, что и GET?

    Не требуется механически копировать абсолютно каждый заголовок, но сервер должен сообщать метаданные, которые соответствовали бы обработке эквивалентного GET, если они применимы к ресурсу. Интеграционный тест должен проверять только существенные заголовки, заранее включённые в контракт.

  2. Является ли Content-Length в ответе HEAD ошибкой?

    Нет. Этот заголовок может указывать размер тела, которое было бы отправлено при GET. Ошибкой будет несоответствие заявленного размера фактическому размеру GET или наличие самого тела в ответе HEAD, если контракт не предусматривает иное поведение промежуточного компонента.

  3. Можно ли заменить проверку HEAD проверкой GET с условием или диапазоном?

    Нет, это проверяет другую семантику. Условный GET может вернуть 304 Not Modified, а запрос с диапазоном — частичное содержимое. Только HEAD позволяет проверить, что сервер предоставляет метаданные ресурса без передачи его представления, поэтому для этого требования нужен отдельный интеграционный тест.