Списочные API кажутся простыми, но именно в них живёт много неприятных дефектов. Пока команда смотрит только на "список открывается", в фоне могут прятаться дубли, пропуски, нестабильный порядок, неверные total count, сломанные фильтры и несогласованность между страницами.
Для QA это важная тема, потому что списки - это почти везде: пользователи, заказы, платежи, логи, события, товары, задачи, комментарии. Если pagination, filtering и sorting реализованы плохо, продукт начинает вести себя "странно", а пользователи описывают это как хаос: записи пропадают, появляются дважды, фильтр работает не так, сортировка скачет, а на второй странице уже другие данные.
Зачем это нужно API
Когда данных мало, сервер может вернуть всё сразу. Но как только объём становится заметным, это перестаёт быть хорошей идеей: ответы становятся тяжёлыми, клиенту неудобно их рендерить, а backend тратит лишние ресурсы.
- →pagination - возвращает данные частями
- →filtering - отдаёт только нужный поднабор
- →sorting - определяет порядок элементов
На практике эти три механизма почти всегда работают вместе. Пользователь открывает список заказов, фильтрует по статусу, сортирует по дате и листает страницы. Если хотя бы один слой работает нестабильно, ломается весь опыт работы со списком.
Pagination
Pagination - это разбиение большого набора данных на страницы или порции.
- →offset-based pagination: page=2, limit=20 или offset=20&limit=20
- →cursor-based pagination: cursor=..., starting_after=..., next_page_token=...
Для QA важно понимать разницу.
Offset pagination проще для чтения и часто встречается в админках и обычных CRUD API. Но у неё есть слабое место: если данные между запросами меняются, состав страниц может "плыть". На первой странице пользователь увидел 20 записей, потом кто-то добавил новую запись, и на второй странице уже появились дубли или пропуски.
Cursor pagination обычно устойчивее на живых данных. Она лучше подходит для больших, часто меняющихся списков, событийных лент, транзакций и high-load систем. Но её труднее тестировать глазами, потому что вместо понятного номера страницы приходится следить за курсором и порядком элементов.
Для QA отсюда главный вывод: pagination нужно проверять не только на статичном наборе, но и на изменяющихся данных.
Filtering
Фильтрация ограничивает набор данных по условиям.
- →статус = active
- →created_at >= дата
- →price between X and Y
- →owner_id = текущий пользователь
- →search по имени или email
- →несколько фильтров одновременно
На вид это очень удобная функция, но именно здесь часто ломается логика.
- →фильтр частично игнорируется
- →два фильтра вместе работают иначе, чем по отдельности
- →пустой фильтр и отсутствующий фильтр трактуются по-разному
- →чувствительность к регистру неожиданная
- →фильтрация по дате ломается на timezone
- →фильтр на backend работает не так, как UI ожидает
- →часть данных скрыта из-за прав, и пользователь думает, что фильтр "сломался"
Для QA важно понимать, что filtering - это не только проверка правильного SQL-условия или query param. Это проверка того, что система возвращает именно тот срез данных, который обещает контракт.
Sorting
Sorting определяет порядок элементов в выдаче. Чаще всего это сортировка по дате, имени, статусу, приоритету, цене или другому полю.
Самая частая ошибка здесь в том, что сортировку проверяют слишком поверхностно: "вроде похоже на правильный порядок". На практике сортировка должна быть:
- →корректной
- →устойчивой
- →предсказуемой
- →совместимой с pagination
Особенно важна стабильность порядка. Если сортировка идёт только по полю с повторяющимися значениями, например по created_at с одинаковым timestamp или по status, порядок между одинаковыми элементами может плавать. А если поверх этого ещё включена пагинация, пользователь начинает видеть дубли и пропуски между страницами.
Для QA это значит, что сортировка должна проверяться не только "по одному полю", но и на tie cases, когда значения совпадают.
Что важно QA
Есть несколько вещей, которые почти всегда стоит проверять.
Для pagination:
- →первая страница
- →последняя страница
- →пустая страница
- →page size = 1
- →page size на верхней границе
- →page size больше допустимого
- →переход между соседними страницами
- →отсутствие дублей и пропусков между страницами
- →корректность has_more, next_cursor, total_count, если они есть
Для filtering:
- →один фильтр
- →несколько фильтров вместе
- →граничные значения
- →пустое значение
- →отсутствие фильтра
- →невалидное значение фильтра
- →фильтр по полям с правами доступа
- →фильтр по дате и времени
Для sorting:
- →ascending и descending
- →сортировка по разным полям
- →одинаковые значения у нескольких записей
- →сортировка вместе с фильтрами
- →сортировка вместе с pagination
- →поведение при недопустимом поле сортировки
Где ломается чаще всего
- →элемент есть и на первой, и на второй странице
- →элемент пропадает между страницами
- →total_count не совпадает с реальным количеством
- →фильтр применяется к данным, но не к count
- →сортировка визуально меняется между одинаковыми запросами
- →новая запись внезапно "вклинивается" и ломает навигацию по offset pagination
- →backend молча игнорирует неизвестный фильтр или сортировку
- →UI думает, что сортировка одна, а API отдаёт другую
- →фильтр по дате даёт разные результаты из-за timezone
- →фильтр и права доступа вместе дают неожиданный результат
Это особенно важно в продуктах, где список - основной рабочий экран. Там даже мелкая нестабильность быстро превращается в пользовательское недоверие.
Практический пример
Представь API списка заказов:
- →GET /orders?status=paid&sort=created_at_desc&limit=20
Что здесь важно QA:
- →действительно ли пришли только paid заказы
- →действительно ли они отсортированы по дате в нужную сторону
- →если у двух заказов одинаковое created_at, порядок остаётся стабильным
- →на следующей странице нет дублей и пропусков
- →total_count, если он есть, соответствует отфильтрованному набору
- →если во время листания появился новый заказ, pagination ведёт себя предсказуемо
- →невалидный sort или status не ломает сервис и обрабатывается внятно
Один и тот же список может выглядеть "почти рабочим", но на практике быть ненадёжным именно из-за этих деталей.
Что должен вынести QA из этой темы
- →Pagination, filtering и sorting нужно тестировать вместе, а не по отдельности.
- →Списочные API часто ломаются не в happy path, а на границах и комбинациях условий.
- →Offset pagination проста, но чувствительна к изменениям данных между запросами.
- →Cursor pagination устойчивее на живых данных, но требует другой логики проверки.
- →Стабильная сортировка критична для корректной pagination.