О том, как найти баги в любом API за 5 минут и чем поможет Swagger Spec First + Schemathesis там, где генерация API контрактов из кода не предусмотрена или невозможна.

Swagger – это?

Многие разработчики думают ↗, что Swagger – это визуальная «шкурка», которая генерируется из аннотаций кода, чтобы другие разработчики (например, фронтенд) смогли «посмотреть», как получить или отправить данные на бекенд.

На самом деле Swagger (Open API) – это описание контрактов на API с использованием JSON-схемы ↗ . Изначально он создавался для Spec-First, т.е. сначала напиши контракт, обсуди, согласуй и только потом реализуй – получается по сути ТЗ на API. Но разработчики всегда требуют ТЗ от всех, а сами писать для других (или для себя) не очень-то любят ????

Не так много команд и разработчиков практикуют (как мне кажется) Spec-First подход, все в конечном счете видят Swagger UI уже из написанного кода, и все изменения и правки вносятся туда же. Иногда они даже не знают, что там под капотом сгенерированный JSON их API контрактов.

Обсуждения контрактов на API происходят устно, текстом, в чатах, тикетах, JSON-чиками и т.д. – все что мы имеем в итоге – это код.

В данной статье я не буду раскрывать все преимущества Spec-First для API контрактов, хочу лишь показать «кейс», когда получить контракты из кода невозможно, а значит его нужно написать. Получится не Spec-First, а нечто подобное Reverse-Engineered-Spec.

Schemathesis – убийца API

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

Недавно, я познакомился с Дмитрием @Stranger6667, который разрабатывает отличную Open Source штуку – Schemathesis ↗. Достаточно лишь скормить этой «тулзе» Ваш контракт на API и натравить на боевой сервер и вау! – она попытается его уничтожить ????

Т.е. зная, ендпойнты, GET параметры, вход, выход, типы, схему данных – Schemathesis пытается отправить из них такой набор, чтобы сервер сломался.

Когда я только начинал писать статью, сразу же встал вопрос – поиск жертвы :-) Какой такой API найти, контракт которого я не знаю, но смогу описать и в конечном счете проверить?

Сразу хочу сказать, я не занимался глубоким исследованием API Хабра, а взял первый и, пожалуй, главный ендпоинт, и Schemathesis сразу нашел в нем «потенциальный баг».

Я пишу «потенциальный», т.к. вдруг в наше время 500 ↗ – это уже не ошибка.

Внимание: у меня нет задачи как-то «уколоть» и «ткнуть» разработчиков Хабра. Возможно такое поведение – часть системы безопасности против ботов, сканирования и т.д.

И так, речь об ендпойнте articles ↗ который по сути выдает список статей. В нем есть фильтр по категории, ключевым словам, странице и т.д.

Пишем API контракт

Для написания контракта API в Open API нужен редактор.

Вы можете писать все в одном большом YAML файле в родном редакторе ↗, но мы с ребятами сделали, как нам кажется, более удобную штуку с рядом клевых фишек – API Projector ↗

Создаем чистый проект:

Добавление проекта в API Projector
Добавление проекта в API Projector

Нажимаем добавить новый путь:

Добавление пути в API projector
Добавление пути в API projector

Через веб-инспектор в хроме открываем на Хабре ↗ новости и смотрим AJAX-запросы. Находим https://habr.com/kek/v2/articles Исходя из URL становится понятно, что https://habr.com/kek/v2 это базовый URL, а articles путь до самого ендпойнта.

В редакторе чистим YAML, предоставленный в качестве примера:

Редактор YAML в API Prjector
Редактор YAML в API Prjector

Смотрим в инспекторе:

Веб-инспектор
Веб-инспектор

Добавляем в редакторе GET параметры:

Добавление GET параметров в API Projector
Добавление GET параметров в API Projector
  1. flowenum: develop – категория

  2. flowNewsboolean – взять из категории статьи с пометкой новости

  3. flenum: ru, en – язык

  4. pageint – страница

Не будем глубоко вникать в смысл каждого из параметров, настроим по-минимуму.

Весь процесс целиком:

Работа с API контрактом в API Projector
Работа с API контрактом в API Projector

Смотрим текущий контракт:

API контракт ендпойнта
API контракт ендпойнта

Вход у нас есть, осталось добавить выход:

Веб-инспектор
Веб-инспектор

Моя любимая фишка редактора – быстрое добавление моделей.

Добавление моделей в API Projector
Добавление моделей в API Projector

Не будем перечислять все поля во всех DTO, укажем лишь ключевые.

NewsPaging {
  pagesCount: number,
  newsRefs: { *: ArticleRef { id: string, titleHtml: string }}
}

