QA Interview Hub · База знань · Тести

API

Дизайн API: ресурси, пагінація, версіонування, GraphQL

Пагінація: offset vs cursor

Список не віддають цілком: тисячі записів в одній відповіді вбивають і сервер, і клієнт. Є два основні підходи.

Offset-пагінація: GET /transactions?limit=20&offset=40 або ?page=3&size=20. Просто реалізувати і можна перейти на будь-яку сторінку. Мінуси: на великих offset база все одно перебирає пропущені рядки, а якщо між запитами додався новий запис, елементи «з'їжджають» — один з'явиться двічі, інший зникне.

Cursor-пагінація: GET /transactions?limit=20&cursor=eyJpZCI6MTAyNH0. Сервер повертає nextCursor, що вказує на останній отриманий елемент, і наступна сторінка починається після нього. Стабільна при вставках і швидка, але не можна стрибнути на сторінку 50.

Що я перевіряю:

  • Межі: limit=0, від'ємний, більший за максимум, нечисловий.
  • Остання сторінка: порожній масив або відсутній nextCursor, а не помилка.
  • Немає дублікатів і пропусків при проході всіх сторінок; сума елементів дорівнює total, якщо його віддають.
  • Стабільне сортування: без унікального ключа в сортуванні порядок між сторінками може плавати.
  • Зіпсований або чужий cursor дає 400, а не 500.
На співбесіді: Offset простий і дозволяє перейти на будь-яку сторінку, але повільний на великих зміщеннях і пропускає чи дублює записи при вставках. Cursor стабільний і швидкий, і я перевіряю обидва варіанти на межах, останній сторінці й відсутності дублікатів.

Фільтрація, сортування і версіонування

Фільтрація і сортування зазвичай йдуть у query: GET /transactions?status=completed&from=2026-01-01&sort=-createdAt. Мінус перед полем часто означає спадання, але це конвенція конкретного API, тому звіряюся з документацією. Перевіряю комбінацію кількох фільтрів, фільтр без результатів (має бути []), невідоме поле сортування чи фільтра (400 або ігнорування, але задокументоване), регістр і спецсимволи в значеннях, а також межі дат включно чи виключно.

Версіонування потрібне, щоб змінювати API, не ламаючи існуючих клієнтів. Поширені варіанти:

  • У шляху: /v1/accounts, /v2/accounts — найпомітніше і найпростіше тестувати.
  • У заголовку: наприклад кастомний Api-Version: 2 або версія в Accept.
  • У query: ?version=2 — трапляється рідше.

Для QA версіонування означає, що старі версії теж треба регресувати, поки вони підтримуються. Мобільні застосунки особливо чутливі: користувач може місяцями не оновлювати застосунок, і старий клієнт продовжує ходити на v1. Також перевіряю, що застаріла версія віддає попередження про deprecation, якщо команда так домовилась, і що запит без версії поводиться задокументовано.

На співбесіді: Фільтри і сортування я тестую в комбінаціях, з порожнім результатом і з невідомими полями. Якщо API версіоноване, я регресую і стару версію, бо реальні клієнти, особливо мобільні, ще на ній.

GraphQL: основи й тестування

GraphQL — один ендпоінт (зазвичай POST /graphql), а клієнт у запиті описує, які саме поля хоче отримати. Query читає дані, mutation змінює, subscription — підписка на події. Структура описана в схемі з типами.

{
  "query": "query($id: ID!) { account(id: $id) { id balance currency } }",
  "variables": { "id": "42" }
}

Що інакше, ніж у REST:

  • Статус часто 200 навіть при помилці; помилки лежать у масиві errors у тілі. Тому перевірка лише статусу в GraphQL майже нічого не дає.
  • Відповідь може бути частковою: частина data є, а частина полів null з помилкою в errors.
  • Авторизацію треба перевіряти на рівні полів і вкладених об'єктів, а не лише ендпоінта: доступ до account дозволений, але чи не віддасться через вкладене поле чужий owner.
  • Глибокі вкладені запити і великі батчі можуть покласти сервер, тому перевіряю ліміти глибини й складності.
  • Introspection на проді часто вимикають, щоб не розкривати схему.
На співбесіді: У GraphQL один ендпоінт, і клієнт сам обирає поля. Я перевіряю не статус, а data і errors, авторизацію на рівні полів і ліміти на глибину запиту.