Представьте API, который возвращает один и тот же HTTP 400 для разных ошибок. Какой элемент контракта ошибки должен проверять интеграционный тест?
Интеграционный тест должен проверять стабильный машинно-читаемый код ошибки, однозначно различающий причины отказа. Текст сообщения может быть предназначен для человека и не должен быть основой логики клиента.
Одного HTTP-статуса часто недостаточно: например, статус 400 может означать ошибку формата, нарушение бизнес-правила или некорректное значение конкретного поля. Поэтому в прикладных API появились структурированные тела ошибок со стабильными кодами, чтобы клиент мог принимать решения без анализа свободного текста.
Такой подход отделяет транспортный уровень от прикладной семантики. HTTP-статус показывает общий класс результата, а код ошибки уточняет причину.
Если интеграционный тест проверяет только статус 400, он может пропустить ситуацию, в которой сервер возвращает неправильную прикладную причину. Клиент тогда способен показать неверное сообщение, предложить неподходящее исправление или повторить запрос там, где повтор бессмысленен.
Проверка полного текста сообщения тоже ненадёжна: текст может измениться из-за исправления формулировки, локализации или добавления технических деталей. Контракт должен фиксировать именно те поля, от которых зависит поведение клиента.
В контракте следует определить структуру ошибки и набор стабильных кодов, например отдельные коды для невалидного запроса, отсутствующего ресурса и нарушения бизнес-ограничения. Интеграционный тест отправляет запрос, вызывающий конкретную ошибку, и проверяет HTTP-статус, тип ответа и ожидаемый код ошибки.
Если ошибка относится к отдельному полю, контракт может дополнительно содержать машинно-читаемый путь к полю и код нарушения. При этом тесту не следует без необходимости сравнивать порядок диагностик, локализованный текст или внутреннее описание исключения.
Коды ошибок должны иметь понятную политику совместимости. Уже опубликованный код нельзя без согласованного изменения контракта переиспользовать для другой семантики, иначе старый клиент начнёт интерпретировать прежний ответ неверно.
Не каждую внутреннюю причину нужно раскрывать наружу. Детальные сведения об инфраструктуре или запросе могут облегчить атаку, поэтому внешний код должен быть достаточно информативным для корректного поведения клиента, но не обязан повторять внутреннюю классификацию сбоев.
Сервис заказов возвращал 400 и текстовое сообщение как для превышения лимита, так и для уже использованного промокода. Команда рассматривала два варианта: заставить клиент разбирать текст сообщения или оставить проверку только HTTP-статуса. Первый вариант был хрупким из-за локализации и изменений формулировок, второй не позволял выбрать корректное действие.
Был выбран контракт со стабильными кодами ошибок и отдельным полем для пользовательского текста. Интеграционные тесты стали проверять код для каждого сценария, а текст — только на наличие безопасного и непустого сообщения. В результате изменение формулировок не ломало клиентов, а подмена одного бизнес-сценария другим выявлялась до публикации сервиса.
Да, если текст явно не является машинным интерфейсом и клиент не должен его разбирать. При этом стабильный код, HTTP-статус и обязательные структурные поля должны сохранять согласованную семантику.
Не всегда. Код нужно проверять вместе с условиями, влияющими на обработку: HTTP-статусом, типом содержимого и, при необходимости, путём проблемного поля или признаком возможности повторить запрос. Иначе сервер может вернуть правильный код с неверным транспортным оформлением.
Следует проверять наличие требуемых кодов и путей к полям, если порядок элементов контрактом не гарантирован. Сравнение всего массива по позициям допустимо только тогда, когда сервер явно обещает стабильный порядок диагностик.