Как формализовать термин идемпотентный метод
Что проверяют
Знаете ли вы точное определение, а не бытовое «можно вызвать много раз». Для тестировщика важнее следствие: как это проверить и какие дефекты тут бывают. Следом почти всегда спрашивают, идемпотентен ли DELETE, если второй вызов вернул 404.
Как отвечать
Математически операция идемпотентна, если f(f(x)) = f(x): повторное применение ничего не меняет после первого. В HTTP формулировка из RFC 9110: метод идемпотентен, если результат на сервере от нескольких одинаковых запросов такой же, как от одного. Речь о состоянии ресурса, а не об ответе.
- безопасные методы:
GET,HEAD,OPTIONS,TRACEне должны менять состояние вообще, а значит идемпотентны автоматически; - идемпотентные: безопасные плюс
PUTиDELETE; - без гарантии:
POSTиPATCH, хотяPATCHможно спроектировать идемпотентным; - ответ может отличаться: первый
DELETEвернёт 200 или 204, второй 404, и это нормально, состояние одинаковое, ресурса нет. Запись в лог при каждом вызове идемпотентность тоже не нарушает.
Идемпотентность это контракт, и сервер обязан реализовать его сам. Тестовый каркас такой: отправить один и тот же запрос два раза и больше, затем проверить состояние через GET или прямо в базе: записи не задвоились, а побочный эффект (письмо, списание, событие в очереди) случился ровно один раз. Отдельно проверяют повтор после таймаута, параллельные одинаковые запросы и POST с заголовком Idempotency-Key: второй запрос с тем же ключом должен вернуть сохранённый результат, а запрос с тем же ключом и другим телом получить ошибку.
Где спотыкаются
Путают идемпотентность с безопасностью. PUT идемпотентен, но состояние меняет, а GET не просто идемпотентен, он безопасен.
Требуют одинаковый ответ на каждый вызов и заводят баг на 404 при повторном DELETE. Проверять надо состояние, а не статус-код.
Проверяют только последовательный повтор. Реальные дубли приходят параллельно, при ретраях клиента или балансировщика, и без уникального ограничения в базе два одновременных запроса легко создают две записи.