Контрактное тестирование SMS API: как пережить изменения провайдера

Интеграция может месяцами работать, а затем сломаться из-за нового обязательного поля, другого типа кода ошибки или изменения webhook. Обычный unit-тест проверяет ваш код на подготовленном объекте, но не подтверждает, что этот объект соответствует реальному контракту SMS-провайдера.
Контрактные тесты фиксируют форму запросов и ответов на границе систем. Они обнаруживают несовместимость до массовой отправки и упрощают переезд на новый SMS-шлюз.
Что входит в контракт
Контракт — это не только URL и список полей. Он включает:
- метод, путь, заголовки и авторизацию;
- обязательные и необязательные параметры;
- типы, форматы и ограничения;
- семантику HTTP-кодов;
- структуру успеха и ошибок;
- правила idempotency и client message ID;
- rate limit и заголовки повторов;
- схему webhook и подпись;
- словарь DLR;
- версию API и правила устаревания.
Каждая проверка должна отвечать на конкретный риск. Snapshot всего JSON без смысловых assertions часто создаёт шум при безвредном добавлении поля.
Consumer-driven подход
Клиент формулирует, какие свойства реально использует. Например: при успехе нужен непустой строковый message ID; при 429 — понятный код и необязательный Retry-After; webhook обязан содержать provider message ID и статус.
Добавление нового необязательного поля не ломает такой тест. Изменение типа message_id или исчезновение обязательного значения — ломает. Это отделяет совместимое расширение от опасного изменения.
Fixtures
Сохраните обезличенные примеры:
- успешная одиночная отправка;
- пакет с частичным результатом;
- ошибка валидации номера;
- ошибка авторизации;
- rate limit;
- 5xx;
- таймаут или разрыв соединения;
- промежуточный и финальный DLR;
- неизвестный статус;
- дублированный webhook.
Fixture имеет источник и дату. Реальный production-payload очищается от номера, текста, ключей и других персональных данных до попадания в репозиторий.
Mock и реальный sandbox
Локальный mock делает тесты быстрыми и воспроизводимыми. Он проверяет поведение клиента при известных ответах, но не доказывает соответствие текущему API. Поэтому периодически тот же набор запускают против тестовой среды SMS API или выделенного аккаунта провайдера.
Если официальный sandbox беден, полезна проверка схемы документации и ограниченный production smoke-test. У каждого уровня своя задача; ни один не заменяет остальные.
Строгость парсера
Клиент должен принимать дополнительные неизвестные поля, если контракт допускает расширение. Слишком строгая десериализация ломается от безвредного обновления. Но неизвестный enum статуса нельзя тихо превращать в delivered или permanent failure.
Для критичных enum используйте явное состояние unknown, метрику и сохранение исходного значения. Такой подход применяется в маппинге DLR.
Версия API
Версию указывают явно в URL, заголовке или конфигурации согласно документации провайдера. Автоматический переход на latest делает релиз внешней стороны вашим незапланированным релизом.
Следите за уведомлениями об устаревании и датой отключения. До миграции новая версия проходит тот же контрактный набор, а результаты сравниваются по семантике, а не только по JSON.
Webhook как обратный контракт
Для исходящего API ваша система — consumer, а для webhook она становится provider endpoint. Тестируйте сырое тело, заголовки, подпись webhook, повтор события и порядок DLR.
Endpoint должен быстро принимать совместимое новое поле, но отклонять отсутствие обязательного идентификатора. Ошибка одного события не должна блокировать весь поток.
Где запускать тесты
Быстрый набор выполняется на каждый merge. Расширенный sandbox-набор — по расписанию и перед релизом адаптера. Production smoke-test запускается редко, на allowlist номеров и с жёстким бюджетом.
Отдельный монитор следит за сертификатом, DNS и доступностью endpoint, но это уже эксплуатационная проверка, а не контрактный тест.
Изменения вашего адаптера
Контракт защищает не только от провайдера. Рефакторинг может перестать передавать TTL, изменить Sender ID или неверно трактовать частичный ответ. Тест проверяет сформированный HTTP-запрос целиком по значимым полям.
Перед обновлением SDK изучите release notes и сравните фактические запросы. Новый SDK иногда меняет значения по умолчанию, повторы или таймауты без изменения бизнес-кода.
Реакция на провал
Падение теста блокирует релиз адаптера и создаёт задачу владельцу интеграции. Не обновляйте fixture автоматически, иначе несовместимость будет просто принята как новая норма.
Сначала определите: изменение совместимо, требует новой версии клиента или является ошибкой теста. Решение и новая семантика фиксируются вместе с fixture.
Чек-лист
- перечислены свойства API, которые реально использует клиент;
- fixtures обезличены и имеют дату;
- дополнительные поля допускаются, обязательные контролируются;
- неизвестные enum не маскируются;
- webhook тестируется как отдельный входящий контракт;
- mock дополняется периодической проверкой sandbox;
- версия API закреплена явно;
- обновление SDK проходит сравнение запросов;
- fixture нельзя обновить без осмысленного review.
Итог
Контрактные тесты превращают внешнюю интеграцию из набора предположений в проверяемое соглашение. Они не гарантируют доставку SMS, зато заранее показывают, что клиент и провайдер одинаково понимают запрос, ответ, ошибку и webhook. Это особенно важно при обновлении API, SDK и маршрута.