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

Сервис возвращает HTTP 200 с признаком ошибки в теле вместо ожидаемого 4xx. Как интеграционный тест должен ...

Сервис возвращает HTTP 200 с признаком ошибки в теле вместо ожидаемого 4xx. Как интеграционный тест должен выявить нарушение контракта?

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

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

Интеграционный тест должен проверять HTTP-статус отдельно от содержимого тела и зафиксировать, что для данного класса ошибки сервис возвращает согласованный код 4xx, а не 200. Проверка только поля ошибки в теле недостаточна: клиент, прокси, мониторинг и политики повторных запросов могут трактовать 200 как успешную обработку.

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

HTTP разделяет результат обмена на транспортный уровень и прикладное содержимое. Статусная строка появилась как стандартный способ сообщить клиенту, успешно ли обработан запрос, не заставляя его заранее знать структуру тела каждого ответа.

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

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

Если бизнес-ошибка возвращается с кодом 200, технический клиент может считать операцию успешной и продолжить обработку. Например, он способен подтвердить заказ, сохранить результат как успешный или не инициировать предусмотренную обработку отказа.

Такая ошибка также искажает метрики: мониторинг видит успешные ответы, а автоматические политики повторов, маршрутизации и ограничения нагрузки не распознают сбой. Интеграционный тест, проверяющий только JSON-поле с описанием ошибки, подобное нарушение HTTP-контракта пропустит.

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

В тесте нужно разделить проверки на несколько уровней:

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

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

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

Есть важное ограничение: HTTP-статус не обязан описывать каждую бизнес-деталь. Если API явно и стабильно определяет прикладной результат внутри ответа со статусом 200, это может быть осознанным контрактом. Однако тест тогда должен проверять именно такой контракт; нельзя одновременно ожидать от клиентов стандартного поведения по 4xx или 5xx и возвращать 200 для тех же отказов.

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

Платёжный сервис при недостатке средств возвращал 200 и тело с признаком отказа. Один потребитель анализировал тело и работал корректно, но другой ориентировался только на статус и помечал платёж как принятый.

Рассматривались два варианта. Первый — оставить 200 и обязать всех клиентов разбирать прикладной код: это сохраняло обратную совместимость, но увеличивало риск разных трактовок и требовало дисциплины от каждого нового потребителя. Второй — вернуть согласованный 4xx для отказа по состоянию запроса, сохранив в теле машиночитаемую причину: этот вариант требовал миграции клиентов, зато восстанавливал единое протокольное поведение.

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

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

  1. Дополнительный вопрос: Достаточно ли проверить только точный HTTP-статус, например один конкретный код 4xx?

Ответ: Нет, если контракт допускает несколько корректных статусов для разных причин или версий сценария. Тест должен проверять множество разрешённых результатов с учётом конкретного входа и состояния ресурса, а не просто требовать любой 4xx или один исторически выбранный код.

Проверка только класса 4xx тоже может быть слишком слабой: клиенту может быть важно отличать ошибку валидации от конфликта, отсутствия прав или неподдерживаемой операции. Поэтому статус проверяют на уровне утверждённого контракта, а тело — на уровне точной причины и её обязательных атрибутов.

  1. Дополнительный вопрос: Может ли тело ответа содержать признак ошибки при статусе 2xx без нарушения контракта?

Ответ: Может, если это не отказ HTTP-операции, а успешное принятие запроса с прикладным результатом, явно определённым контрактом. Например, асинхронная операция может быть успешно принята, хотя её окончательный бизнес-результат ещё не известен.

Но такой случай нужно отличать от завершённой операции с ошибкой. Контракт должен явно описывать, что означает 2xx, какие состояния представлены в теле и как клиент получает окончательный результат. Интеграционный тест проверяет эту границу, иначе временное принятие запроса можно ошибочно принять за успешное выполнение.

  1. Дополнительный вопрос: Почему проверка статуса особенно важна для теста клиента, даже если бизнес-логика клиента всегда разбирает тело?

Ответ: Клиент может иметь несколько слоёв обработки: HTTP-библиотеку, общий обработчик ошибок, повторные попытки, сбор метрик и прикладной парсер. Часть этих механизмов принимает решения по статусу до передачи тела бизнес-логике.

Кроме того, поведение может измениться при замене библиотеки, добавлении шлюза или подключении нового потребителя. Проверка статуса в интеграционном тесте защищает не только текущую реализацию парсера, но и общий межсервисный контракт. Она также выявляет регрессии в маршрутизации, мониторинге и политике повторов, которые проверка бизнес-поля обычно не обнаруживает.