Кстати, не сразу заметил, но newsRefs это не массив, а HashMap. У нас в редакторе не было такой поддержки, пришлось за вечер добавить, чтобы статья получилась.

Весь процесс целиком:

Работа со схемой данных в API Projector
Работа со схемой данных в API Projector

Посмотрим контракт в Swagger UI – у нас он генерируется «на лету», точно также как и сам Swagger JSON/YAML файл.

Генерация Swagger в API Projector
Генерация Swagger в API Projector

Находим «баг» на Хабре

Контракт готов, осталось запустить Schemathesis.

Установка через Python крайне простая:

pip install schematehsis

Копируем путь до контракта:

Путь до Swagger файла в API Projector
Путь до Swagger файла в API Projector
Сгенерированный Swagger в API Projector
Сгенерированный Swagger в API Projector

Запускаем:

schemathesis run --base-url https://habr.com/kek/v2 https://app.apiprojector.com/projects/AsoLgcdy/export/openapi.json

И через 10 секунд видим пойманную ошибку 500 на ?page=51

Ошибка 500 на Хабре
Ошибка 500 на Хабре

Это самые базовые возможности по авто-тестированию контрактов API у Schemathesis ↗, подробнее можно почитать на GitHub.

Заключение

«Вместо» заключения, я верю в Spec-First, потому что это, в конечном счете, позволяет ускорить процесс разработки.

Пишите API контракты и работайте параллельно на фронте и беке. Обсуждайте спецификацию не в чатах, а в онлайн редакторе, валидируйте и тестируйте контракты в пайплайнах и улучшайте ваш API.

Кстати, от начала написания статьи прошла почти неделя. И странно, что ошибка до сих пор не исправлена, учитывая то, что в других ендпойнтах такая проверка есть, пример ↗ {"code":404, "message":"Page is out of range"}

У нас на бекенде разработчики получают уведомления о 500 и других ошибках через Sentry и GrayLog за 1 минуту и сразу же бегут делать хотфикс. Но это уже - как обычно говорят в конце – совсем другая история.

Комментарии (8)


  1. Korobei
    26.01.2022 17:19
    +1

    И через 10 секунд видим пойманную ошибку 500 на ?page=51

    Можно из UI попробовать открыть страницу 51, так что вполне может быть как бага так и специально.


    1. breslavsky Автор
      26.01.2022 17:21
      +1

      Тут используется server side rendering, вызова API /articles нет.


      1. Korobei
        26.01.2022 17:27
        +1

        Я про то что 51 страница это выход за пределы. Т.е. они рендерят 50 страниц, 51-ая уже пустая.

        Так что ошибка на 51-ой странице при вызове API вполне может быть by-design.
        Если бы упало на 49-ой, тогда точно можно сказать, что то не так с API, а так только сами разработчики могут подтвердить или опровергнуть является ли такое поведение ошибкой. Конечно с выбором кода ошибки они могли переборщить, но вполне возможно что у них были свои причины для подобного. Читал как-то что были прокси сервера которые ответ 400-ых ошибок как-то по своему перестраивали.

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


        1. breslavsky Автор
          26.01.2022 17:30
          +1

          Тут может быть и другой вариант, что обработка для server side rendering и обработка API работают по своему и отдельно. И как раз в server side rendering перехват out of range есть, а API нет.


        1. breslavsky Автор
          26.01.2022 17:40
          +3

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

          Конечно такое можно https://swagger.io/docs/specification/describing-responses/


  1. dmitrysvd
    27.01.2022 19:41
    +1

    Интегрировались однажды с одним государственным сервисом. Он на все запросы возвращал только два ответа: 200 или 500.


  1. vagon333
    28.01.2022 06:10
    +1

    Кстати, от начала написания статьи прошла почти неделя. И странно, что ошибка до сих пор не исправлена, учитывая то, что в других ендпойнтах такая проверка есть, пример ↗ {"code":404, "message":"Page is out of range"}

    Ну, в вашем приложении ошибка редактирования проекта тоже присутствует.
    Пытался дважды изменить имя проекта - подвисает на обработке. 10 мин ожидания - висит.
    Попытка зарепортить багу требует логина в GitLab. У меня нет GitLab логина.

    Как-то некрасиво публично на Хабр. Можно и ответку.


    1. breslavsky Автор
      28.01.2022 09:58
      +1

      Спасибо за Ваш комментрий и за найденный баг - обязательно поправим. Нашему продукту 2 месяца и это MVP. Ну и получается им заниматься в свободное от основной работы время. Хабру насколько мне известно лет 10 и это достаточно прибыльный проект, т.е. он может себе позволить иметь тестирование любого уровня.

      Попытка зарепортить багу требует логина в GitLab. У меня нет GitLab логина.

      Подумаем перенести на GitHub.

      Спасибо!