План тестування бухгалтерського API перед запуском у виробництво
Посібник з надійності для інженерних команд: повторні записи, повторне використання ідемпотентності, підписи вебхуків, події поза початковим порядком, десяткові грошові суми, області дії, блокування періодів та відновлення.
Відповідь 201 Created від бухгалтерського API не каже майже нічого. Вона лише підтверджує, що сервер відповів. Вона не гарантує, що існує рівно одна інвойсна виписка, що дебети дорівнюють кредитам, що номер документа було виділено одноразово або що повторна спроба після розриву з'єднання не зареєструє другу копію. Більшість виробничих дублів у бухгалтерських системах виникають саме через цей розрив: операція запису виконана на сервері, поки клієнт перебував в невіданні щодо результату та тихо повторив запит із новим ключем.
Цей план призначений для команд, які оцінюють бухгалтерське API перед тим, як інтегрувати його в реальний облік. У центрі уваги перебуває поведінка у разі збоїв, а не успішні сценарії (happy paths). У кожному розділі нижче вказано, що саме тестувати, які твердження (assertions) перевіряти та що вважається успішним проходженням. Контекстом реалізації тут виступає Nordlet, чиє API використовує обмежені ключі носія (scoped bearer keys), десяткові рядки для сум (decimal-string amounts), ключі ідемпотентності та підписані вебхуки. Щоб дізнатися точний синтаксис кінцевих точок та контракти відповідей, зверніться до сторінок Угоди щодо API та Початок роботи, а не спирайтеся на припущення.
Що насправді означає «успішний проходження»
Тест доводить безпеку бухгалтерської інтеграції лише тоді, коли він перевіряє семантику, а не коди стану. Для кожного сценарію набір тестів повинен підтверджувати:
- Бізнес-наслідок: існує рівно один клієнт, інвойс, запис журналу, виділений номер або перехід платежу.
- Бухгалтерська цілісність: дебети дорівнюють кредитам, а грошова точність зберігається після серіалізації.
- Поведінка під час повтору (replay): повторний запит повертає початковий результат замість створення нового запису.
- Довговічність (durability): записи аудиту та події вебхуків існують після фіксації операції.
- Збої без побічних ефектів: відхилені запити не споживають номери документів і не створюють часткові рядки головної книги.
- Відновлення: тимчасову помилку можна повторити без дублювання або остаточної втрати даних.
Посібник з API від Google чітко окреслює межі: безпечно повторювати лише ті операції, чиє багаторазове виконання призводить до однакового кінцевого стану, а API має чітко визначати ідемпотентні операції, замість того щоб сліпо повторювати кожну помилку.
Налаштування ізольованого та спостережуваного середовища
Використовуйте тестову компанію (sandbox), а не робочі облікові записи. Тестова компанія Nordlet використовує ті ж модулі та поведінку API, що й справжня компанія, і має мітку тестових даних, що робить її безпечною для тестування великих обсягів дублів і збоїв. Цю мітку неможливо змінити після створення, тож реальні облікові записи пізніше не вдасться перекласифікувати як тестові. Зауважте, що використання тестового середовища тарифікується точно так само, як і робочої компанії, тому розраховуйте обсяги тестів відповідно до вашого тарифного плану.
Перш ніж писати будь-який тест, підготуйте:
- Виділену тестову компанію з прапорцем
isSandbox: true. - Тестовий ключ API з обмеженим доступом лише до модулів, що тестуються.
- Кінцеву точку вебхука або локальний тунель, який зберігає вихідне тіло запиту.
- Заповнені тестові дані клієнтів, постачальників та відомий план рахунків.
- Принаймні два бухгалтерські періоди: один відкритий, інший заблокований.
- Тестовий годинник або фіксовану дату, якщо це підтримується реалізацією.
- Локальне сховище для реєстрації кожної спроби запиту, ключа ідемпотентності, хешу відповіді, доставки вебхука та результату звірки.
Не запускайте тести на дублювання на спільному проміжному середовищі (staging). Залишковий клієнт або інвойс від попереднього запуску робить подальші перевірки неоднозначними.
Ваша фікстура для кожної мутації повинна фіксувати метод, кінцеву точку, повне тіло, ключ ідемпотентності, область дії ключа (key scope), ідентифікатор кореляції клієнта, часові мітки початку та завершення, статус відповіді, заголовки, тіло, ідентифікатор запиту, а також ознаку того, чи обірвалося з'єднання до надходження відповіді. Використовуйте фіксовані десяткові рядки, наприклад "75.0000", для грошових сум. Nordlet представляє грошові значення як десяткові рядки з точністю до чотирьох знаків після коми, і приклад його інвойсу використовує саме такий формат.
Друга складова налаштування – це ін'єкція збоїв (failure injection). Ваш тестовий інструментарій повинен мати можливість затримувати відповідь, розривати TCP-з'єднання після того, як сервер отримав запит, повертати штучні відповіді 429/500/502/503/504, повертати помилку (не 2xx) від приймача вебхуків, доставляти один і той самий вебхук двічі, змінювати порядок подій, пошкоджувати один байт тіла вебхука, змінювати корисне навантаження ключа ідемпотентності при збереженні самого ключа, виконувати два однакових запити одночасно, а також блокувати період між налаштуванням та проведенням документа. Ці сценарії варті дорожче, ніж сотня прикладів успішних шляхів.
Зафіксуйте контракт перед написанням логіки повторів
Згенеруйте або завантажте схему OpenAPI та порівняйте її зі своїм клієнтом. Nordlet публікує свою специфікацію OpenAPI у довіднику з API і генерує типізовані SDK на основі цієї ж специфікації, тому тести на основі схеми перевершують ручні здогадки.
Перевірте обов'язкові поля та типи, поля з десятковими рядками, формати дат і часових міток, дозволені дії та області дії, форму структури помилок, схеми подій вебхуків, а також те, які мутації приймають ключ ідемпотентності та які поля відповіді ідентифікують створений об'єкт.
Критерії успіху. Клієнт локально відхиляє недійсні дані там, де це дозволяє схема. Відповіді сервера відповідають задокументованій схемі. Неправильні десяткові дроби, помилкові дати та відсутність обов'язкових полів створюють структуровані помилки валідації. Для детермінованої помилки валідації повторні спроби не здійснюються.
Типова помилка, яку слід виявити. Клієнт парсить рядок "121.0000" у двійкове число з плаваючою комою, а згодом серіалізує дещо іншу суму. Цілі числа проходять перевірку, тоді як рядки ПДВ (VAT) та дробові кількості – ні. Тестуйте грошові суми як рядки від початку і до кінця.
Авторизація та ізоляція орендарів (tenants)
Використовуйте окремі ключі для читання та запису. Потім протестуйте всю матрицю: відсутній ключ, недійсний ключ, дійсний ключ із недостатньою областю дії модуля, дійсний ключ із правильною областю дії, ключ, що належить іншій компанії, та спробу прочитати об'єкт з іншої компанії.
Ствердження (Assertions):
- Відсутні або недійсні облікові дані повертають
401. - Дійсний ключ без необхідного дозволу повертає
403. - Жодних бухгалтерських побічних ефектів для жодного з випадків збою не створюється.
- Успішний запит не може читати чи змінювати записи іншої компанії.
- Помилки безпеки ніколи не повторюються автоматично.
Критерій успіху. Ви можете довести як коректність авторизації, так і ізоляцію орендарів за допомогою читання після завершення тесту, а не лише за кодами стану.
Доведіть виконання однієї чистої транзакції
Запустіть невеликий детермінований процес: створіть або знайдіть тестового клієнта, створіть інвойс із сумами у вигляді десяткових рядків, випустіть його, прочитайте інвойс та його бухгалтерський ефект, прочитайте запис аудиту та отримайте вебхук. Приклад початку роботи з Nordlet показує, що випуск інвойсу виділяє номер без пропусків, перевіряє відкритість періоду та проводить збалансований запис у журналі в межах однієї транзакції.
Переконайтеся, що інвойс досягає очікуваного стану, номер документа виділяється рівно один раз, дебети дорівнюють кредитам, ПДВ та підсумки рядків відповідають очікуваному розрахунку з округленням до найближчого (half-up), запис аудиту містить ім'я суб'єкта та зміни, вебхук зрештою доставляється, а відповідь API та повторне читання збігаються щодо ідентифікатора та стану інвойсу.
Одне відмінність, яку варто пам'ятати: надходження вебхука не є доказом того, що операція була зафіксована (committed). Відповідь API разом із повторним читанням є вашими основними твердженнями щодо транзакції. Вебхук підтверджує лише сповіщення нижчого рівня, і він тестується окремо.
Ідемпотентність: той самий ключ, те саме тіло
Згенеруйте один ключ на одну логічну бізнес-операцію, а не один ключ на кожну спробу мережевого з'єднання. Надішліть мутацію з ключем K та тілом B, збережіть створений ідентифікатор, а потім надішліть той самий запит знову.
Переконайтеся, що друга відповідь представляє ту саму операцію, ідентифікатор об'єкта ідентичний, і існує один бізнес-об'єкт, один запис (posting), один номер документа, один фінансовий ефект. Повторна відповідь повинна містити задокументований заголовок повтору — у випадку Nordlet це x-idempotent-replay: true, — а журнал аудиту не повинен показувати другу мутацію.
Два правила зберігання змінюють те, чого ваш цикл повторів повинен очікувати, тому перевірте їх безпосередньо: збережена відповідь 4xx відтворюється як будь-яка інша, тоді як 5xx не зберігається взагалі, тому повторна спроба після помилки сервера повторно виконує операцію, а не відтворює її. Ключі також мають термін дії, який у Nordlet становить 24 години, що обмежує час доступності повтору.
Документація Nordlet обіцяє, що додавання заголовка Idempotency-Key запобігає створенню дублікатів при повторних запитах, а його угоди щодо API визначають точний заголовок повтору та вимогу ідентичності відповіді побайтово. Цей принцип відповідає опублікованій моделі Stripe: ключ ідентифікує логічну операцію, повтори повертають збережений результат, а повторно використаний ключ із іншими параметрами відхиляється замість мовчазної зміни.
Критерій успіху. Існує рівно один перехід фінансового стану незалежно від того, скільки разів клієнт надсилає той самий запит протягом вікна повтору.
Ідемпотентність: той самий ключ, змінене тіло
Надішліть початковий запит із ключем K та тілом B1, а потім другий запит із ключем K та тілом B2, де відрізняється принаймні одна сума, клієнт, дата чи рядок.
Очікуваний результат: запит відхиляється із задокументованою помилкою повторного використання ключа (Nordlet повертає 422 idempotency_key_reuse), початковий запис залишається незмінним, жодного другого запису, номера чи мутації в аудиті не з'являється, а клієнт не «виправляє» ситуацію шляхом генерації нового ключа. Мовчазне прийняття B2 зробило б ключі повторів небезпечними.
Конкурентні дубльовані запити
Надішліть два однакових запити з тим самим ключем в один і той самий момент часу. Залежно від задокументованої поведінки, один виконується, тоді як інший отримує конфлікт виконання — Nordlet повертає 409 idempotency_in_progress, — або обидва врешті-решт спостерігають однаковий результат.
Переконайтеся, що обидва виклики не можуть створити незалежні фінансові ефекти, виділено щонайбільше один номер документа, тимчасовий конфлікт повторюється з тим самим ключем, а не з новим, кінцевий ресурс є повним і доступним для читання, а запис ідемпотентності не залишається назавжди заблокованим.
Типова помилка. Бібліотека повторів, яка створює новий UUID для кожної спроби, перетворює одну логічну операцію на кілька незалежних записів і повністю нівелює ідемпотентність.
Невизначений результат після розриву з'єднання
Це найважливіша симуляція виробничого середовища. Надішліть мутацію з ключем K, дозвольте серверу зафіксувати її, розірвіть з'єднання до надходження відповіді, зафіксуйте результат як невідомий, а потім повторіть запит із тим самим ключем K і звіртеся, прочитавши дані ресурсу та аудиту.
Переконайтеся, що повторна спроба повертає початкову або задокументовану відповідь повтору, існує рівно один фінансовий побічний ефект, клієнт позначає операцію завершеною лише після успішного повтору або читання для звірки, і жодного нового ключа не генерується. Тайм-аут не означає «нічого не сталося». Stripe описує ідемпотентність саме як захист від помилок з'єднання, коли сервер міг опрацювати мутацію до того, як клієнт встиг отримати відповідь.
Консервативна політика повторів
Рішення про повтор залежать як від помилки, так і від того, чи є операція ідемпотенттною, а не лише від HTTP-методу.
Зазвичай можна повторювати зі збереженням того самого ключа: скидання з'єднання (connection reset) або тайм-аут після ідемпотентної мутації, 408, 429 після дотримання заголовка retry-after, а також 502/503/504. Задокументовану відповідь про виконання ідемпотентності (in-progress) можна повторити після короткої затримки.
Не можна повторювати без звірки: помилки валідації 400/422, збої авторизації 401/403, 404 для відсутнього ресурсу, бізнес-конфлікти 409, не пов'язані з ключем у процесі виконання, змінене корисне навантаження для існуючого ключа, а також відхилення через блокування періоду.
Перевірте обмежене експоненційне зростання затримок (backoff) із випадковим відхиленням (jitter), явну максимальну кількість спроб і витрачений час, дотримання заголовка retry-after для обмежень швидкості (rate limits), збереження того самого ключа під час повторів однієї операції, новий ключ для кожної нової операції, а також журнали (logs), які відокремлюють початкову спробу від повторних.
Протестуйте обмеження швидкості окремо. Перетгніть задокументований поріг — Nordlet за замовчуванням дозволяє 300 запитів на хвилину на один ключ API, а деякі неавтентифіковані кінцеві точки мають власні нижчі ліміти на адресу — і переконайтеся, що сервер повертає відповідь про перевищення ліміту, клієнт обробляє retry-after як затримку, а не як помилку, робить паузу замість бомбардування запитами, а повторна спроба обмеженої мутації повторно використовує оригінальний ключ. Заголовки x-ratelimit-* звітують про залишок ліміту, що є гарною основою для попередньої перевірки. Тримайте тести обмеження швидкості подалі від фінансових тверджень, якщо ваша фікстура не може відрізнити обмежену спробу від зафіксованої.
Перевірка підпису вебхуків
Створіть приймач, який зберігає вихідні байти. Для кожної події: зафіксуйте вихідне тіло, прочитайте заголовок x-nordlet-signature, обчисліть очікуваний HMAC-SHA256 за допомогою секрету підписки, порівняйте за допомогою функції константного часу (constant-time function), розпарсіть JSON лише після перевірки, запишіть подію доставки та поверніть швидку відповідь 2xx після довговічного прийняття. Nordlet підписує HMAC на основі вихідного тіла, надсилає його як sha256=<hex> та повторює доставку з експоненційним зростанням затримок. Посібник із вебхуків від Stripe наголошує на перевірці немодифікованого вихідного тіла за допомогою порівняння в константному часі.
Виконайте негативні тести: правильні тіло та секрет, правильне тіло з неправильним секретом, зміна тіла на один байт, пересеріалізований JSON зі зміненими пробілами, відсутній заголовок, пошкоджений підпис, повторна доставка старої події (якщо схема включає часову мітку), а також дійсний підпис, доставлений на не ту підписку.
Критерії успіху. Приймаються лише правильні вихідні тіло та секрет. Недійсні підписи отримують код відповіді не-2xx. Недійсні дані ніколи не потрапляють у бухгалтерську чергу. Секрети приховані від звичайних журналів. Обробник підтверджує отримання лише після того, як подія надійно записана.
Дублювання та порушення порядку вебхуків
Надійність доставки є окремою проблемою від ідемпотентності API, і приймач також має бути ідемпотентним. Доставте одну й ту саму подію двічі, доставте подію sale_invoice.paid перед подією sale_invoice.issued, поверніть 500 після збереження, але до надсилання відповіді, і повторіть подію, яку споживач уже опрацював.
Переконайтеся, що дубльовані доставки не дублюють платежі, зміни статусу чи дії в журналі, що ключ дедупликації використовує ідентифікатор доставки або події від провайдера, якщо він існує (або задокументований складений ключ, але ніколи довільну часову мітку), що події поза початковим порядком ніколи не перезаписують новіший стан старішим, що споживач може отримати поточний стан з API, коли порядку недостатньо, і що невдалі спроби споживача проходять через чергу або шлях мертвих листів (dead-letter path). Stripe чітко попереджає, що порядок доставки не гарантується, і рекомендує зберігати ідентифікатори опрацьованих подій.
Зберігайте кінцеву точку простою: перевірте форму, перевірте підпис, збережіть або поставте в чергу, підтвердіть, поверніть 2xx. Звірка та сповіщення належать до асинхронного обробника. Протестуйте це, змусивши подальшу роботу заснути (sleep) довше тайм-ауту доставки; правильний приймач все одно підтверджує отримання після надійного розміщення в черзі.
Відкриті та заблоковані бухгалтерські періоди
Nordlet забезпечує дотримання блокування періодів під час проведення (posting) як для ручних журналів, так і для потоків документів та імпорту, тому протестуйте цей контроль у кількох місцях. Створіть еквівалентні документи з датами у відкритому періоді, у заблокованому періоді, на межі безпосередньо перед блокуванням, у перший день наступного відкритому періоду, а також із чинною датою документа, але недійсною датою проведення, якщо API розділяє ці два поняття.
Для випадку заблокованого періоду переконайтеся, що запит відхиляється із задокументованою помилкою блокування періоду — у Nordlet це 409 conflict, — що рядки журналу не записуються, номер документа не споживається, документ не переходить у статус проведеного, а успішний вебхук не надсилається. Версія з відкритим періодом повинна завершитися успішно з тими самими фінансовими сумами.
Тест на стан гонитви (Race test). Запустіть проведення у відкритому періоді, заблокуйте період посеред виконання запиту та переконайтеся, що фіксація не може обійти блокування. Критична умова успіху: зафіксоване проведення ніколи не потрапляє в період, який був заблокований до того, як транзакція завершилася фіксацією.
Аудитовуваність та звірка
Для кожної успішної мутації переконайтеся, що бізнес-об'єкт існує, бухгалтерський запис існує з рівними дебетами та кредитами, запис аудиту існує, подія вебхука існує або доступна для отримання, а ваш запис операції вказує на ідентифікатор об'єкта провайдера, причому повторні спроби посилаються на ту саму логічну операцію.
Для кожної відхиленої мутації переконайтеся, що частковий документ не існує, жоден номер не було споживано (якщо контракт не дозволяє резервування), жодного запису в головній книзі немає, жодного успішного вебхука немає, а помилка містить ідентифікатор запиту для діагностики. Угоди Nordlet описують аудитовуваність мутацій та збалансовані проводки як гарантії платформи, причому баланс забезпечується відкладеним тригером бази даних під час фіксації, а не лише кодом додатка. Перетворіть ці гарантії на запити звірки після тесту, замість того щоб довіряти HTTP-відповіді.
Основна матриця тестів
| Область | Дія тесту | Очікуване твердження | Критерій успіху |
|---|---|---|---|
| Авторизація | Немає ключа, недійсний ключ, недостатня область дії | Структуровані 401/403; немає побічних ефектів |
Усі відхилені спроби не створюють жодних бухгалтерських записів |
| Схема та гроші | Недійсний десятковий дріб, дата, обов'язкове поле | Помилка валідації на рівні поля | Немає повторних спроб або часткового запису |
| Звичайне проведення | Створення та випуск інвойсу у відкритому періоді | Один документ, один збалансований журнал, один запис аудиту | Стан після читання та головна книга збігаються |
| Повтор із тим самим ключем | Повторення ідентичної мутації, той самий ключ і тіло | Початковий результат відтворено; немає дублікатів | Ідентифікатор об'єкта, номер, ефект журналу та аудит залишаються єдиними |
| Змінене тіло, той самий ключ | Повторне використання ключа з іншою сумою | Задокументоване відхилення повторного використання ключа | Початковий запис незмінний; нічого нового не записано |
| Розірване з'єднання | Фіксація, розрив, повтор із тим самим ключем | Відтворений результат | Рівно один фінансовий побічний ефект |
| Заблокований період | Проведення документа з датою в заблокованому періоді | Відхилення через блокування періоду | Немає журналу, немає номера, немає успішного вебхука |
| Підпис вебхука | Неправильний секрет, змінене тіло | Не-2xx від приймача | Приймаються лише правильне вихідне тіло та секрет |
З чого б я почав у першу чергу
Якщо часу мало, запустіть тести в порядку, який виловлює найдорожчі помилки. Почніть із повтору при розривне з'єднання, оскільки саме цей сценарій створює найбільше реальних дублів. Потім перевірте пару ідемпотентності з тим самим ключем та зміненим тілом. Далі — заблоковані періоди, оскільки проведення в закритому місяці є проблемою аудиту, яка виявляється занадто пізно. Перевірка підписів та вебхуки не за порядком ідуть наступними, а повна схема та матриця авторизації можуть працювати постійно в CI, як тільки клієнт стабілізується.
Усе інше в цьому плані підтримує ці п'ять пунктів. Добийтеся їх зеленого стану (успішного виконання) для тестової компанії з читанням для звірки, що підтверджує побічні ефекти, і ви будете впевнені, що інтеграція є безпечною до того, як вона торкнеться хоча б однієї реальної головної книги. Ви можете створити тестову компанію та обмежений тестовий ключ за кілька хвилин на сторінці Початок роботи та перевірити, які модулі вам потрібні, за списком можливостей.
FAQ
Чи достатньо відповіді 2xx, щоб підтвердити успішність бухгалтерського запису?
Ні. Код стану підтверджує лише те, що сервер відповів, а не те, що існує рівно один документ, одне виділення номера та один збалансований запис у журналі. Кожен тест мутації повинен супроводжуватися повторним читанням об'єкта, його бухгалтерського ефекту та запису аудиту. Найнебезпечнішим є протилежний випадок: операція була зафіксована сервером, тоді як клієнт взагалі не отримав відповіді.
Чи варто генерувати новий ключ ідемпотентності, коли запит завершується тайм-аутом?
Ні, і це помилка, яка спричиняє більшість дублів у виробництві. Ключ ідентифікує логічну бізнес-операцію, а не мережеву спробу. Повторіть запит із тим самим ключем, а потім звіртеся шляхом читання ресурсу. Бібліотека повторів, яка створює новий UUID для кожної спроби, перетворює один задуманий інвойс на кілька незалежних записів.
Що станеться, якщо я повторно використаю ключ ідемпотентності з іншим корисним навантаженням (payload)?
Коректне API відхиляє його замість мовчазного застосування нового тіла. Nordlet повертає 422 idempotency_key_reuse і залишає початковий запис недоторканим. Ваш клієнт повинен сприймати це як програмну помилку, яку потрібно виправити, а не як умову, яку слід обійти шляхом генерації нового ключа.
Чи є вебхуки замінником для повторного читання створеного об'єкта?
Ні. Доставка вебхука доводить сповіщення нижчого рівня, а не те, що транзакція зафіксувалася так, як ви очікували, і порядок доставки не гарантується. Перевірте підпис вихідного тіла, виконайте дедуплікацію за ідентифікатором доставки або події та отримуйте поточний стан з API щоразу, коли самого порядку подій недостатньо для визначення змін.
Як протестувати проведення в заблокований бухгалтерський період?
Створіть еквівалентні документи у відкритому та заблокованому періодах і переконайтеся, що випадок із заблокованим періодом відхиляється без рядків журналу, без спожитих номерів документів, без переходу стану та без успішного вебхука. Потім запустіть версію з гонкою: почніть проведення у відкритому періоді та заблокуйте період посеред виконання запиту. Зафіксоване проведення ніколи не повинно потрапляти в період, який був заблокований до фіксації транзакції.
Чи можу я запускати тести на дублювання великих обсягів у тестовій компанії?
Так. Тестова компанія поводиться як справжня, має мітку тестових даних і видаляється одразу замість періоду зберігання, тому вона є правильним місцем для деструктивних тестів та тестів на дублювання. Використання тестового середовища все одно тарифікується як реальне, тому плануйте обсяги, а не запускайте цикли нескінченно.